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