View Javadoc

1   package org.apache.maven.index;
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.FileInputStream;
24  import java.io.IOException;
25  import java.security.MessageDigest;
26  import java.security.NoSuchAlgorithmException;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.concurrent.ConcurrentHashMap;
33  
34  import org.apache.lucene.queryParser.ParseException;
35  import org.apache.lucene.search.Query;
36  import org.apache.lucene.store.Directory;
37  import org.apache.lucene.store.FSDirectory;
38  import org.apache.maven.index.context.ContextMemberProvider;
39  import org.apache.maven.index.context.DefaultIndexingContext;
40  import org.apache.maven.index.context.IndexCreator;
41  import org.apache.maven.index.context.IndexUtils;
42  import org.apache.maven.index.context.IndexingContext;
43  import org.apache.maven.index.context.MergedIndexingContext;
44  import org.apache.maven.index.context.StaticContextMemberProvider;
45  import org.apache.maven.index.context.UnsupportedExistingLuceneIndexException;
46  import org.apache.maven.index.expr.SearchExpression;
47  import org.apache.maven.index.util.IndexCreatorSorter;
48  import org.codehaus.plexus.component.annotations.Component;
49  import org.codehaus.plexus.component.annotations.Requirement;
50  import org.codehaus.plexus.logging.AbstractLogEnabled;
51  import org.codehaus.plexus.util.FileUtils;
52  import org.codehaus.plexus.util.IOUtil;
53  
54  /**
55   * A default {@link NexusIndexer} implementation.
56   * 
57   * @author Tamas Cservenak
58   * @author Eugene Kuleshov
59   */
60  @Component( role = NexusIndexer.class )
61  public class DefaultNexusIndexer
62      extends AbstractLogEnabled
63      implements NexusIndexer
64  {
65      @Requirement
66      private Scanner scanner;
67  
68      @Requirement
69      private SearchEngine searcher;
70  
71      @Requirement
72      private IndexerEngine indexerEngine;
73  
74      @Requirement
75      private QueryCreator queryCreator;
76  
77      private Map<String, IndexingContext> indexingContexts;
78  
79      public DefaultNexusIndexer()
80      {
81          this.indexingContexts = new ConcurrentHashMap<String, IndexingContext>();
82      }
83  
84      // ----------------------------------------------------------------------------
85      // Contexts
86      // ----------------------------------------------------------------------------
87  
88      public IndexingContext addIndexingContext( String id, String repositoryId, File repository, File indexDirectory,
89                                                 String repositoryUrl, String indexUpdateUrl,
90                                                 List<? extends IndexCreator> indexers )
91          throws IOException, UnsupportedExistingLuceneIndexException
92      {
93          IndexingContext context =
94              new DefaultIndexingContext( id, repositoryId, repository, indexDirectory, repositoryUrl, indexUpdateUrl,
95                  IndexCreatorSorter.sort( indexers ), false );
96  
97          indexingContexts.put( context.getId(), context );
98  
99          return context;
100     }
101 
102     public IndexingContext addIndexingContextForced( String id, String repositoryId, File repository,
103                                                      File indexDirectory, String repositoryUrl, String indexUpdateUrl,
104                                                      List<? extends IndexCreator> indexers )
105         throws IOException
106     {
107         IndexingContext context = null;
108 
109         try
110         {
111             context =
112                 new DefaultIndexingContext( id, repositoryId, repository, indexDirectory, repositoryUrl,
113                     indexUpdateUrl, IndexCreatorSorter.sort( indexers ), true );
114 
115             indexingContexts.put( context.getId(), context );
116         }
117         catch ( UnsupportedExistingLuceneIndexException e )
118         {
119             // will not be thrown
120         }
121 
122         return context;
123     }
124 
125     public IndexingContext addIndexingContext( String id, String repositoryId, File repository, Directory directory,
126                                                String repositoryUrl, String indexUpdateUrl,
127                                                List<? extends IndexCreator> indexers )
128         throws IOException, UnsupportedExistingLuceneIndexException
129     {
130         IndexingContext context =
131             new DefaultIndexingContext( id, repositoryId, repository, directory, repositoryUrl, indexUpdateUrl,
132                 IndexCreatorSorter.sort( indexers ), false );
133 
134         indexingContexts.put( context.getId(), context );
135 
136         return context;
137     }
138 
139     public IndexingContext addIndexingContextForced( String id, String repositoryId, File repository,
140                                                      Directory directory, String repositoryUrl, String indexUpdateUrl,
141                                                      List<? extends IndexCreator> indexers )
142         throws IOException
143     {
144         IndexingContext context = null;
145 
146         try
147         {
148             context =
149                 new DefaultIndexingContext( id, repositoryId, repository, directory, repositoryUrl, indexUpdateUrl,
150                     IndexCreatorSorter.sort( indexers ), true );
151 
152             indexingContexts.put( context.getId(), context );
153         }
154         catch ( UnsupportedExistingLuceneIndexException e )
155         {
156             // will not be thrown
157         }
158 
159         return context;
160     }
161 
162     public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
163                                                      File indexDirectory, boolean searchable,
164                                                      Collection<IndexingContext> contexts )
165         throws IOException
166     {
167         IndexingContext context =
168             new MergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable,
169                 new StaticContextMemberProvider( contexts ) );
170 
171         indexingContexts.put( context.getId(), context );
172 
173         return context;
174     }
175 
176     public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
177                                                      File indexDirectory, boolean searchable,
178                                                      ContextMemberProvider membersProvider )
179         throws IOException
180     {
181         IndexingContext context =
182             new MergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable, membersProvider );
183 
184         indexingContexts.put( context.getId(), context );
185 
186         return context;
187     }
188 
189     public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
190                                                      Directory indexDirectory, boolean searchable,
191                                                      Collection<IndexingContext> contexts )
192         throws IOException
193     {
194         IndexingContext context =
195             new MergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable,
196                 new StaticContextMemberProvider( contexts ) );
197 
198         indexingContexts.put( context.getId(), context );
199 
200         return context;
201     }
202 
203     public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
204                                                      Directory indexDirectory, boolean searchable,
205                                                      ContextMemberProvider membersProvider )
206         throws IOException
207     {
208         IndexingContext context =
209             new MergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable, membersProvider );
210 
211         indexingContexts.put( context.getId(), context );
212 
213         return context;
214     }
215 
216     public void removeIndexingContext( IndexingContext context, boolean deleteFiles )
217         throws IOException
218     {
219         if ( indexingContexts.containsKey( context.getId() ) )
220         {
221             indexingContexts.remove( context.getId() );
222             context.close( deleteFiles );
223         }
224     }
225 
226     public Map<String, IndexingContext> getIndexingContexts()
227     {
228         return Collections.unmodifiableMap( indexingContexts );
229     }
230 
231     // ----------------------------------------------------------------------------
232     // Scanning
233     // ----------------------------------------------------------------------------
234 
235     public void scan( final IndexingContext context )
236         throws IOException
237     {
238         scan( context, null );
239     }
240 
241     public void scan( final IndexingContext context, boolean update )
242         throws IOException
243     {
244         scan( context, null, update );
245     }
246 
247     public void scan( final IndexingContext context, final ArtifactScanningListener listener )
248         throws IOException
249     {
250         scan( context, listener, false );
251     }
252 
253     public void scan( final IndexingContext context, final ArtifactScanningListener listener, final boolean update )
254         throws IOException
255     {
256         scan( context, null, listener, update );
257     }
258 
259     /**
260      * Uses {@link Scanner} to scan repository content. A {@link ArtifactScanningListener} is used to process found
261      * artifacts and to add them to the index using
262      * {@link NexusIndexer#artifactDiscovered(ArtifactContext, IndexingContext)}.
263      * 
264      * @see DefaultScannerListener
265      * @see #artifactDiscovered(ArtifactContext, IndexingContext)
266      */
267     public void scan( final IndexingContext context, final String fromPath, final ArtifactScanningListener listener,
268                       final boolean update )
269         throws IOException
270     {
271         File repositoryDirectory = context.getRepository();
272 
273         if ( repositoryDirectory == null )
274         {
275             // nothing to scan
276             return;
277         }
278 
279         if ( !repositoryDirectory.exists() )
280         {
281             throw new IOException( "Repository directory " + repositoryDirectory + " does not exist" );
282         }
283 
284         // always use temporary context when reindexing
285         File indexDir = context.getIndexDirectoryFile();
286         File dir = null;
287         if ( indexDir != null )
288         {
289             dir = indexDir.getParentFile();
290         }
291 
292         File tmpFile = File.createTempFile( context.getId() + "-tmp", "" );
293         File tmpDir = new File( tmpFile.getParentFile(), tmpFile.getName() + ".dir" );
294         if ( !tmpDir.mkdirs() )
295         {
296             throw new IOException( "Cannot create temporary directory: " + tmpDir );
297         }
298 
299         IndexingContext tmpContext = null;
300         try
301         {
302             FSDirectory directory = FSDirectory.open( tmpDir );
303 
304             if ( update )
305             {
306                 IndexUtils.copyDirectory( context.getIndexDirectory(), directory );
307             }
308 
309             tmpContext = new DefaultIndexingContext( context.getId() + "-tmp", //
310                 context.getRepositoryId(), //
311                 context.getRepository(), //
312                 directory, //
313                 context.getRepositoryUrl(), //
314                 context.getIndexUpdateUrl(), //
315                 context.getIndexCreators(), //
316                 true );
317 
318             scanner.scan( new ScanningRequest( tmpContext, //
319                 new DefaultScannerListener( tmpContext, indexerEngine, update, listener ), fromPath ) );
320 
321             tmpContext.updateTimestamp( true );
322             context.replace( tmpContext.getIndexDirectory() );
323 
324             removeIndexingContext( tmpContext, true );
325         }
326         catch ( Exception ex )
327         {
328             throw (IOException) new IOException( "Error scanning context " + context.getId() + ": " + ex ).initCause( ex );
329         }
330         finally
331         {
332             if ( tmpContext != null )
333             {
334                 tmpContext.close( true );
335             }
336 
337             if ( tmpFile.exists() )
338             {
339                 tmpFile.delete();
340             }
341 
342             FileUtils.deleteDirectory( tmpDir );
343         }
344 
345     }
346 
347     /**
348      * Delegates to the {@link IndexerEngine} to add a new artifact to the index
349      */
350     public void artifactDiscovered( ArtifactContext ac, IndexingContext context )
351         throws IOException
352     {
353         if ( ac != null )
354         {
355             indexerEngine.index( context, ac );
356         }
357     }
358 
359     // ----------------------------------------------------------------------------
360     // Modifying
361     // ----------------------------------------------------------------------------
362 
363     /**
364      * Delegates to the {@link IndexerEngine} to update artifact to the index
365      */
366     public void addArtifactToIndex( ArtifactContext ac, IndexingContext context )
367         throws IOException
368     {
369         if ( ac != null )
370         {
371             indexerEngine.update( context, ac );
372 
373             context.commit();
374         }
375     }
376 
377     public void addArtifactsToIndex( Collection<ArtifactContext> ac, IndexingContext context )
378         throws IOException
379     {
380         if ( ac != null && !ac.isEmpty() )
381         {
382             for ( ArtifactContext actx : ac )
383             {
384                 indexerEngine.update( context, actx );
385             }
386 
387             context.commit();
388         }
389     }
390 
391     /**
392      * Delegates to the {@link IndexerEngine} to remove artifact from the index
393      */
394     public void deleteArtifactFromIndex( ArtifactContext ac, IndexingContext context )
395         throws IOException
396     {
397         if ( ac != null )
398         {
399             indexerEngine.remove( context, ac );
400 
401             context.commit();
402         }
403     }
404 
405     public void deleteArtifactsFromIndex( Collection<ArtifactContext> ac, IndexingContext context )
406         throws IOException
407     {
408         if ( ac != null && !ac.isEmpty() )
409         {
410             for ( ArtifactContext actx : ac )
411             {
412                 indexerEngine.remove( context, actx );
413 
414                 context.commit();
415             }
416         }
417     }
418 
419     // ----------------------------------------------------------------------------
420     // Searching
421     // ----------------------------------------------------------------------------
422 
423     public FlatSearchResponse searchFlat( FlatSearchRequest request )
424         throws IOException
425     {
426         if ( request.getContexts().isEmpty() )
427         {
428             return searcher.searchFlatPaged( request, indexingContexts.values() );
429         }
430         else
431         {
432             return searcher.forceSearchFlatPaged( request, request.getContexts() );
433         }
434     }
435 
436     public IteratorSearchResponse searchIterator( IteratorSearchRequest request )
437         throws IOException
438     {
439         if ( request.getContexts().isEmpty() )
440         {
441             return searcher.searchIteratorPaged( request, indexingContexts.values() );
442         }
443         else
444         {
445             return searcher.forceSearchIteratorPaged( request, request.getContexts() );
446         }
447     }
448 
449     public GroupedSearchResponse searchGrouped( GroupedSearchRequest request )
450         throws IOException
451     {
452         if ( request.getContexts().isEmpty() )
453         {
454             // search all
455             return searcher.searchGrouped( request, indexingContexts.values() );
456         }
457         else
458         {
459             // search targeted
460             return searcher.forceSearchGrouped( request, request.getContexts() );
461         }
462     }
463 
464     // ----------------------------------------------------------------------------
465     // Query construction
466     // ----------------------------------------------------------------------------
467 
468     @Deprecated
469     public Query constructQuery( Field field, String query, SearchType type )
470         throws IllegalArgumentException
471     {
472         try
473         {
474             return queryCreator.constructQuery( field, query, type );
475         }
476         catch ( ParseException e )
477         {
478             throw new IllegalArgumentException( e );
479         }
480     }
481 
482     public Query constructQuery( Field field, SearchExpression expression )
483         throws IllegalArgumentException
484     {
485         try
486         {
487             return queryCreator.constructQuery( field, expression );
488         }
489         catch ( ParseException e )
490         {
491             throw new IllegalArgumentException( e );
492         }
493     }
494 
495     // ----------------------------------------------------------------------------
496     // Identification
497     // ----------------------------------------------------------------------------
498 
499     public Collection<ArtifactInfo> identify( Field field, String query )
500         throws IllegalArgumentException, IOException
501     {
502         return identify( constructQuery( field, query, SearchType.EXACT ) );
503     }
504 
505     public Collection<ArtifactInfo> identify( File artifact )
506         throws IOException
507     {
508         return identify( artifact, indexingContexts.values() );
509     }
510 
511     public Collection<ArtifactInfo> identify( File artifact, Collection<IndexingContext> contexts )
512         throws IOException
513     {
514         FileInputStream is = null;
515 
516         try
517         {
518             MessageDigest sha1 = MessageDigest.getInstance( "SHA-1" );
519 
520             is = new FileInputStream( artifact );
521 
522             byte[] buff = new byte[4096];
523 
524             int n;
525 
526             while ( ( n = is.read( buff ) ) > -1 )
527             {
528                 sha1.update( buff, 0, n );
529             }
530 
531             byte[] digest = sha1.digest();
532 
533             Query q = constructQuery( MAVEN.SHA1, encode( digest ), SearchType.EXACT );
534 
535             return identify( q, contexts );
536         }
537         catch ( NoSuchAlgorithmException ex )
538         {
539             IOException ioe = new IOException( "Unable to calculate digest" );
540             ioe.initCause( ex );
541             throw ioe;
542         }
543         finally
544         {
545             IOUtil.close( is );
546         }
547     }
548 
549     public Collection<ArtifactInfo> identify( Query query )
550         throws IOException
551     {
552         return identify( query, indexingContexts.values() );
553     }
554 
555     public Collection<ArtifactInfo> identify( Query query, Collection<IndexingContext> contexts )
556         throws IOException
557     {
558         IteratorSearchResponse result = searcher.searchIteratorPaged( new IteratorSearchRequest( query ), contexts );
559 
560         try
561         {
562             ArrayList<ArtifactInfo> ais = new ArrayList<ArtifactInfo>( result.getTotalHitsCount() );
563 
564             for ( ArtifactInfo ai : result )
565             {
566                 ais.add( ai );
567             }
568 
569             return ais;
570         }
571         finally
572         {
573             result.close();
574         }
575     }
576 
577     // ==
578 
579     private static final char[] DIGITS = "0123456789abcdef".toCharArray();
580 
581     private static String encode( byte[] digest )
582     {
583         char[] buff = new char[digest.length * 2];
584 
585         int n = 0;
586 
587         for ( byte b : digest )
588         {
589             buff[n++] = DIGITS[( 0xF0 & b ) >> 4];
590             buff[n++] = DIGITS[0x0F & b];
591         }
592 
593         return new String( buff );
594     }
595 }