1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.index.updater;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.BufferedInputStream;
26 import java.io.BufferedOutputStream;
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileNotFoundException;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.OutputStream;
36 import java.io.OutputStreamWriter;
37 import java.io.Writer;
38 import java.nio.charset.StandardCharsets;
39 import java.nio.file.Files;
40 import java.text.ParseException;
41 import java.text.SimpleDateFormat;
42 import java.util.ArrayList;
43 import java.util.Date;
44 import java.util.List;
45 import java.util.Properties;
46 import java.util.Set;
47 import java.util.TimeZone;
48
49 import org.apache.lucene.document.Document;
50 import org.apache.lucene.index.DirectoryReader;
51 import org.apache.lucene.index.IndexReader;
52 import org.apache.lucene.index.IndexWriter;
53 import org.apache.lucene.index.IndexWriterConfig;
54 import org.apache.lucene.index.MultiBits;
55 import org.apache.lucene.store.Directory;
56 import org.apache.lucene.util.Bits;
57 import org.apache.maven.index.context.DocumentFilter;
58 import org.apache.maven.index.context.IndexUtils;
59 import org.apache.maven.index.context.IndexingContext;
60 import org.apache.maven.index.context.NexusAnalyzer;
61 import org.apache.maven.index.context.NexusIndexWriter;
62 import org.apache.maven.index.fs.Lock;
63 import org.apache.maven.index.fs.Locker;
64 import org.apache.maven.index.incremental.IncrementalHandler;
65 import org.apache.maven.index.updater.IndexDataReader.IndexDataReadResult;
66 import org.codehaus.plexus.util.FileUtils;
67 import org.codehaus.plexus.util.io.RawInputStreamFacade;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71
72
73
74
75
76
77 @Singleton
78 @Named
79 public class DefaultIndexUpdater implements IndexUpdater {
80
81 private final Logger logger = LoggerFactory.getLogger(getClass());
82
83 protected Logger getLogger() {
84 return logger;
85 }
86
87 private final IncrementalHandler incrementalHandler;
88
89 private final List<IndexUpdateSideEffect> sideEffects;
90
91 @Inject
92 public DefaultIndexUpdater(
93 final IncrementalHandler incrementalHandler, final List<IndexUpdateSideEffect> sideEffects) {
94 this.incrementalHandler = incrementalHandler;
95 this.sideEffects = sideEffects;
96 }
97
98 public IndexUpdateResult fetchAndUpdateIndex(final IndexUpdateRequest updateRequest) throws IOException {
99 IndexUpdateResult result = new IndexUpdateResult();
100
101 IndexingContext context = updateRequest.getIndexingContext();
102
103 ResourceFetcher fetcher = null;
104
105 if (!updateRequest.isOffline()) {
106 fetcher = updateRequest.getResourceFetcher();
107
108
109
110 if (fetcher == null) {
111 throw new IOException("Update of the index without provided ResourceFetcher is impossible.");
112 }
113
114 fetcher.connect(context.getId(), context.getIndexUpdateUrl());
115 }
116
117 File cacheDir = updateRequest.getLocalIndexCacheDir();
118 Locker locker = updateRequest.getLocker();
119 Lock lock = locker != null && cacheDir != null ? locker.lock(cacheDir) : null;
120 try {
121 if (cacheDir != null) {
122 LocalCacheIndexAdaptor cache = new LocalCacheIndexAdaptor(cacheDir, result);
123
124 if (!updateRequest.isOffline()) {
125 cacheDir.mkdirs();
126
127 try {
128 if (fetchAndUpdateIndex(updateRequest, fetcher, cache).isSuccessful()) {
129 cache.commit();
130 }
131 } finally {
132 fetcher.disconnect();
133 }
134 }
135
136 fetcher = cache.getFetcher();
137 } else if (updateRequest.isOffline()) {
138 throw new IllegalArgumentException("LocalIndexCacheDir can not be null in offline mode");
139 }
140
141 try {
142 if (!updateRequest.isCacheOnly()) {
143 LuceneIndexAdaptor target = new LuceneIndexAdaptor(updateRequest);
144 result = fetchAndUpdateIndex(updateRequest, fetcher, target);
145
146 if (result.isSuccessful()) {
147 target.commit();
148 }
149 }
150 } finally {
151 fetcher.disconnect();
152 }
153 } finally {
154 if (lock != null) {
155 lock.release();
156 }
157 }
158
159 return result;
160 }
161
162 private Date loadIndexDirectory(
163 final IndexUpdateRequest updateRequest,
164 final ResourceFetcher fetcher,
165 final boolean merge,
166 final String remoteIndexFile)
167 throws IOException {
168 File indexDir;
169 if (updateRequest.getIndexTempDir() != null) {
170 updateRequest.getIndexTempDir().mkdirs();
171 indexDir = Files.createTempDirectory(updateRequest.getIndexTempDir().toPath(), remoteIndexFile + ".dir")
172 .toFile();
173 } else {
174 indexDir = Files.createTempDirectory(remoteIndexFile + ".dir").toFile();
175 }
176 try (BufferedInputStream is = new BufferedInputStream(fetcher.retrieve(remoteIndexFile));
177 Directory directory = updateRequest.getFSDirectoryFactory().open(indexDir)) {
178 Date timestamp;
179
180 Set<String> rootGroups;
181 Set<String> allGroups;
182 if (remoteIndexFile.endsWith(".gz")) {
183 IndexDataReadResult result =
184 unpackIndexData(is, updateRequest.getThreads(), directory, updateRequest.getIndexingContext());
185 timestamp = result.getTimestamp();
186 rootGroups = result.getRootGroups();
187 allGroups = result.getAllGroups();
188 } else {
189
190 throw new IllegalArgumentException(
191 "The legacy format is no longer supported " + "by this version of maven-indexer.");
192 }
193
194 if (updateRequest.getDocumentFilter() != null) {
195 filterDirectory(directory, updateRequest.getDocumentFilter());
196 }
197
198 if (merge) {
199 updateRequest.getIndexingContext().merge(directory);
200 } else {
201 updateRequest.getIndexingContext().replace(directory, allGroups, rootGroups);
202 }
203 if (sideEffects != null && sideEffects.size() > 0) {
204 getLogger().info(IndexUpdateSideEffect.class.getName() + " extensions found: " + sideEffects.size());
205 for (IndexUpdateSideEffect sideeffect : sideEffects) {
206 sideeffect.updateIndex(directory, updateRequest.getIndexingContext(), merge);
207 }
208 }
209
210 return timestamp;
211 } finally {
212 try {
213 FileUtils.deleteDirectory(indexDir);
214 } catch (IOException ex) {
215
216 }
217 }
218 }
219
220 private static void filterDirectory(final Directory directory, final DocumentFilter filter) throws IOException {
221 IndexReader r = null;
222 IndexWriter w = null;
223 try {
224 r = DirectoryReader.open(directory);
225 w = new NexusIndexWriter(directory, new IndexWriterConfig(new NexusAnalyzer()));
226
227 Bits liveDocs = MultiBits.getLiveDocs(r);
228
229 int numDocs = r.maxDoc();
230
231 for (int i = 0; i < numDocs; i++) {
232 if (liveDocs != null && !liveDocs.get(i)) {
233 continue;
234 }
235
236 Document d = r.document(i);
237
238 if (!filter.accept(d)) {
239 boolean success = w.tryDeleteDocument(r, i) != -1;
240
241 }
242 }
243 w.commit();
244 } finally {
245 IndexUtils.close(r);
246 IndexUtils.close(w);
247 }
248
249 w = null;
250 try {
251
252 w = new NexusIndexWriter(directory, new IndexWriterConfig(new NexusAnalyzer()));
253
254 w.commit();
255 } finally {
256 IndexUtils.close(w);
257 }
258 }
259
260 private Properties loadIndexProperties(final File indexDirectoryFile, final String remoteIndexPropertiesName) {
261 File indexProperties = new File(indexDirectoryFile, remoteIndexPropertiesName);
262
263 try (FileInputStream fis = new FileInputStream(indexProperties)) {
264 Properties properties = new Properties();
265
266 properties.load(fis);
267
268 return properties;
269 } catch (IOException e) {
270 getLogger().debug("Unable to read remote properties stored locally", e);
271 }
272 return null;
273 }
274
275 private void storeIndexProperties(final File dir, final String indexPropertiesName, final Properties properties)
276 throws IOException {
277 File file = new File(dir, indexPropertiesName);
278
279 if (properties != null) {
280 try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
281 properties.store(os, null);
282 }
283 } else {
284 file.delete();
285 }
286 }
287
288 private Properties downloadIndexProperties(final ResourceFetcher fetcher) throws IOException {
289 try (InputStream fis = fetcher.retrieve(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE)) {
290 Properties properties = new Properties();
291
292 properties.load(fis);
293
294 return properties;
295 }
296 }
297
298 public Date getTimestamp(final Properties properties, final String key) {
299 String indexTimestamp = properties.getProperty(key);
300
301 if (indexTimestamp != null) {
302 try {
303 SimpleDateFormat df = new SimpleDateFormat(IndexingContext.INDEX_TIME_FORMAT);
304 df.setTimeZone(TimeZone.getTimeZone("GMT"));
305 return df.parse(indexTimestamp);
306 } catch (ParseException ex) {
307 }
308 }
309 return null;
310 }
311
312
313
314
315
316
317
318 public static IndexDataReadResult unpackIndexData(
319 final InputStream is, final int threads, final Directory d, final IndexingContext context)
320 throws IOException {
321 IndexWriterConfig config = new IndexWriterConfig(new NexusAnalyzer());
322 config.setUseCompoundFile(false);
323 NexusIndexWriter w = new NexusIndexWriter(d, config);
324 try {
325 IndexDataReader dr = new IndexDataReader(is, threads);
326
327 return dr.readIndex(w, context);
328 } finally {
329 IndexUtils.close(w);
330 }
331 }
332
333
334
335
336 public static class FileFetcher implements ResourceFetcher {
337 private final File basedir;
338
339 public FileFetcher(File basedir) {
340 this.basedir = basedir;
341 }
342
343 public void connect(String id, String url) throws IOException {
344
345 }
346
347 public void disconnect() throws IOException {
348
349 }
350
351 public void retrieve(String name, File targetFile) throws IOException, FileNotFoundException {
352 FileUtils.copyFile(getFile(name), targetFile);
353 }
354
355 public InputStream retrieve(String name) throws IOException, FileNotFoundException {
356 return new FileInputStream(getFile(name));
357 }
358
359 private File getFile(String name) {
360 return new File(basedir, name);
361 }
362 }
363
364 private abstract class IndexAdaptor {
365 protected final File dir;
366
367 protected Properties properties;
368
369 protected IndexAdaptor(File dir) {
370 this.dir = dir;
371 }
372
373 public abstract Properties getProperties();
374
375 public abstract void storeProperties() throws IOException;
376
377 public abstract void addIndexChunk(ResourceFetcher source, String filename) throws IOException;
378
379 public abstract Date setIndexFile(ResourceFetcher source, String string) throws IOException;
380
381 public Properties setProperties(ResourceFetcher source) throws IOException {
382 this.properties = downloadIndexProperties(source);
383 return properties;
384 }
385
386 public abstract Date getTimestamp();
387
388 public void commit() throws IOException {
389 storeProperties();
390 }
391 }
392
393 private class LuceneIndexAdaptor extends IndexAdaptor {
394 private final IndexUpdateRequest updateRequest;
395
396 LuceneIndexAdaptor(IndexUpdateRequest updateRequest) {
397 super(updateRequest.getIndexingContext().getIndexDirectoryFile());
398 this.updateRequest = updateRequest;
399 }
400
401 public Properties getProperties() {
402 if (properties == null) {
403 properties = loadIndexProperties(dir, IndexingContext.INDEX_UPDATER_PROPERTIES_FILE);
404 }
405 return properties;
406 }
407
408 public void storeProperties() throws IOException {
409 storeIndexProperties(dir, IndexingContext.INDEX_UPDATER_PROPERTIES_FILE, properties);
410 }
411
412 public Date getTimestamp() {
413 return updateRequest.getIndexingContext().getTimestamp();
414 }
415
416 public void addIndexChunk(ResourceFetcher source, String filename) throws IOException {
417 loadIndexDirectory(updateRequest, source, true, filename);
418 }
419
420 public Date setIndexFile(ResourceFetcher source, String filename) throws IOException {
421 return loadIndexDirectory(updateRequest, source, false, filename);
422 }
423
424 public void commit() throws IOException {
425 super.commit();
426
427 updateRequest.getIndexingContext().commit();
428 }
429 }
430
431 private class LocalCacheIndexAdaptor extends IndexAdaptor {
432 private static final String CHUNKS_FILENAME = "chunks.lst";
433
434 private final IndexUpdateResult result;
435
436 private final ArrayList<String> newChunks = new ArrayList<>();
437
438 LocalCacheIndexAdaptor(File dir, IndexUpdateResult result) {
439 super(dir);
440 this.result = result;
441 }
442
443 public Properties getProperties() {
444 if (properties == null) {
445 properties = loadIndexProperties(dir, IndexingContext.INDEX_REMOTE_PROPERTIES_FILE);
446 }
447 return properties;
448 }
449
450 public void storeProperties() throws IOException {
451 storeIndexProperties(dir, IndexingContext.INDEX_REMOTE_PROPERTIES_FILE, properties);
452 }
453
454 public Date getTimestamp() {
455 Properties properties = getProperties();
456 if (properties == null) {
457 return null;
458 }
459
460 Date timestamp = DefaultIndexUpdater.this.getTimestamp(properties, IndexingContext.INDEX_TIMESTAMP);
461
462 if (timestamp == null) {
463 timestamp = DefaultIndexUpdater.this.getTimestamp(properties, IndexingContext.INDEX_LEGACY_TIMESTAMP);
464 }
465
466 return timestamp;
467 }
468
469 public void addIndexChunk(ResourceFetcher source, String filename) throws IOException {
470 File chunk = new File(dir, filename);
471 FileUtils.copyStreamToFile(new RawInputStreamFacade(source.retrieve(filename)), chunk);
472 newChunks.add(filename);
473 }
474
475 public Date setIndexFile(ResourceFetcher source, String filename) throws IOException {
476 cleanCacheDirectory(dir);
477
478 result.setFullUpdate(true);
479
480 File target = new File(dir, filename);
481 FileUtils.copyStreamToFile(new RawInputStreamFacade(source.retrieve(filename)), target);
482
483 return null;
484 }
485
486 @Override
487 public void commit() throws IOException {
488 File chunksFile = new File(dir, CHUNKS_FILENAME);
489 try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(chunksFile, true));
490 Writer w = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
491 for (String filename : newChunks) {
492 w.write(filename + "\n");
493 }
494 w.flush();
495 }
496 super.commit();
497 }
498
499 public List<String> getChunks() throws IOException {
500 ArrayList<String> chunks = new ArrayList<>();
501
502 File chunksFile = new File(dir, CHUNKS_FILENAME);
503 try (BufferedReader r = new BufferedReader(
504 new InputStreamReader(new FileInputStream(chunksFile), StandardCharsets.UTF_8))) {
505 String str;
506 while ((str = r.readLine()) != null) {
507 chunks.add(str);
508 }
509 }
510 return chunks;
511 }
512
513 public ResourceFetcher getFetcher() {
514 return new LocalIndexCacheFetcher(dir) {
515 @Override
516 public List<String> getChunks() throws IOException {
517 return LocalCacheIndexAdaptor.this.getChunks();
518 }
519 };
520 }
521 }
522
523 abstract static class LocalIndexCacheFetcher extends FileFetcher {
524 LocalIndexCacheFetcher(File basedir) {
525 super(basedir);
526 }
527
528 public abstract List<String> getChunks() throws IOException;
529 }
530
531 private IndexUpdateResult fetchAndUpdateIndex(
532 final IndexUpdateRequest updateRequest, ResourceFetcher source, IndexAdaptor target) throws IOException {
533 IndexUpdateResult result = new IndexUpdateResult();
534
535 if (!updateRequest.isForceFullUpdate()) {
536 Properties localProperties = target.getProperties();
537 Date localTimestamp = null;
538
539 if (localProperties != null) {
540 localTimestamp = getTimestamp(localProperties, IndexingContext.INDEX_TIMESTAMP);
541 }
542
543
544
545 Properties remoteProperties = target.setProperties(source);
546
547 Date updateTimestamp = getTimestamp(remoteProperties, IndexingContext.INDEX_TIMESTAMP);
548
549
550 if (updateTimestamp != null) {
551 List<String> filenames = incrementalHandler.loadRemoteIncrementalUpdates(
552 updateRequest, localProperties, remoteProperties);
553
554
555 if (filenames != null) {
556 for (String filename : filenames) {
557 target.addIndexChunk(source, filename);
558 }
559
560 result.setTimestamp(updateTimestamp);
561 result.setSuccessful(true);
562 return result;
563 }
564 } else {
565 updateTimestamp = getTimestamp(remoteProperties, IndexingContext.INDEX_LEGACY_TIMESTAMP);
566 }
567
568
569
570
571 if (localTimestamp != null) {
572
573
574
575 if (updateTimestamp != null && localTimestamp != null && !updateTimestamp.after(localTimestamp)) {
576
577 result.setSuccessful(true);
578 return result;
579 }
580 }
581 } else {
582
583 target.setProperties(source);
584 }
585
586 if (!updateRequest.isIncrementalOnly()) {
587 Date timestamp;
588 try {
589 timestamp = target.setIndexFile(source, IndexingContext.INDEX_FILE_PREFIX + ".gz");
590 if (source instanceof LocalIndexCacheFetcher) {
591
592
593 for (String filename : ((LocalIndexCacheFetcher) source).getChunks()) {
594 target.addIndexChunk(source, filename);
595 }
596 }
597 } catch (IOException ex) {
598
599 try {
600 timestamp = target.setIndexFile(source, IndexingContext.INDEX_FILE_PREFIX + ".zip");
601 } catch (IOException ex2) {
602 getLogger().error("Fallback to *.zip also failed: " + ex2);
603
604 throw ex;
605 }
606 }
607
608 result.setTimestamp(timestamp);
609 result.setSuccessful(true);
610 result.setFullUpdate(true);
611 }
612
613 return result;
614 }
615
616
617
618
619 protected void cleanCacheDirectory(File dir) throws IOException {
620 File[] members = dir.listFiles();
621 if (members == null) {
622 return;
623 }
624
625 for (File member : members) {
626 if (!Locker.LOCK_FILE.equals(member.getName())) {
627 FileUtils.forceDelete(member);
628 }
629 }
630 }
631 }