View Javadoc
1   package org.apache.maven.index.context;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0    
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.channels.FileChannel;
25  import java.nio.channels.FileLock;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.StandardOpenOption;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.Date;
33  import java.util.LinkedHashSet;
34  import java.util.List;
35  import java.util.Set;
36  
37  import org.apache.lucene.analysis.Analyzer;
38  import org.apache.lucene.document.Document;
39  import org.apache.lucene.document.Field;
40  import org.apache.lucene.document.StoredField;
41  import org.apache.lucene.index.CorruptIndexException;
42  import org.apache.lucene.index.DirectoryReader;
43  import org.apache.lucene.index.IndexReader;
44  import org.apache.lucene.index.IndexWriter;
45  import org.apache.lucene.index.IndexWriterConfig;
46  import org.apache.lucene.index.MultiBits;
47  import org.apache.lucene.index.Term;
48  import org.apache.lucene.search.IndexSearcher;
49  import org.apache.lucene.search.SearcherManager;
50  import org.apache.lucene.search.TermQuery;
51  import org.apache.lucene.search.TopDocs;
52  import org.apache.lucene.search.TopScoreDocCollector;
53  import org.apache.lucene.store.Directory;
54  import org.apache.lucene.store.FSDirectory;
55  import org.apache.lucene.store.FSLockFactory;
56  import org.apache.lucene.store.Lock;
57  import org.apache.lucene.store.LockObtainFailedException;
58  import org.apache.lucene.util.Bits;
59  import org.apache.maven.index.ArtifactInfo;
60  import org.apache.maven.index.IndexerField;
61  import org.apache.maven.index.artifact.GavCalculator;
62  import org.apache.maven.index.artifact.M2GavCalculator;
63  import org.codehaus.plexus.util.StringUtils;
64  
65  /**
66   * The default {@link IndexingContext} implementation.
67   * 
68   * @author Jason van Zyl
69   * @author Tamas Cservenak
70   */
71  public class DefaultIndexingContext
72      extends AbstractIndexingContext
73  {
74      /**
75       * A standard location for indices served up by a webserver.
76       */
77      private static final String INDEX_DIRECTORY = ".index";
78  
79      public static final String FLD_DESCRIPTOR = "DESCRIPTOR";
80  
81      public static final String FLD_DESCRIPTOR_CONTENTS = "NexusIndex";
82  
83      public static final String FLD_IDXINFO = "IDXINFO";
84  
85      public static final String VERSION = "1.0";
86  
87      private static final Term DESCRIPTOR_TERM = new Term( FLD_DESCRIPTOR, FLD_DESCRIPTOR_CONTENTS );
88  
89      private Directory indexDirectory;
90  
91      private TrackingLockFactory lockFactory;
92  
93      private File indexDirectoryFile;
94  
95      private String id;
96  
97      private boolean searchable;
98  
99      private String repositoryId;
100 
101     private File repository;
102 
103     private String repositoryUrl;
104 
105     private String indexUpdateUrl;
106 
107     private NexusIndexWriter indexWriter;
108 
109     private SearcherManager searcherManager;
110 
111     private Date timestamp;
112 
113     private List<? extends IndexCreator> indexCreators;
114 
115     /**
116      * Currently nexus-indexer knows only M2 reposes
117      * <p>
118      * XXX move this into a concrete Scanner implementation
119      */
120     private GavCalculator gavCalculator;
121 
122     private DefaultIndexingContext( String id,
123                                     String repositoryId,
124                                     File repository, //
125                                     String repositoryUrl, String indexUpdateUrl,
126                                     List<? extends IndexCreator> indexCreators, Directory indexDirectory,
127                                     TrackingLockFactory lockFactory,
128                                     boolean reclaimIndex,
129                                     File indexDirectoryFile )
130         throws ExistingLuceneIndexMismatchException, IOException
131     {
132 
133         this.id = id;
134 
135         this.searchable = true;
136 
137         this.repositoryId = repositoryId;
138 
139         this.repository = repository;
140 
141         this.repositoryUrl = repositoryUrl;
142 
143         this.indexUpdateUrl = indexUpdateUrl;
144 
145         this.indexWriter = null;
146 
147         this.searcherManager = null;
148 
149         this.indexCreators = indexCreators;
150 
151         this.indexDirectory = indexDirectory;
152 
153         this.lockFactory = lockFactory;
154 
155         // eh?
156         // Guice does NOT initialize these, and we have to do manually?
157         // While in Plexus, all is well, but when in guice-shim,
158         // these objects are still LazyHintedBeans or what not and IndexerFields are NOT registered!
159         for ( IndexCreator indexCreator : indexCreators )
160         {
161             indexCreator.getIndexerFields();
162         }
163 
164         this.gavCalculator = new M2GavCalculator();
165 
166         prepareIndex( reclaimIndex );
167 
168         setIndexDirectoryFile( indexDirectoryFile );
169     }
170 
171     private DefaultIndexingContext( String id, String repositoryId, File repository, File indexDirectoryFile,
172                                    TrackingLockFactory lockFactory, String repositoryUrl, String indexUpdateUrl,
173                                    List<? extends IndexCreator> indexCreators, boolean reclaimIndex )
174         throws IOException, ExistingLuceneIndexMismatchException
175     {
176         this( id, repositoryId, repository, repositoryUrl, indexUpdateUrl, indexCreators,
177             FSDirectory.open( indexDirectoryFile.toPath(), lockFactory ), lockFactory, reclaimIndex,
178                 indexDirectoryFile );
179     }
180 
181     public DefaultIndexingContext( String id, String repositoryId, File repository, File indexDirectoryFile,
182                                    String repositoryUrl, String indexUpdateUrl,
183                                    List<? extends IndexCreator> indexCreators, boolean reclaimIndex )
184         throws IOException, ExistingLuceneIndexMismatchException
185     {
186         this( id, repositoryId, repository, indexDirectoryFile, new TrackingLockFactory( FSLockFactory.getDefault() ),
187               repositoryUrl, indexUpdateUrl, indexCreators, reclaimIndex );
188     }
189 
190     @Deprecated
191     public DefaultIndexingContext( String id, String repositoryId, File repository, Directory indexDirectory,
192                                    String repositoryUrl, String indexUpdateUrl,
193                                    List<? extends IndexCreator> indexCreators, boolean reclaimIndex )
194         throws IOException, ExistingLuceneIndexMismatchException
195     {
196         this( id, repositoryId, repository, repositoryUrl, indexUpdateUrl, indexCreators, indexDirectory, null,
197               reclaimIndex, indexDirectory instanceof FSDirectory
198                         ? ( (FSDirectory) indexDirectory ).getDirectory().toFile() : null
199         ); // Lock factory already installed - pass null
200     }
201 
202     public Directory getIndexDirectory()
203     {
204         return indexDirectory;
205     }
206     
207     /**
208      * Sets index location. As usually index is persistent (is on disk), this will point to that value, but in
209      * some circumstances (ie, using RAMDisk for index), this will point to an existing tmp directory.
210      */
211     protected void setIndexDirectoryFile( File dir )
212         throws IOException
213     {
214         if ( dir == null )
215         {
216             // best effort, to have a directory through the life of a ctx
217             this.indexDirectoryFile = Files.createTempDirectory( "mindexer-ctx" + id ).toFile();
218             this.indexDirectoryFile.deleteOnExit();
219         }
220         else
221         {
222             this.indexDirectoryFile = dir;
223         }
224     }
225 
226     public File getIndexDirectoryFile()
227     {
228         return indexDirectoryFile;
229     }
230 
231     private void prepareIndex( boolean reclaimIndex )
232         throws IOException, ExistingLuceneIndexMismatchException
233     {
234         if ( DirectoryReader.indexExists( indexDirectory ) )
235         {
236             try
237             {
238                 // unlock the dir forcibly
239                 try
240                 {
241                     indexDirectory.obtainLock( IndexWriter.WRITE_LOCK_NAME ).close();
242                 }
243                 catch ( LockObtainFailedException failed )
244                 {
245                     unlockForcibly( lockFactory, indexDirectory );
246                 }
247 
248                 openAndWarmup();
249 
250                 checkAndUpdateIndexDescriptor( reclaimIndex );
251             }
252             catch ( IOException e )
253             {
254                 if ( reclaimIndex )
255                 {
256                     prepareCleanIndex( true );
257                 }
258                 else
259                 {
260                     throw e;
261                 }
262             }
263         }
264         else
265         {
266             prepareCleanIndex( false );
267         }
268 
269         timestamp = IndexUtils.getTimestamp( indexDirectory );
270     }
271 
272     private void prepareCleanIndex( boolean deleteExisting )
273         throws IOException
274     {
275         if ( deleteExisting )
276         {
277             closeReaders();
278 
279             // unlock the dir forcibly
280             try
281             {
282                 indexDirectory.obtainLock( IndexWriter.WRITE_LOCK_NAME ).close();
283             }
284             catch ( LockObtainFailedException failed )
285             {
286                 unlockForcibly( lockFactory, indexDirectory );
287             }
288 
289             deleteIndexFiles( true );
290         }
291 
292         openAndWarmup();
293 
294         if ( StringUtils.isEmpty( getRepositoryId() ) )
295         {
296             throw new IllegalArgumentException( "The repositoryId cannot be null when creating new repository!" );
297         }
298 
299         storeDescriptor();
300     }
301 
302     private void checkAndUpdateIndexDescriptor( boolean reclaimIndex )
303         throws IOException, ExistingLuceneIndexMismatchException
304     {
305         if ( reclaimIndex )
306         {
307             // forcefully "reclaiming" the ownership of the index as ours
308             storeDescriptor();
309             return;
310         }
311 
312         // check for descriptor if this is not a "virgin" index
313         if ( getSize() > 0 )
314         {
315             final TopScoreDocCollector collector = TopScoreDocCollector.create( 1, Integer.MAX_VALUE );
316             final IndexSearcher indexSearcher = acquireIndexSearcher();
317             try
318             {
319                 indexSearcher.search( new TermQuery( DESCRIPTOR_TERM ), collector );
320 
321                 if ( collector.getTotalHits() == 0 )
322                 {
323                     throw new ExistingLuceneIndexMismatchException(
324                         "The existing index has no NexusIndexer descriptor" );
325                 }
326 
327                 if ( collector.getTotalHits() > 1 )
328                 {
329                     // eh? this is buggy index it seems, just iron it out then
330                     storeDescriptor();
331                 }
332                 else
333                 {
334                     // good, we have one descriptor as should
335                     Document descriptor = indexSearcher.doc( collector.topDocs().scoreDocs[0].doc );
336                     String[] h = StringUtils.split( descriptor.get( FLD_IDXINFO ), ArtifactInfo.FS );
337                     // String version = h[0];
338                     String repoId = h[1];
339 
340                     // // compare version
341                     // if ( !VERSION.equals( version ) )
342                     // {
343                     // throw new UnsupportedExistingLuceneIndexException(
344                     // "The existing index has version [" + version + "] and not [" + VERSION + "] version!" );
345                     // }
346 
347                     if ( getRepositoryId() == null )
348                     {
349                         repositoryId = repoId;
350                     }
351                     else if ( !getRepositoryId().equals( repoId ) )
352                     {
353                         throw new ExistingLuceneIndexMismatchException( "The existing index is for repository " //
354                             + "[" + repoId + "] and not for repository [" + getRepositoryId() + "]" );
355                     }
356                 }
357             }
358             finally
359             {
360                 releaseIndexSearcher( indexSearcher );
361             }
362         }
363     }
364 
365     private void storeDescriptor()
366         throws IOException
367     {
368         Document hdr = new Document();
369 
370         hdr.add( new Field( FLD_DESCRIPTOR, FLD_DESCRIPTOR_CONTENTS, IndexerField.KEYWORD_STORED ) );
371 
372         hdr.add( new StoredField( FLD_IDXINFO, VERSION + ArtifactInfo.FS + getRepositoryId() ) );
373 
374         IndexWriter w = getIndexWriter();
375 
376         w.updateDocument( DESCRIPTOR_TERM, hdr );
377 
378         w.commit();
379     }
380 
381     private void deleteIndexFiles( boolean full )
382         throws IOException
383     {
384         if ( indexDirectory != null )
385         {
386             String[] names = indexDirectory.listAll();
387 
388             if ( names != null )
389             {
390 
391                 for ( String name : names )
392                 {
393                     if ( !( name.equals( INDEX_PACKER_PROPERTIES_FILE )
394                         || name.equals( INDEX_UPDATER_PROPERTIES_FILE ) ) )
395                     {
396                         indexDirectory.deleteFile( name );
397                     }
398                 }
399             }
400 
401             if ( full )
402             {
403                 try
404                 {
405                     indexDirectory.deleteFile( INDEX_PACKER_PROPERTIES_FILE );
406                 }
407                 catch ( IOException ioe )
408                 {
409                     //Does not exist
410                 }
411 
412                 try
413                 {
414                     indexDirectory.deleteFile( INDEX_UPDATER_PROPERTIES_FILE );
415                 }
416                 catch ( IOException ioe )
417                 {
418                     //Does not exist
419                 }
420             }
421 
422             IndexUtils.deleteTimestamp( indexDirectory );
423         }
424     }
425 
426     // ==
427 
428     public boolean isSearchable()
429     {
430         return searchable;
431     }
432 
433     public void setSearchable( boolean searchable )
434     {
435         this.searchable = searchable;
436     }
437 
438     public String getId()
439     {
440         return id;
441     }
442 
443     public void updateTimestamp()
444         throws IOException
445     {
446         updateTimestamp( false );
447     }
448 
449     public void updateTimestamp( boolean save )
450         throws IOException
451     {
452         updateTimestamp( save, new Date() );
453     }
454 
455     public void updateTimestamp( boolean save, Date timestamp )
456         throws IOException
457     {
458         this.timestamp = timestamp;
459 
460         if ( save )
461         {
462             IndexUtils.updateTimestamp( indexDirectory, getTimestamp() );
463         }
464     }
465 
466     public Date getTimestamp()
467     {
468         return timestamp;
469     }
470 
471     public int getSize()
472         throws IOException
473     {
474         final IndexSearcher is = acquireIndexSearcher();
475         try
476         {
477             return is.getIndexReader().numDocs();
478         }
479         finally
480         {
481             releaseIndexSearcher( is );
482         }
483     }
484 
485     public String getRepositoryId()
486     {
487         return repositoryId;
488     }
489 
490     public File getRepository()
491     {
492         return repository;
493     }
494 
495     public String getRepositoryUrl()
496     {
497         return repositoryUrl;
498     }
499 
500     public String getIndexUpdateUrl()
501     {
502         if ( repositoryUrl != null )
503         {
504             if ( indexUpdateUrl == null || indexUpdateUrl.trim().length() == 0 )
505             {
506                 return repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + INDEX_DIRECTORY;
507             }
508         }
509         return indexUpdateUrl;
510     }
511 
512     public Analyzer getAnalyzer()
513     {
514         return new NexusAnalyzer();
515     }
516 
517     protected void openAndWarmup()
518         throws IOException
519     {
520         // IndexWriter (close)
521         if ( indexWriter != null )
522         {
523             indexWriter.close();
524 
525             indexWriter = null;
526         }
527         if ( searcherManager != null )
528         {
529             searcherManager.close();
530 
531             searcherManager = null;
532         }
533 
534         this.indexWriter = new NexusIndexWriter( getIndexDirectory(), getWriterConfig() );
535         this.indexWriter.commit(); // LUCENE-2386
536         this.searcherManager = new SearcherManager( indexWriter, false, false, new NexusIndexSearcherFactory( this ) );
537     }
538 
539     /**
540      * Returns new IndexWriterConfig instance
541      * 
542      * @since 5.1
543      */
544     protected IndexWriterConfig getWriterConfig()
545     {
546         return NexusIndexWriter.defaultConfig();
547     }
548 
549     public IndexWriter getIndexWriter()
550         throws IOException
551     {
552         return indexWriter;
553     }
554 
555     public IndexSearcher acquireIndexSearcher()
556         throws IOException
557     {
558         // TODO: move this to separate thread to not penalty next incoming searcher
559         searcherManager.maybeRefresh();
560         return searcherManager.acquire();
561     }
562 
563     public void releaseIndexSearcher( final IndexSearcher is )
564         throws IOException
565     {
566         if ( is == null )
567         {
568             return;
569         }
570         searcherManager.release( is );
571     }
572 
573     public void commit()
574         throws IOException
575     {
576         getIndexWriter().commit();
577     }
578 
579     public void rollback()
580         throws IOException
581     {
582         getIndexWriter().rollback();
583     }
584 
585     public synchronized void optimize()
586         throws CorruptIndexException, IOException
587     {
588         commit();
589     }
590 
591     public synchronized void close( boolean deleteFiles )
592         throws IOException
593     {
594         if ( indexDirectory != null )
595         {
596             IndexUtils.updateTimestamp( indexDirectory, getTimestamp() );
597             closeReaders();
598             if ( deleteFiles )
599             {
600                 deleteIndexFiles( true );
601             }
602             indexDirectory.close();
603         }
604         indexDirectory = null;
605     }
606 
607     public synchronized void purge()
608         throws IOException
609     {
610         closeReaders();
611         deleteIndexFiles( true );
612         try
613         {
614             prepareIndex( true );
615         }
616         catch ( ExistingLuceneIndexMismatchException e )
617         {
618             // just deleted it
619         }
620         rebuildGroups();
621         updateTimestamp( true, null );
622     }
623 
624     public synchronized void replace( Directory directory )
625         throws IOException
626     {
627         replace( directory, null, null );
628     }
629 
630     public synchronized void replace( Directory directory, Set<String> allGroups, Set<String> rootGroups )
631         throws IOException
632     {
633         final Date ts = IndexUtils.getTimestamp( directory );
634         closeReaders();
635         deleteIndexFiles( false );
636         IndexUtils.copyDirectory( directory, indexDirectory );
637         openAndWarmup();
638         // reclaim the index as mine
639         storeDescriptor();
640         if ( allGroups == null && rootGroups == null )
641         {
642             rebuildGroups();
643         }
644         else
645         {
646             if ( allGroups != null )
647             {
648                 setAllGroups( allGroups );
649             }
650             if ( rootGroups != null )
651             {
652                 setRootGroups( rootGroups );
653             }
654         }
655         updateTimestamp( true, ts );
656         optimize();
657     }
658 
659     public synchronized void merge( Directory directory )
660         throws IOException
661     {
662         merge( directory, null );
663     }
664 
665     public synchronized void merge( Directory directory, DocumentFilter filter )
666         throws IOException
667     {
668         final IndexSearcher s = acquireIndexSearcher();
669         try
670         {
671             final IndexWriter w = getIndexWriter();
672             try ( IndexReader directoryReader = DirectoryReader.open( directory ) )
673             {
674                 TopScoreDocCollector collector;
675                 int numDocs = directoryReader.maxDoc();
676 
677                 Bits liveDocs = MultiBits.getLiveDocs( directoryReader );
678                 for ( int i = 0; i < numDocs; i++ )
679                 {
680                     if ( liveDocs != null && !liveDocs.get( i ) )
681                     {
682                         continue;
683                     }
684 
685                     Document d = directoryReader.document( i );
686                     if ( filter != null && !filter.accept( d ) )
687                     {
688                         continue;
689                     }
690 
691                     String uinfo = d.get( ArtifactInfo.UINFO );
692                     if ( uinfo != null )
693                     {
694                         collector = TopScoreDocCollector.create( 1, Integer.MAX_VALUE );
695                         s.search( new TermQuery( new Term( ArtifactInfo.UINFO, uinfo ) ), collector );
696                         if ( collector.getTotalHits() == 0 )
697                         {
698                             w.addDocument( IndexUtils.updateDocument( d, this, false ) );
699                         }
700                     }
701                     else
702                     {
703                         String deleted = d.get( ArtifactInfo.DELETED );
704 
705                         if ( deleted != null )
706                         {
707                             // Deleting the document loses history that it was delete,
708                             // so incrementals wont work. Therefore, put the delete
709                             // document in as well
710                             w.deleteDocuments( new Term( ArtifactInfo.UINFO, deleted ) );
711                             w.addDocument( d );
712                         }
713                     }
714                 }
715 
716             }
717             finally
718             {
719                 commit();
720             }
721 
722             rebuildGroups();
723             Date mergedTimestamp = IndexUtils.getTimestamp( directory );
724 
725             if ( getTimestamp() != null && mergedTimestamp != null && mergedTimestamp.after( getTimestamp() ) )
726             {
727                 // we have both, keep the newest
728                 updateTimestamp( true, mergedTimestamp );
729             }
730             else
731             {
732                 updateTimestamp( true );
733             }
734             optimize();
735         }
736         finally
737         {
738             releaseIndexSearcher( s );
739         }
740     }
741 
742     private void closeReaders()
743         throws CorruptIndexException, IOException
744     {
745         if ( searcherManager != null )
746         {
747             searcherManager.close();
748             searcherManager = null;
749         }
750         if ( indexWriter != null )
751         {
752             indexWriter.close();
753             indexWriter = null;
754         }
755     }
756 
757     public GavCalculator getGavCalculator()
758     {
759         return gavCalculator;
760     }
761 
762     public List<IndexCreator> getIndexCreators()
763     {
764         return Collections.unmodifiableList( indexCreators );
765     }
766 
767     // groups
768 
769     public synchronized void rebuildGroups()
770         throws IOException
771     {
772         final IndexSearcher is = acquireIndexSearcher();
773         try
774         {
775             final IndexReader r = is.getIndexReader();
776 
777             Set<String> rootGroups = new LinkedHashSet<>();
778             Set<String> allGroups = new LinkedHashSet<>();
779 
780             int numDocs = r.maxDoc();
781             Bits liveDocs = MultiBits.getLiveDocs( r );
782 
783             for ( int i = 0; i < numDocs; i++ )
784             {
785                 if ( liveDocs != null && !liveDocs.get( i ) )
786                 {
787                     continue;
788                 }
789 
790                 Document d = r.document( i );
791 
792                 String uinfo = d.get( ArtifactInfo.UINFO );
793 
794                 if ( uinfo != null )
795                 {
796                     ArtifactInfo info = IndexUtils.constructArtifactInfo( d, this );
797                     rootGroups.add( info.getRootGroup() );
798                     allGroups.add( info.getGroupId() );
799                 }
800             }
801 
802             setRootGroups( rootGroups );
803             setAllGroups( allGroups );
804 
805             optimize();
806         }
807         finally
808         {
809             releaseIndexSearcher( is );
810         }
811     }
812 
813     public Set<String> getAllGroups()
814         throws IOException
815     {
816         return getGroups( ArtifactInfo.ALL_GROUPS, ArtifactInfo.ALL_GROUPS_VALUE, ArtifactInfo.ALL_GROUPS_LIST );
817     }
818 
819     public synchronized void setAllGroups( Collection<String> groups )
820         throws IOException
821     {
822         setGroups( groups, ArtifactInfo.ALL_GROUPS, ArtifactInfo.ALL_GROUPS_VALUE, ArtifactInfo.ALL_GROUPS_LIST );
823         commit();
824     }
825 
826     public Set<String> getRootGroups()
827         throws IOException
828     {
829         return getGroups( ArtifactInfo.ROOT_GROUPS, ArtifactInfo.ROOT_GROUPS_VALUE, ArtifactInfo.ROOT_GROUPS_LIST );
830     }
831 
832     public synchronized void setRootGroups( Collection<String> groups )
833         throws IOException
834     {
835         setGroups( groups, ArtifactInfo.ROOT_GROUPS, ArtifactInfo.ROOT_GROUPS_VALUE, ArtifactInfo.ROOT_GROUPS_LIST );
836         commit();
837     }
838 
839     protected Set<String> getGroups( String field, String filedValue, String listField )
840         throws IOException, CorruptIndexException
841     {
842         final TopScoreDocCollector collector = TopScoreDocCollector.create( 1, Integer.MAX_VALUE );
843         final IndexSearcher indexSearcher = acquireIndexSearcher();
844         try
845         {
846             indexSearcher.search( new TermQuery( new Term( field, filedValue ) ), collector );
847             TopDocs topDocs = collector.topDocs();
848             // In Lucene 7 topDocs.totalHits is now a long, but we can safely cast this to an int because
849             // indexes are still bound to at most 2 billion (Integer.MAX_VALUE) documents
850             Set<String> groups = new LinkedHashSet<String>( (int) Math.max( 10L, topDocs.totalHits.value ) );
851             if ( topDocs.totalHits.value > 0 )
852             {
853                 Document doc = indexSearcher.doc( topDocs.scoreDocs[0].doc );
854                 String groupList = doc.get( listField );
855                 if ( groupList != null )
856                 {
857                     groups.addAll( Arrays.asList( groupList.split( "\\|" ) ) );
858                 }
859             }
860             return groups;
861         }
862         finally
863         {
864             releaseIndexSearcher( indexSearcher );
865         }
866     }
867 
868     protected void setGroups( Collection<String> groups, String groupField, String groupFieldValue,
869                               String groupListField )
870         throws IOException, CorruptIndexException
871     {
872         final IndexWriter w = getIndexWriter();
873         w.updateDocument( new Term( groupField, groupFieldValue ),
874             createGroupsDocument( groups, groupField, groupFieldValue, groupListField ) );
875     }
876 
877     protected Document createGroupsDocument( Collection<String> groups, String field, String fieldValue,
878                                              String listField )
879     {
880         final Document groupDoc = new Document();
881         groupDoc.add( new Field( field, fieldValue, IndexerField.KEYWORD_STORED ) );
882         groupDoc.add( new StoredField( listField, ArtifactInfo.lst2str( groups ) ) );
883         return groupDoc;
884     }
885 
886     @Override
887     public String toString()
888     {
889         return id + " : " + timestamp;
890     }
891 
892     private static void unlockForcibly( final TrackingLockFactory lockFactory, final Directory dir )
893         throws IOException
894     {
895         //Warning: Not doable in lucene >= 5.3 consider to remove it as IndexWriter.unlock
896         //was always strongly non recommended by Lucene.
897         //For now try to do the best to simulate the IndexWriter.unlock at least on FSDirectory
898         //using FSLockFactory, the RAMDirectory uses SingleInstanceLockFactory.
899         //custom lock factory?
900         if ( lockFactory != null )
901         {
902             final Set<? extends Lock> emittedLocks = lockFactory.getEmittedLocks( IndexWriter.WRITE_LOCK_NAME );
903             for ( Lock emittedLock : emittedLocks )
904             {
905                 emittedLock.close();
906             }
907         }
908         if ( dir instanceof FSDirectory )
909         {
910             final FSDirectory fsdir = (FSDirectory) dir;
911             final Path dirPath = fsdir.getDirectory();
912             if ( Files.isDirectory( dirPath ) )
913             {
914                 Path lockPath = dirPath.resolve( IndexWriter.WRITE_LOCK_NAME );
915                 try
916                 {
917                     lockPath = lockPath.toRealPath();
918                 }
919                 catch ( IOException ioe )
920                 {
921                     // Not locked
922                     return;
923                 }
924                 try ( FileChannel fc = FileChannel.open( lockPath, StandardOpenOption.CREATE,
925                         StandardOpenOption.WRITE ) )
926                 {
927                     final FileLock lck = fc.tryLock();
928                     if ( lck == null )
929                     {
930                         // Still active
931                         throw new LockObtainFailedException( "Lock held by another process: " + lockPath );
932                     }
933                     else
934                     {
935                         // Not held fine to release
936                         lck.close();
937                     }
938                 }
939                 Files.delete( lockPath );
940             }
941         }
942     }
943 }