1 package org.apache.maven.index.context;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Date;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Set;
31
32 import org.apache.lucene.analysis.Analyzer;
33 import org.apache.lucene.document.Document;
34 import org.apache.lucene.document.Field;
35 import org.apache.lucene.index.CorruptIndexException;
36 import org.apache.lucene.index.IndexFileNameFilter;
37 import org.apache.lucene.index.IndexReader;
38 import org.apache.lucene.index.IndexWriter;
39 import org.apache.lucene.index.IndexWriterConfig;
40 import org.apache.lucene.index.Term;
41 import org.apache.lucene.search.IndexSearcher;
42 import org.apache.lucene.search.SearcherManager;
43 import org.apache.lucene.search.TermQuery;
44 import org.apache.lucene.search.TopDocs;
45 import org.apache.lucene.search.TopScoreDocCollector;
46 import org.apache.lucene.store.Directory;
47 import org.apache.lucene.store.FSDirectory;
48 import org.apache.maven.index.ArtifactInfo;
49 import org.apache.maven.index.artifact.GavCalculator;
50 import org.apache.maven.index.artifact.M2GavCalculator;
51 import org.codehaus.plexus.util.StringUtils;
52
53
54
55
56
57
58
59 public class DefaultIndexingContext
60 extends AbstractIndexingContext
61 {
62
63
64
65 private static final String INDEX_DIRECTORY = ".index";
66
67 public static final String FLD_DESCRIPTOR = "DESCRIPTOR";
68
69 private static final String FLD_DESCRIPTOR_CONTENTS = "NexusIndex";
70
71 private static final String FLD_IDXINFO = "IDXINFO";
72
73 private static final String VERSION = "1.0";
74
75 private static final Term DESCRIPTOR_TERM = new Term( FLD_DESCRIPTOR, FLD_DESCRIPTOR_CONTENTS );
76
77 private Directory indexDirectory;
78
79 private File indexDirectoryFile;
80
81 private String id;
82
83 private boolean searchable;
84
85 private String repositoryId;
86
87 private File repository;
88
89 private String repositoryUrl;
90
91 private String indexUpdateUrl;
92
93 private NexusIndexWriter indexWriter;
94
95 private SearcherManager searcherManager;
96
97 private Date timestamp;
98
99 private List<? extends IndexCreator> indexCreators;
100
101
102
103
104
105
106 private GavCalculator gavCalculator;
107
108 private DefaultIndexingContext( String id,
109 String repositoryId,
110 File repository,
111 String repositoryUrl, String indexUpdateUrl,
112 List<? extends IndexCreator> indexCreators, Directory indexDirectory,
113 boolean reclaimIndex )
114 throws ExistingLuceneIndexMismatchException, IOException
115 {
116 this.id = id;
117
118 this.searchable = true;
119
120 this.repositoryId = repositoryId;
121
122 this.repository = repository;
123
124 this.repositoryUrl = repositoryUrl;
125
126 this.indexUpdateUrl = indexUpdateUrl;
127
128 this.indexWriter = null;
129
130 this.searcherManager = null;
131
132 this.indexCreators = indexCreators;
133
134 this.indexDirectory = indexDirectory;
135
136
137
138
139
140 for ( IndexCreator indexCreator : indexCreators )
141 {
142 indexCreator.getIndexerFields();
143 }
144
145 this.gavCalculator = new M2GavCalculator();
146
147 prepareIndex( reclaimIndex );
148 }
149
150 public DefaultIndexingContext( String id, String repositoryId, File repository, File indexDirectoryFile,
151 String repositoryUrl, String indexUpdateUrl,
152 List<? extends IndexCreator> indexCreators, boolean reclaimIndex )
153 throws IOException, ExistingLuceneIndexMismatchException
154 {
155 this( id, repositoryId, repository, repositoryUrl, indexUpdateUrl, indexCreators,
156 FSDirectory.open( indexDirectoryFile ), reclaimIndex );
157
158 this.indexDirectoryFile = indexDirectoryFile;
159 }
160
161 @Deprecated
162 public DefaultIndexingContext( String id, String repositoryId, File repository, Directory indexDirectory,
163 String repositoryUrl, String indexUpdateUrl,
164 List<? extends IndexCreator> indexCreators, boolean reclaimIndex )
165 throws IOException, ExistingLuceneIndexMismatchException
166 {
167 this( id, repositoryId, repository, repositoryUrl, indexUpdateUrl, indexCreators, indexDirectory, reclaimIndex );
168
169 if ( indexDirectory instanceof FSDirectory )
170 {
171 this.indexDirectoryFile = ( (FSDirectory) indexDirectory ).getDirectory();
172 }
173 }
174
175 public Directory getIndexDirectory()
176 {
177 return indexDirectory;
178 }
179
180 public File getIndexDirectoryFile()
181 {
182 return indexDirectoryFile;
183 }
184
185 private void prepareIndex( boolean reclaimIndex )
186 throws IOException, ExistingLuceneIndexMismatchException
187 {
188 if ( IndexReader.indexExists( indexDirectory ) )
189 {
190 try
191 {
192
193 if ( IndexWriter.isLocked( indexDirectory ) )
194 {
195 IndexWriter.unlock( indexDirectory );
196 }
197
198 openAndWarmup();
199
200 checkAndUpdateIndexDescriptor( reclaimIndex );
201 }
202 catch ( IOException e )
203 {
204 if ( reclaimIndex )
205 {
206 prepareCleanIndex( true );
207 }
208 else
209 {
210 throw e;
211 }
212 }
213 }
214 else
215 {
216 prepareCleanIndex( false );
217 }
218
219 timestamp = IndexUtils.getTimestamp( indexDirectory );
220 }
221
222 private void prepareCleanIndex( boolean deleteExisting )
223 throws IOException
224 {
225 if ( deleteExisting )
226 {
227 closeReaders();
228
229
230 if ( IndexWriter.isLocked( indexDirectory ) )
231 {
232 IndexWriter.unlock( indexDirectory );
233 }
234
235 deleteIndexFiles( true );
236 }
237
238 openAndWarmup();
239
240 if ( StringUtils.isEmpty( getRepositoryId() ) )
241 {
242 throw new IllegalArgumentException( "The repositoryId cannot be null when creating new repository!" );
243 }
244
245 storeDescriptor();
246 }
247
248 private void checkAndUpdateIndexDescriptor( boolean reclaimIndex )
249 throws IOException, ExistingLuceneIndexMismatchException
250 {
251 if ( reclaimIndex )
252 {
253
254 storeDescriptor();
255 return;
256 }
257
258
259 if ( getSize() > 0 )
260 {
261 final TopScoreDocCollector collector = TopScoreDocCollector.create( 1, false );
262 final IndexSearcher indexSearcher = acquireIndexSearcher();
263 try
264 {
265 indexSearcher.search( new TermQuery( DESCRIPTOR_TERM ), collector );
266
267 if ( collector.getTotalHits() == 0 )
268 {
269 throw new ExistingLuceneIndexMismatchException(
270 "The existing index has no NexusIndexer descriptor" );
271 }
272
273 if ( collector.getTotalHits() > 1 )
274 {
275
276 storeDescriptor();
277 return;
278 }
279 else
280 {
281
282 Document descriptor = indexSearcher.doc( collector.topDocs().scoreDocs[0].doc );
283 String[] h = StringUtils.split( descriptor.get( FLD_IDXINFO ), ArtifactInfo.FS );
284
285 String repoId = h[1];
286
287
288
289
290
291
292
293
294 if ( getRepositoryId() == null )
295 {
296 repositoryId = repoId;
297 }
298 else if ( !getRepositoryId().equals( repoId ) )
299 {
300 throw new ExistingLuceneIndexMismatchException( "The existing index is for repository "
301 + "[" + repoId + "] and not for repository [" + getRepositoryId() + "]" );
302 }
303 }
304 }
305 finally
306 {
307 releaseIndexSearcher( indexSearcher );
308 }
309 }
310 }
311
312 private void storeDescriptor()
313 throws IOException
314 {
315 Document hdr = new Document();
316
317 hdr.add( new Field( FLD_DESCRIPTOR, FLD_DESCRIPTOR_CONTENTS, Field.Store.YES, Field.Index.NOT_ANALYZED ) );
318
319 hdr.add( new Field( FLD_IDXINFO, VERSION + ArtifactInfo.FS + getRepositoryId(), Field.Store.YES, Field.Index.NO ) );
320
321 IndexWriter w = getIndexWriter();
322
323 w.updateDocument( DESCRIPTOR_TERM, hdr );
324
325 w.commit();
326 }
327
328 private void deleteIndexFiles( boolean full )
329 throws IOException
330 {
331 if ( indexDirectory != null )
332 {
333 String[] names = indexDirectory.listAll();
334
335 if ( names != null )
336 {
337 IndexFileNameFilter filter = IndexFileNameFilter.getFilter();
338
339 for ( int i = 0; i < names.length; i++ )
340 {
341 if ( filter.accept( null, names[i] ) )
342 {
343 indexDirectory.deleteFile( names[i] );
344 }
345 }
346 }
347
348 if ( full )
349 {
350 if ( indexDirectory.fileExists( INDEX_PACKER_PROPERTIES_FILE ) )
351 {
352 indexDirectory.deleteFile( INDEX_PACKER_PROPERTIES_FILE );
353 }
354
355 if ( indexDirectory.fileExists( INDEX_UPDATER_PROPERTIES_FILE ) )
356 {
357 indexDirectory.deleteFile( INDEX_UPDATER_PROPERTIES_FILE );
358 }
359 }
360
361 IndexUtils.deleteTimestamp( indexDirectory );
362 }
363 }
364
365
366
367 public boolean isSearchable()
368 {
369 return searchable;
370 }
371
372 public void setSearchable( boolean searchable )
373 {
374 this.searchable = searchable;
375 }
376
377 public String getId()
378 {
379 return id;
380 }
381
382 public void updateTimestamp()
383 throws IOException
384 {
385 updateTimestamp( false );
386 }
387
388 public void updateTimestamp( boolean save )
389 throws IOException
390 {
391 updateTimestamp( save, new Date() );
392 }
393
394 public void updateTimestamp( boolean save, Date timestamp )
395 throws IOException
396 {
397 this.timestamp = timestamp;
398
399 if ( save )
400 {
401 IndexUtils.updateTimestamp( indexDirectory, getTimestamp() );
402 }
403 }
404
405 public Date getTimestamp()
406 {
407 return timestamp;
408 }
409
410 public int getSize()
411 throws IOException
412 {
413 final IndexSearcher is = acquireIndexSearcher();
414 try
415 {
416 return is.getIndexReader().numDocs();
417 }
418 finally
419 {
420 releaseIndexSearcher( is );
421 }
422 }
423
424 public String getRepositoryId()
425 {
426 return repositoryId;
427 }
428
429 public File getRepository()
430 {
431 return repository;
432 }
433
434 public String getRepositoryUrl()
435 {
436 return repositoryUrl;
437 }
438
439 public String getIndexUpdateUrl()
440 {
441 if ( repositoryUrl != null )
442 {
443 if ( indexUpdateUrl == null || indexUpdateUrl.trim().length() == 0 )
444 {
445 return repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + INDEX_DIRECTORY;
446 }
447 }
448 return indexUpdateUrl;
449 }
450
451 public Analyzer getAnalyzer()
452 {
453 return new NexusAnalyzer();
454 }
455
456 protected void openAndWarmup()
457 throws IOException
458 {
459
460 if ( indexWriter != null )
461 {
462 indexWriter.close();
463
464 indexWriter = null;
465 }
466 if ( searcherManager != null )
467 {
468 searcherManager.close();
469
470 searcherManager = null;
471 }
472
473 this.indexWriter = new NexusIndexWriter( getIndexDirectory(), getWriterConfig() );
474 this.indexWriter.commit();
475 this.searcherManager = new SearcherManager( indexWriter, false, new NexusIndexSearcherFactory( this ) );
476 }
477
478
479
480
481
482
483 protected IndexWriterConfig getWriterConfig()
484 {
485 return NexusIndexWriter.defaultConfig();
486 }
487
488 public IndexWriter getIndexWriter()
489 throws IOException
490 {
491 return indexWriter;
492 }
493
494 public IndexSearcher acquireIndexSearcher()
495 throws IOException
496 {
497
498 searcherManager.maybeRefresh();
499 return searcherManager.acquire();
500 }
501
502 public void releaseIndexSearcher( final IndexSearcher is )
503 throws IOException
504 {
505 if ( is == null )
506 {
507 return;
508 }
509 searcherManager.release( is );
510 }
511
512 public void commit()
513 throws IOException
514 {
515 getIndexWriter().commit();
516 }
517
518 public void rollback()
519 throws IOException
520 {
521 getIndexWriter().rollback();
522 }
523
524 public synchronized void optimize()
525 throws CorruptIndexException, IOException
526 {
527 getIndexWriter().optimize();
528 commit();
529 }
530
531 public synchronized void close( boolean deleteFiles )
532 throws IOException
533 {
534 if ( indexDirectory != null )
535 {
536 IndexUtils.updateTimestamp( indexDirectory, getTimestamp() );
537 closeReaders();
538 if ( deleteFiles )
539 {
540 deleteIndexFiles( true );
541 }
542 indexDirectory.close();
543 }
544 indexDirectory = null;
545 }
546
547 public synchronized void purge()
548 throws IOException
549 {
550 closeReaders();
551 deleteIndexFiles( true );
552 openAndWarmup();
553 try
554 {
555 prepareIndex( true );
556 }
557 catch ( ExistingLuceneIndexMismatchException e )
558 {
559
560 }
561 rebuildGroups();
562 updateTimestamp( true, null );
563 }
564
565 public synchronized void replace( Directory directory )
566 throws IOException
567 {
568 final Date ts = IndexUtils.getTimestamp( directory );
569 closeReaders();
570 deleteIndexFiles( false );
571 IndexUtils.copyDirectory( directory, indexDirectory );
572 openAndWarmup();
573
574 storeDescriptor();
575 rebuildGroups();
576 updateTimestamp( true, ts );
577 optimize();
578 }
579
580 public synchronized void merge( Directory directory )
581 throws IOException
582 {
583 merge( directory, null );
584 }
585
586 public synchronized void merge( Directory directory, DocumentFilter filter )
587 throws IOException
588 {
589 final IndexSearcher s = acquireIndexSearcher();
590 try
591 {
592 final IndexWriter w = getIndexWriter();
593 final IndexReader directoryReader = IndexReader.open( directory, true );
594 TopScoreDocCollector collector = null;
595 try
596 {
597 int numDocs = directoryReader.maxDoc();
598 for ( int i = 0; i < numDocs; i++ )
599 {
600 if ( directoryReader.isDeleted( i ) )
601 {
602 continue;
603 }
604
605 Document d = directoryReader.document( i );
606 if ( filter != null && !filter.accept( d ) )
607 {
608 continue;
609 }
610
611 String uinfo = d.get( ArtifactInfo.UINFO );
612 if ( uinfo != null )
613 {
614 collector = TopScoreDocCollector.create( 1, false );
615 s.search( new TermQuery( new Term( ArtifactInfo.UINFO, uinfo ) ), collector );
616 if ( collector.getTotalHits() == 0 )
617 {
618 w.addDocument( IndexUtils.updateDocument( d, this, false ) );
619 }
620 }
621 else
622 {
623 String deleted = d.get( ArtifactInfo.DELETED );
624
625 if ( deleted != null )
626 {
627
628
629
630 w.deleteDocuments( new Term( ArtifactInfo.UINFO, deleted ) );
631 w.addDocument( d );
632 }
633 }
634 }
635
636 }
637 finally
638 {
639 directoryReader.close();
640 commit();
641 }
642
643 rebuildGroups();
644 Date mergedTimestamp = IndexUtils.getTimestamp( directory );
645
646 if ( getTimestamp() != null && mergedTimestamp != null && mergedTimestamp.after( getTimestamp() ) )
647 {
648
649 updateTimestamp( true, mergedTimestamp );
650 }
651 else
652 {
653 updateTimestamp( true );
654 }
655 optimize();
656 }
657 finally
658 {
659 releaseIndexSearcher( s );
660 }
661 }
662
663 private void closeReaders()
664 throws CorruptIndexException, IOException
665 {
666 if ( searcherManager != null )
667 {
668 searcherManager.close();
669 searcherManager = null;
670 }
671 if ( indexWriter != null )
672 {
673 indexWriter.close();
674 indexWriter = null;
675 }
676 }
677
678 public GavCalculator getGavCalculator()
679 {
680 return gavCalculator;
681 }
682
683 public List<IndexCreator> getIndexCreators()
684 {
685 return Collections.unmodifiableList( indexCreators );
686 }
687
688
689
690 public synchronized void rebuildGroups()
691 throws IOException
692 {
693 final IndexSearcher is = acquireIndexSearcher();
694 try
695 {
696 final IndexReader r = is.getIndexReader();
697
698 Set<String> rootGroups = new LinkedHashSet<String>();
699 Set<String> allGroups = new LinkedHashSet<String>();
700
701 int numDocs = r.maxDoc();
702
703 for ( int i = 0; i < numDocs; i++ )
704 {
705 if ( r.isDeleted( i ) )
706 {
707 continue;
708 }
709
710 Document d = r.document( i );
711
712 String uinfo = d.get( ArtifactInfo.UINFO );
713
714 if ( uinfo != null )
715 {
716 ArtifactInfo info = IndexUtils.constructArtifactInfo( d, this );
717 rootGroups.add( info.getRootGroup() );
718 allGroups.add( info.groupId );
719 }
720 }
721
722 setRootGroups( rootGroups );
723 setAllGroups( allGroups );
724
725 optimize();
726 }
727 finally
728 {
729 releaseIndexSearcher( is );
730 }
731 }
732
733 public Set<String> getAllGroups()
734 throws IOException
735 {
736 return getGroups( ArtifactInfo.ALL_GROUPS, ArtifactInfo.ALL_GROUPS_VALUE, ArtifactInfo.ALL_GROUPS_LIST );
737 }
738
739 public synchronized void setAllGroups( Collection<String> groups )
740 throws IOException
741 {
742 setGroups( groups, ArtifactInfo.ALL_GROUPS, ArtifactInfo.ALL_GROUPS_VALUE, ArtifactInfo.ALL_GROUPS_LIST );
743 commit();
744 }
745
746 public Set<String> getRootGroups()
747 throws IOException
748 {
749 return getGroups( ArtifactInfo.ROOT_GROUPS, ArtifactInfo.ROOT_GROUPS_VALUE, ArtifactInfo.ROOT_GROUPS_LIST );
750 }
751
752 public synchronized void setRootGroups( Collection<String> groups )
753 throws IOException
754 {
755 setGroups( groups, ArtifactInfo.ROOT_GROUPS, ArtifactInfo.ROOT_GROUPS_VALUE, ArtifactInfo.ROOT_GROUPS_LIST );
756 commit();
757 }
758
759 protected Set<String> getGroups( String field, String filedValue, String listField )
760 throws IOException, CorruptIndexException
761 {
762 final TopScoreDocCollector collector = TopScoreDocCollector.create( 1, false );
763 final IndexSearcher indexSearcher = acquireIndexSearcher();
764 try
765 {
766 indexSearcher.search( new TermQuery( new Term( field, filedValue ) ), collector );
767 TopDocs topDocs = collector.topDocs();
768 Set<String> groups = new LinkedHashSet<String>( Math.max( 10, topDocs.totalHits ) );
769 if ( topDocs.totalHits > 0 )
770 {
771 Document doc = indexSearcher.doc( topDocs.scoreDocs[0].doc );
772 String groupList = doc.get( listField );
773 if ( groupList != null )
774 {
775 groups.addAll( Arrays.asList( groupList.split( "\\|" ) ) );
776 }
777 }
778 return groups;
779 }
780 finally
781 {
782 releaseIndexSearcher( indexSearcher );
783 }
784 }
785
786 protected void setGroups( Collection<String> groups, String groupField, String groupFieldValue,
787 String groupListField )
788 throws IOException, CorruptIndexException
789 {
790 final IndexWriter w = getIndexWriter();
791 w.updateDocument( new Term( groupField, groupFieldValue ),
792 createGroupsDocument( groups, groupField, groupFieldValue, groupListField ) );
793 }
794
795 protected Document createGroupsDocument( Collection<String> groups, String field, String fieldValue,
796 String listField )
797 {
798 final Document groupDoc = new Document();
799 groupDoc.add( new Field( field,
800 fieldValue, Field.Store.YES, Field.Index.NOT_ANALYZED ) );
801 groupDoc.add( new Field( listField,
802 ArtifactInfo.lst2str( groups ), Field.Store.YES, Field.Index.NO ) );
803 return groupDoc;
804 }
805
806 @Override
807 public String toString()
808 {
809 return id + " : " + timestamp;
810 }
811 }