]> source.dussan.org Git - archiva.git/blob
9c6432290a4771ff9733d8252c5f52b70f95ac94
[archiva.git] /
1 package org.apache.archiva.indexer.search;
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 org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.beans.ProxyConnector;
25 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
26 import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin;
27 import org.apache.archiva.common.plexusbridge.MavenIndexerUtils;
28 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
29 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
30 import org.apache.archiva.indexer.util.SearchUtil;
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.lucene.search.BooleanClause.Occur;
33 import org.apache.lucene.search.BooleanQuery;
34 import org.apache.maven.index.ArtifactInfo;
35 import org.apache.maven.index.FlatSearchRequest;
36 import org.apache.maven.index.FlatSearchResponse;
37 import org.apache.maven.index.MAVEN;
38 import org.apache.maven.index.NexusIndexer;
39 import org.apache.maven.index.OSGI;
40 import org.apache.maven.index.context.IndexCreator;
41 import org.apache.maven.index.context.IndexingContext;
42 import org.apache.maven.index.context.UnsupportedExistingLuceneIndexException;
43 import org.apache.maven.index.expr.StringSearchExpression;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46 import org.springframework.stereotype.Service;
47
48 import javax.inject.Inject;
49 import java.io.File;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57
58 /**
59  * RepositorySearch implementation which uses the Nexus Indexer for searching.
60  */
61 @Service( "nexusSearch" )
62 public class NexusRepositorySearch
63     implements RepositorySearch
64 {
65     private Logger log = LoggerFactory.getLogger( getClass() );
66
67     private NexusIndexer indexer;
68
69     private ManagedRepositoryAdmin managedRepositoryAdmin;
70
71     private ProxyConnectorAdmin proxyConnectorAdmin;
72
73     private MavenIndexerUtils mavenIndexerUtils;
74
75     @Inject
76     public NexusRepositorySearch( PlexusSisuBridge plexusSisuBridge, ManagedRepositoryAdmin managedRepositoryAdmin,
77                                   MavenIndexerUtils mavenIndexerUtils, ProxyConnectorAdmin proxyConnectorAdmin )
78         throws PlexusSisuBridgeException
79     {
80         this.indexer = plexusSisuBridge.lookup( NexusIndexer.class );
81         this.managedRepositoryAdmin = managedRepositoryAdmin;
82         this.mavenIndexerUtils = mavenIndexerUtils;
83         this.proxyConnectorAdmin = proxyConnectorAdmin;
84     }
85
86     /**
87      * @see RepositorySearch#search(String, List, String, SearchResultLimits, List)
88      */
89     public SearchResults search( String principal, List<String> selectedRepos, String term, SearchResultLimits limits,
90                                  List<String> previousSearchTerms )
91         throws RepositorySearchException
92     {
93         List<String> indexingContextIds = addIndexingContexts( selectedRepos );
94
95         // since upgrade to nexus 2.0.0, query has changed from g:[QUERIED TERM]* to g:*[QUERIED TERM]*
96         //      resulting to more wildcard searches so we need to increase max clause count
97         BooleanQuery.setMaxClauseCount( Integer.MAX_VALUE );
98         BooleanQuery q = new BooleanQuery();
99
100         if ( previousSearchTerms == null || previousSearchTerms.isEmpty() )
101         {
102             constructQuery( term, q );
103         }
104         else
105         {
106             for ( String previousTerm : previousSearchTerms )
107             {
108                 BooleanQuery iQuery = new BooleanQuery();
109                 constructQuery( previousTerm, iQuery );
110
111                 q.add( iQuery, Occur.MUST );
112             }
113
114             BooleanQuery iQuery = new BooleanQuery();
115             constructQuery( term, iQuery );
116             q.add( iQuery, Occur.MUST );
117         }
118
119         // we retun only artifacts without classifier in quick search, olamy cannot find a way to say with this field empty
120         // FIXME  cannot find a way currently to setup this in constructQuery !!!
121         return search( limits, q, indexingContextIds, NoClassifierArtifactInfoFiler.LIST );
122
123     }
124
125     /**
126      * @see RepositorySearch#search(String, SearchFields, SearchResultLimits)
127      */
128     public SearchResults search( String principal, SearchFields searchFields, SearchResultLimits limits )
129         throws RepositorySearchException
130     {
131         if ( searchFields.getRepositories() == null )
132         {
133             throw new RepositorySearchException( "Repositories cannot be null." );
134         }
135
136         List<String> indexingContextIds = addIndexingContexts( searchFields.getRepositories() );
137
138         BooleanQuery q = new BooleanQuery();
139         if ( StringUtils.isNotBlank( searchFields.getGroupId() ) )
140         {
141             q.add( indexer.constructQuery( MAVEN.GROUP_ID, new StringSearchExpression( searchFields.getGroupId() ) ),
142                    Occur.MUST );
143         }
144
145         if ( StringUtils.isNotBlank( searchFields.getArtifactId() ) )
146         {
147             q.add(
148                 indexer.constructQuery( MAVEN.ARTIFACT_ID, new StringSearchExpression( searchFields.getArtifactId() ) ),
149                 Occur.MUST );
150         }
151
152         if ( StringUtils.isNotBlank( searchFields.getVersion() ) )
153         {
154             q.add( indexer.constructQuery( MAVEN.VERSION, new StringSearchExpression( searchFields.getVersion() ) ),
155                    Occur.MUST );
156         }
157
158         if ( StringUtils.isNotBlank( searchFields.getPackaging() ) )
159         {
160             q.add( indexer.constructQuery( MAVEN.PACKAGING, new StringSearchExpression( searchFields.getPackaging() ) ),
161                    Occur.MUST );
162         }
163
164         if ( StringUtils.isNotBlank( searchFields.getClassName() ) )
165         {
166             q.add(
167                 indexer.constructQuery( MAVEN.CLASSNAMES, new StringSearchExpression( searchFields.getClassName() ) ),
168                 Occur.MUST );
169         }
170
171         if ( StringUtils.isNotBlank( searchFields.getBundleSymbolicName() ) )
172         {
173             q.add( indexer.constructQuery( OSGI.SYMBOLIC_NAME,
174                                            new StringSearchExpression( searchFields.getBundleSymbolicName() ) ),
175                    Occur.MUST );
176         }
177
178         if ( StringUtils.isNotBlank( searchFields.getBundleVersion() ) )
179         {
180             q.add(
181                 indexer.constructQuery( OSGI.VERSION, new StringSearchExpression( searchFields.getBundleVersion() ) ),
182                 Occur.MUST );
183         }
184
185         if ( StringUtils.isNotBlank( searchFields.getBundleExportPackage() ) )
186         {
187             q.add( indexer.constructQuery( OSGI.EXPORT_PACKAGE,
188                                            new StringSearchExpression( searchFields.getBundleExportPackage() ) ),
189                    Occur.MUST );
190         }
191
192         if ( StringUtils.isNotBlank( searchFields.getBundleExportService() ) )
193         {
194             q.add( indexer.constructQuery( OSGI.EXPORT_SERVICE,
195                                            new StringSearchExpression( searchFields.getBundleExportService() ) ),
196                    Occur.MUST );
197         }
198
199         if ( StringUtils.isNotBlank( searchFields.getBundleImportPackage() ) )
200         {
201             q.add( indexer.constructQuery( OSGI.IMPORT_PACKAGE,
202                                            new StringSearchExpression( searchFields.getBundleImportPackage() ) ),
203                    Occur.MUST );
204         }
205
206         if ( StringUtils.isNotBlank( searchFields.getBundleName() ) )
207         {
208             q.add( indexer.constructQuery( OSGI.NAME, new StringSearchExpression( searchFields.getBundleName() ) ),
209                    Occur.MUST );
210         }
211
212         if ( StringUtils.isNotBlank( searchFields.getClassifier() ) )
213         {
214             q.add(
215                 indexer.constructQuery( MAVEN.CLASSIFIER, new StringSearchExpression( searchFields.getClassifier() ) ),
216                 Occur.MUST );
217         }
218
219         if ( q.getClauses() == null || q.getClauses().length <= 0 )
220         {
221             throw new RepositorySearchException( "No search fields set." );
222         }
223
224         return search( limits, q, indexingContextIds, Collections.<ArtifactInfoFiler>emptyList() );
225     }
226
227     private SearchResults search( SearchResultLimits limits, BooleanQuery q, List<String> indexingContextIds,
228                                   List<? extends ArtifactInfoFiler> filters )
229         throws RepositorySearchException
230     {
231
232         try
233         {
234             FlatSearchRequest request = new FlatSearchRequest( q );
235             request.setContexts( getIndexingContexts( indexingContextIds ) );
236             FlatSearchResponse response = indexer.searchFlat( request );
237
238             if ( response == null || response.getTotalHits() == 0 )
239             {
240                 SearchResults results = new SearchResults();
241                 results.setLimits( limits );
242                 return results;
243             }
244
245             return convertToSearchResults( response, limits, filters );
246         }
247         catch ( IOException e )
248         {
249             throw new RepositorySearchException( e );
250         }
251         /*
252         olamy : don't understand why this ?? it remove content from index ??
253         comment until someone explain WTF ?? :-))
254         finally
255         {
256             Map<String, IndexingContext> indexingContexts = indexer.getIndexingContexts();
257
258             for ( Map.Entry<String, IndexingContext> entry : indexingContexts.entrySet() )
259             {
260                 try
261                 {
262                     indexer.removeIndexingContext( entry.getValue(), false );
263                     log.debug( "Indexing context '{}' removed from search.", entry.getKey() );
264                 }
265                 catch ( IOException e )
266                 {
267                     log.warn( "IOException occurred while removing indexing content '" + entry.getKey() + "'." );
268                     continue;
269                 }
270             }
271         }*/
272     }
273
274     private List<IndexingContext> getIndexingContexts( List<String> ids )
275     {
276         List<IndexingContext> contexts = new ArrayList<IndexingContext>( ids.size() );
277
278         for ( String id : ids )
279         {
280             IndexingContext context = indexer.getIndexingContexts().get( id );
281             if ( context != null )
282             {
283                 contexts.add( context );
284             }
285             else
286             {
287                 log.warn( "context with id {} not exists", id );
288             }
289         }
290
291         return contexts;
292     }
293
294     private void constructQuery( String term, BooleanQuery q )
295     {
296         q.add( indexer.constructQuery( MAVEN.GROUP_ID, new StringSearchExpression( term ) ), Occur.SHOULD );
297         q.add( indexer.constructQuery( MAVEN.ARTIFACT_ID, new StringSearchExpression( term ) ), Occur.SHOULD );
298         q.add( indexer.constructQuery( MAVEN.VERSION, new StringSearchExpression( term ) ), Occur.SHOULD );
299         q.add( indexer.constructQuery( MAVEN.PACKAGING, new StringSearchExpression( term ) ), Occur.SHOULD );
300         q.add( indexer.constructQuery( MAVEN.CLASSNAMES, new StringSearchExpression( term ) ), Occur.SHOULD );
301
302         //Query query =
303         //    new WildcardQuery( new Term( MAVEN.CLASSNAMES.getFieldName(), "*" ) );
304         //q.add( query, Occur.MUST_NOT );
305         // olamy IMHO we could set this option as at least one must match
306         //q.setMinimumNumberShouldMatch( 1 );
307     }
308
309
310     /**
311      * @param selectedRepos
312      * @return indexing contextId used
313      */
314     private List<String> addIndexingContexts( List<String> selectedRepos )
315     {
316         Set<String> indexingContextIds = new HashSet<String>();
317         for ( String repo : selectedRepos )
318         {
319             try
320             {
321                 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repo );
322
323                 if ( repoConfig != null )
324                 {
325                     String indexDir = repoConfig.getIndexDirectory();
326                     File indexDirectory = null;
327                     if ( indexDir != null && !"".equals( indexDir ) )
328                     {
329                         indexDirectory = new File( repoConfig.getIndexDirectory() );
330                     }
331                     else
332                     {
333                         indexDirectory = new File( repoConfig.getLocation(), ".indexer" );
334                     }
335
336                     IndexingContext context = indexer.getIndexingContexts().get( repoConfig.getId() );
337                     if ( context != null )
338                     {
339                         // alreday here so no need to record it again
340                         log.debug( "index with id {} already exists skip adding it", repoConfig.getId() );
341                         // set searchable flag
342                         context.setSearchable( repoConfig.isScanned() );
343                         indexingContextIds.add( context.getId() );
344                         indexingContextIds.addAll( getRemoteIndexingContextIds( repo ) );
345                         continue;
346                     }
347
348                     context = indexer.addIndexingContext( repoConfig.getId(), repoConfig.getId(),
349                                                           new File( repoConfig.getLocation() ), indexDirectory, null,
350                                                           null, getAllIndexCreators() );
351                     context.setSearchable( repoConfig.isScanned() );
352                     if ( context.isSearchable() )
353                     {
354                         indexingContextIds.addAll( getRemoteIndexingContextIds( repo ) );
355                         indexingContextIds.add( context.getId() );
356                     }
357                     else
358                     {
359                         log.warn( "indexingContext with id {} not searchable", repoConfig.getId() );
360                     }
361
362                 }
363                 else
364                 {
365                     log.warn( "Repository '" + repo + "' not found in configuration." );
366                 }
367             }
368             catch ( UnsupportedExistingLuceneIndexException e )
369             {
370                 log.warn( "Error accessing index of repository '" + repo + "' : " + e.getMessage() );
371                 continue;
372             }
373             catch ( IOException e )
374             {
375                 log.warn( "IO error occured while accessing index of repository '" + repo + "' : " + e.getMessage() );
376                 continue;
377             }
378             catch ( RepositoryAdminException e )
379             {
380                 log.warn( "RepositoryAdminException occured while accessing index of repository '" + repo + "' : "
381                               + e.getMessage() );
382                 continue;
383             }
384         }
385
386         return new ArrayList<String>( indexingContextIds );
387     }
388
389
390     private Set<String> getRemoteIndexingContextIds( String managedRepoId )
391         throws RepositoryAdminException
392     {
393         Set<String> ids = new HashSet<String>();
394
395         List<ProxyConnector> proxyConnectors = proxyConnectorAdmin.getProxyConnectorAsMap().get( managedRepoId );
396
397         if ( proxyConnectors == null || proxyConnectors.isEmpty() )
398         {
399             return ids;
400         }
401
402         for ( ProxyConnector proxyConnector : proxyConnectors )
403         {
404             String remoteId = "remote-" + proxyConnector.getTargetRepoId();
405             IndexingContext context = indexer.getIndexingContexts().get( remoteId );
406             if ( context != null && context.isSearchable() )
407             {
408                 ids.add( remoteId );
409             }
410         }
411
412         return ids;
413     }
414
415
416     protected List<? extends IndexCreator> getAllIndexCreators()
417     {
418         return mavenIndexerUtils.getAllIndexCreators();
419     }
420
421
422     private SearchResults convertToSearchResults( FlatSearchResponse response, SearchResultLimits limits,
423                                                   List<? extends ArtifactInfoFiler> artifactInfoFilers )
424     {
425         SearchResults results = new SearchResults();
426         Set<ArtifactInfo> artifactInfos = response.getResults();
427
428         for ( ArtifactInfo artifactInfo : artifactInfos )
429         {
430             String id = SearchUtil.getHitId( artifactInfo.groupId, artifactInfo.artifactId, artifactInfo.classifier,
431                                              artifactInfo.packaging );
432             Map<String, SearchResultHit> hitsMap = results.getHitsMap();
433
434             if ( !applyArtifactInfoFilters( artifactInfo, artifactInfoFilers, hitsMap ) )
435             {
436                 continue;
437             }
438
439             SearchResultHit hit = hitsMap.get( id );
440             if ( hit != null )
441             {
442                 if ( !hit.getVersions().contains( artifactInfo.version ) )
443                 {
444                     hit.addVersion( artifactInfo.version );
445                 }
446             }
447             else
448             {
449                 hit = new SearchResultHit();
450                 hit.setArtifactId( artifactInfo.artifactId );
451                 hit.setGroupId( artifactInfo.groupId );
452                 hit.setRepositoryId( artifactInfo.repository );
453                 hit.addVersion( artifactInfo.version );
454                 hit.setBundleExportPackage( artifactInfo.bundleExportPackage );
455                 hit.setBundleExportService( artifactInfo.bundleExportService );
456                 hit.setBundleSymbolicName( artifactInfo.bundleSymbolicName );
457                 hit.setBundleVersion( artifactInfo.bundleVersion );
458                 hit.setBundleDescription( artifactInfo.bundleDescription );
459                 hit.setBundleDocUrl( artifactInfo.bundleDocUrl );
460                 hit.setBundleRequireBundle( artifactInfo.bundleRequireBundle );
461                 hit.setBundleImportPackage( artifactInfo.bundleImportPackage );
462                 hit.setBundleLicense( artifactInfo.bundleLicense );
463                 hit.setBundleName( artifactInfo.bundleName );
464                 hit.setContext( artifactInfo.context );
465                 hit.setGoals( artifactInfo.goals );
466                 hit.setPrefix( artifactInfo.prefix );
467                 hit.setPackaging( artifactInfo.packaging );
468                 hit.setClassifier( artifactInfo.classifier );
469                 hit.setUrl( artifactInfo.remoteUrl );
470             }
471
472             results.addHit( id, hit );
473         }
474
475         results.setTotalHits( response.getTotalHitsCount() );
476         results.setReturnedHitsCount( response.getReturnedHitsCount() );
477         results.setLimits( limits );
478
479         if ( limits == null || limits.getSelectedPage() == SearchResultLimits.ALL_PAGES )
480         {
481             return results;
482         }
483         else
484         {
485             return paginate( results );
486         }
487     }
488
489     private boolean applyArtifactInfoFilters( ArtifactInfo artifactInfo,
490                                               List<? extends ArtifactInfoFiler> artifactInfoFilers,
491                                               Map<String, SearchResultHit> currentResult )
492     {
493         if ( artifactInfoFilers == null || artifactInfoFilers.isEmpty() )
494         {
495             return true;
496         }
497
498         for ( ArtifactInfoFiler filter : artifactInfoFilers )
499         {
500             if ( !filter.addArtifactInResult( artifactInfo, currentResult ) )
501             {
502                 return false;
503             }
504         }
505         return true;
506     }
507
508     private SearchResults paginate( SearchResults results )
509     {
510         SearchResultLimits limits = results.getLimits();
511         SearchResults paginated = new SearchResults();
512
513         int fetchCount = limits.getPageSize();
514         int offset = ( limits.getSelectedPage() * limits.getPageSize() );
515
516         if ( fetchCount > results.getTotalHits() )
517         {
518             fetchCount = results.getTotalHits();
519         }
520
521         // Goto offset.
522         if ( offset < results.getTotalHits() )
523         {
524             // only process if the offset is within the hit count.
525             for ( int i = 0; i < fetchCount; i++ )
526             {
527                 // Stop fetching if we are past the total # of available hits.
528                 if ( offset + i >= results.getHits().size() )
529                 {
530                     break;
531                 }
532
533                 SearchResultHit hit = results.getHits().get( ( offset + i ) );
534                 if ( hit != null )
535                 {
536                     String id = SearchUtil.getHitId( hit.getGroupId(), hit.getArtifactId(), hit.getClassifier(),
537                                                      hit.getPackaging() );
538                     paginated.addHit( id, hit );
539                 }
540                 else
541                 {
542                     break;
543                 }
544             }
545         }
546         paginated.setTotalHits( results.getTotalHits() );
547         paginated.setReturnedHitsCount( paginated.getHits().size() );
548         paginated.setLimits( limits );
549
550         return paginated;
551     }
552 }