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