blob: e93ed64e49bb155ce60690544562418ff24b5ddd [file] [log] [blame]
package org.apache.maven.index;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.maven.index.context.IndexUtils;
import org.apache.maven.index.context.IndexingContext;
import org.apache.maven.index.context.NexusIndexMultiReader;
import org.apache.maven.index.context.NexusIndexMultiSearcher;
/**
* A default search engine implementation
*
* @author Eugene Kuleshov
* @author Tamas Cservenak
*/
@Singleton
@Named
public class DefaultSearchEngine
extends ComponentSupport
implements SearchEngine
{
@Deprecated
public Set<ArtifactInfo> searchFlat( Comparator<ArtifactInfo> artifactInfoComparator,
IndexingContext indexingContext, Query query )
throws IOException
{
return searchFlatPaged( new FlatSearchRequest( query, artifactInfoComparator, indexingContext ),
Arrays.asList( indexingContext ), true ).getResults();
}
@Deprecated
public Set<ArtifactInfo> searchFlat( Comparator<ArtifactInfo> artifactInfoComparator,
Collection<IndexingContext> indexingContexts, Query query )
throws IOException
{
return searchFlatPaged( new FlatSearchRequest( query, artifactInfoComparator ), indexingContexts ).getResults();
}
public FlatSearchResponse searchFlatPaged( FlatSearchRequest request, Collection<IndexingContext> indexingContexts )
throws IOException
{
return searchFlatPaged( request, indexingContexts, false );
}
public FlatSearchResponse forceSearchFlatPaged( FlatSearchRequest request,
Collection<IndexingContext> indexingContexts )
throws IOException
{
return searchFlatPaged( request, indexingContexts, true );
}
protected FlatSearchResponse searchFlatPaged( FlatSearchRequest request,
Collection<IndexingContext> indexingContexts, boolean ignoreContext )
throws IOException
{
List<IndexingContext> contexts = getParticipatingContexts( indexingContexts, ignoreContext );
final TreeSet<ArtifactInfo> result = new TreeSet<ArtifactInfo>( request.getArtifactInfoComparator() );
return new FlatSearchResponse( request.getQuery(), searchFlat( request, result, contexts, request.getQuery() ),
result );
}
// ==
public GroupedSearchResponse searchGrouped( GroupedSearchRequest request,
Collection<IndexingContext> indexingContexts )
throws IOException
{
return searchGrouped( request, indexingContexts, false );
}
public GroupedSearchResponse forceSearchGrouped( GroupedSearchRequest request,
Collection<IndexingContext> indexingContexts )
throws IOException
{
return searchGrouped( request, indexingContexts, true );
}
protected GroupedSearchResponse searchGrouped( GroupedSearchRequest request,
Collection<IndexingContext> indexingContexts, boolean ignoreContext )
throws IOException
{
List<IndexingContext> contexts = getParticipatingContexts( indexingContexts, ignoreContext );
final TreeMap<String, ArtifactInfoGroup> result =
new TreeMap<String, ArtifactInfoGroup>( request.getGroupKeyComparator() );
return new GroupedSearchResponse( request.getQuery(), searchGrouped( request, result, request.getGrouping(),
contexts, request.getQuery() ), result );
}
// ===
protected int searchFlat( FlatSearchRequest req, Collection<ArtifactInfo> result,
List<IndexingContext> participatingContexts, Query query )
throws IOException
{
int hitCount = 0;
for ( IndexingContext context : participatingContexts )
{
final IndexSearcher indexSearcher = context.acquireIndexSearcher();
try
{
final TopScoreDocCollector collector = doSearchWithCeiling( req, indexSearcher, query );
if ( collector.getTotalHits() == 0 )
{
// context has no hits, just continue to next one
continue;
}
ScoreDoc[] scoreDocs = collector.topDocs().scoreDocs;
// uhm btw hitCount contains dups
hitCount += collector.getTotalHits();
int start = 0; // from == FlatSearchRequest.UNDEFINED ? 0 : from;
// we have to pack the results as long: a) we have found aiCount ones b) we depleted hits
for ( int i = start; i < scoreDocs.length; i++ )
{
Document doc = indexSearcher.doc( scoreDocs[i].doc );
ArtifactInfo artifactInfo = IndexUtils.constructArtifactInfo( doc, context );
if ( artifactInfo != null )
{
artifactInfo.repository = context.getRepositoryId();
artifactInfo.context = context.getId();
if ( req.getArtifactInfoFilter() != null )
{
if ( !req.getArtifactInfoFilter().accepts( context, artifactInfo ) )
{
continue;
}
}
if ( req.getArtifactInfoPostprocessor() != null )
{
req.getArtifactInfoPostprocessor().postprocess( context, artifactInfo );
}
result.add( artifactInfo );
}
}
}
finally
{
context.releaseIndexSearcher( indexSearcher );
}
}
return hitCount;
}
protected int searchGrouped( GroupedSearchRequest req, Map<String, ArtifactInfoGroup> result, Grouping grouping,
List<IndexingContext> participatingContexts, Query query )
throws IOException
{
int hitCount = 0;
for ( IndexingContext context : participatingContexts )
{
final IndexSearcher indexSearcher = context.acquireIndexSearcher();
try
{
final TopScoreDocCollector collector = doSearchWithCeiling( req, indexSearcher, query );
if ( collector.getTotalHits() > 0 )
{
ScoreDoc[] scoreDocs = collector.topDocs().scoreDocs;
hitCount += collector.getTotalHits();
for ( int i = 0; i < scoreDocs.length; i++ )
{
Document doc = indexSearcher.doc( scoreDocs[i].doc );
ArtifactInfo artifactInfo = IndexUtils.constructArtifactInfo( doc, context );
if ( artifactInfo != null )
{
artifactInfo.repository = context.getRepositoryId();
artifactInfo.context = context.getId();
if ( req.getArtifactInfoFilter() != null )
{
if ( !req.getArtifactInfoFilter().accepts( context, artifactInfo ) )
{
continue;
}
}
if ( req.getArtifactInfoPostprocessor() != null )
{
req.getArtifactInfoPostprocessor().postprocess( context, artifactInfo );
}
if ( !grouping.addArtifactInfo( result, artifactInfo ) )
{
// fix the hitCount accordingly
hitCount--;
}
}
}
}
}
finally
{
context.releaseIndexSearcher( indexSearcher );
}
}
return hitCount;
}
// == NG Search
public IteratorSearchResponse searchIteratorPaged( IteratorSearchRequest request,
Collection<IndexingContext> indexingContexts )
throws IOException
{
return searchIteratorPaged( request, indexingContexts, false );
}
public IteratorSearchResponse forceSearchIteratorPaged( IteratorSearchRequest request,
Collection<IndexingContext> indexingContexts )
throws IOException
{
return searchIteratorPaged( request, indexingContexts, true );
}
private IteratorSearchResponse searchIteratorPaged( IteratorSearchRequest request,
Collection<IndexingContext> indexingContexts,
boolean ignoreContext )
throws IOException
{
List<IndexingContext> contexts = getParticipatingContexts( indexingContexts, ignoreContext );
NexusIndexMultiReader multiReader = getMergedIndexReader( indexingContexts, ignoreContext );
NexusIndexMultiSearcher indexSearcher = new NexusIndexMultiSearcher( multiReader );
TopScoreDocCollector hits = doSearchWithCeiling( request, indexSearcher, request.getQuery() );
return new IteratorSearchResponse( request.getQuery(), hits.getTotalHits(), new DefaultIteratorResultSet(
request, indexSearcher, contexts, hits.topDocs() ) );
}
// ==
protected TopScoreDocCollector doSearchWithCeiling( final AbstractSearchRequest request,
final IndexSearcher indexSearcher, final Query query )
throws IOException
{
int topHitCount = getTopDocsCollectorHitNum( request, AbstractSearchRequest.UNDEFINED );
if ( AbstractSearchRequest.UNDEFINED != topHitCount )
{
// count is set, simply just execute it as-is
final TopScoreDocCollector hits = TopScoreDocCollector.create( topHitCount, true );
indexSearcher.search( query, hits );
return hits;
}
else
{
// set something reasonable as 1k
topHitCount = 1000;
// perform search
TopScoreDocCollector hits = TopScoreDocCollector.create( topHitCount, true );
indexSearcher.search( query, hits );
// check total hits against, does it fit?
if ( topHitCount < hits.getTotalHits() )
{
topHitCount = hits.getTotalHits();
if ( getLogger().isDebugEnabled() )
{
// warn the user and leave trace just before OOM might happen
// the hits.getTotalHits() might be HUUGE
getLogger().debug(
"Executing unbounded search, and fitting topHitCounts to "
+ topHitCount
+ ", an OOMEx might follow. To avoid OOM use narrower queries or limit your expectancy with request.setCount() method where appropriate. See MINDEXER-14 for details." );
}
// redo all, but this time with correct numbers
hits = TopScoreDocCollector.create( topHitCount, true );
indexSearcher.search( query, hits );
}
return hits;
}
}
/**
* Returns the list of participating contexts. Does not locks them, just builds a list of them.
*/
protected List<IndexingContext> getParticipatingContexts( final Collection<IndexingContext> indexingContexts,
final boolean ignoreContext )
{
// to not change the API all away, but we need stable ordering here
// filter for those 1st, that take part in here
final ArrayList<IndexingContext> contexts = new ArrayList<IndexingContext>( indexingContexts.size() );
for ( IndexingContext ctx : indexingContexts )
{
if ( ignoreContext || ctx.isSearchable() )
{
contexts.add( ctx );
}
}
return contexts;
}
/**
* Locks down participating contexts, and returns a "merged" reader of them. In case of error, unlocks as part of
* cleanup and re-throws exception. Without error, it is the duty of caller to unlock contexts!
*
* @param indexingContexts
* @param ignoreContext
* @return
* @throws IOException
*/
protected NexusIndexMultiReader getMergedIndexReader( final Collection<IndexingContext> indexingContexts,
final boolean ignoreContext )
throws IOException
{
final List<IndexingContext> contexts = getParticipatingContexts( indexingContexts, ignoreContext );
return new NexusIndexMultiReader( contexts );
}
protected int getTopDocsCollectorHitNum( final AbstractSearchRequest request, final int ceiling )
{
if ( request instanceof AbstractSearchPageableRequest )
{
final AbstractSearchPageableRequest prequest = (AbstractSearchPageableRequest) request;
if ( AbstractSearchRequest.UNDEFINED != prequest.getCount() )
{
// easy, user knows and tells us how many results he want
return prequest.getCount() + prequest.getStart();
}
}
else
{
if ( AbstractSearchRequest.UNDEFINED != request.getCount() )
{
// easy, user knows and tells us how many results he want
return request.getCount();
}
}
return ceiling;
}
}