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