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