/*
* Copyright 2015 JBoss, by Red Hat, Inc
*
* Licensed 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.
*/
package org.uberfire.ext.metadata.backend.lucene.search;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.search.WildcardQuery;
import org.uberfire.ext.metadata.backend.lucene.index.LuceneIndexManager;
import org.uberfire.ext.metadata.model.KObject;
import org.uberfire.ext.metadata.search.ClusterSegment;
import org.uberfire.ext.metadata.search.DateRange;
import org.uberfire.ext.metadata.search.IOSearchService;
import org.uberfire.ext.metadata.search.SearchIndex;
import static java.util.Collections.emptyList;
import static org.apache.lucene.search.BooleanClause.Occur.MUST;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import static org.apache.lucene.search.NumericRangeQuery.newLongRange;
import static org.uberfire.commons.validation.PortablePreconditions.checkNotNull;
import static org.uberfire.ext.metadata.backend.lucene.util.KObjectUtil.toKObject;
import static org.uberfire.ext.metadata.engine.MetaIndexEngine.FULL_TEXT_FIELD;
/**
*
*/
public class LuceneSearchIndex implements SearchIndex {
private final LuceneIndexManager indexManager;
private final QueryParser queryParser;
public LuceneSearchIndex(final LuceneIndexManager indexManager,
final Analyzer analyzer) {
this.indexManager = checkNotNull("lucene",
indexManager);
this.queryParser = new QueryParser(FULL_TEXT_FIELD,
analyzer);
this.queryParser.setAllowLeadingWildcard(true);
}
@Override
public List<KObject> searchByAttrs(final Map<String, ?> attrs,
final IOSearchService.Filter filter,
final ClusterSegment... clusterSegments) {
if (clusterSegments == null || clusterSegments.length == 0) {
return emptyList();
}
if (attrs == null || attrs.size() == 0) {
return emptyList();
}
final int totalNumHitsEstimate = searchByAttrsHits(attrs,
clusterSegments);
return search(buildQuery(attrs,
clusterSegments),
totalNumHitsEstimate,
filter,
clusterSegments);
}
@Override
public List<KObject> fullTextSearch(final String term,
final IOSearchService.Filter filter,
final ClusterSegment... clusterSegments) {
if (clusterSegments == null || clusterSegments.length == 0) {
return emptyList();
}
final int totalNumHitsEstimate = fullTextSearchHits(term,
clusterSegments);
return search(buildQuery(term,
clusterSegments),
totalNumHitsEstimate,
filter,
clusterSegments);
}
@Override
public int searchByAttrsHits(final Map<String, ?> attrs,
final ClusterSegment... clusterSegments) {
if (clusterSegments == null || clusterSegments.length == 0) {
return 0;
}
if (attrs == null || attrs.size() == 0) {
return 0;
}
return searchHits(buildQuery(attrs,
clusterSegments),
clusterSegments);
}
@Override
public int fullTextSearchHits(final String term,
final ClusterSegment... clusterSegments) {
if (clusterSegments == null || clusterSegments.length == 0) {
return 0;
}
return searchHits(buildQuery(term,
clusterSegments),
clusterSegments);
}
private int searchHits(final Query query,
final ClusterSegment... clusterSegments) {
final IndexSearcher index = indexManager.getIndexSearcher(clusterSegments);
try {
final TotalHitCountCollector collector = new TotalHitCountCollector();
index.search(query,
collector);
return collector.getTotalHits();
} catch (final Exception ex) {
throw new RuntimeException("Error during Query!",
ex);
} finally {
indexManager.release(index);
}
}
private List<KObject> search(final Query query,
final int totalNumHitsEstimate,
final IOSearchService.Filter filter,
final ClusterSegment... clusterSegments) {
final TopScoreDocCollector collector = TopScoreDocCollector.create(totalNumHitsEstimate);
final IndexSearcher index = indexManager.getIndexSearcher(clusterSegments);
final List<KObject> result = new ArrayList<KObject>();
try {
index.search(query,
collector);
final ScoreDoc[] hits = collector.topDocs(0).scoreDocs;
for (int i = 0; i < hits.length; i++) {
final KObject kObject = toKObject(index.doc(hits[i].doc));
if (filter.accept(kObject)) {
result.add(kObject);
}
}
} catch (final Exception ex) {
throw new RuntimeException("Error during Query!",
ex);
} finally {
indexManager.release(index);
}
return result;
}
private Query buildQuery(final Map<String, ?> attrs,
final ClusterSegment... clusterSegments) {
final BooleanQuery query = new BooleanQuery();
for (final Map.Entry<String, ?> entry : attrs.entrySet()) {
if (entry.getValue() instanceof DateRange) {
final Long from = ((DateRange) entry.getValue()).after().getTime();
final Long to = ((DateRange) entry.getValue()).before().getTime();
query.add(newLongRange(entry.getKey(),
from,
to,
true,
true),
MUST);
} else if (entry.getValue() instanceof String) {
query.add(new WildcardQuery(new Term(entry.getKey(),
entry.getValue().toString())),
MUST);
} else if (entry.getValue() instanceof Boolean) {
query.add(new TermQuery(new Term(entry.getKey(),
((Boolean) entry.getValue()) ? "0" : "1")),
MUST);
}
}
return composeQuery(query,
clusterSegments);
}
private Query buildQuery(final String term,
final ClusterSegment... clusterSegments) {
Query fullText;
try {
fullText = queryParser.parse(term);
if (fullText.toString().isEmpty()) {
fullText = new WildcardQuery(new Term(FULL_TEXT_FIELD,
format(term) + "*"));
}
} catch (ParseException ex) {
fullText = new WildcardQuery(new Term(FULL_TEXT_FIELD,
format(term)));
}
return composeQuery(fullText,
clusterSegments);
}
private Query composeQuery(final Query query,
final ClusterSegment... clusterSegments) {
if (clusterSegments == null || clusterSegments.length == 0) {
return query;
}
final BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query,
MUST);
final BooleanClause.Occur occur = (clusterSegments.length == 1 ? MUST : SHOULD);
for (ClusterSegment clusterSegment : clusterSegments) {
final BooleanQuery clusterSegmentQuery = new BooleanQuery();
addClusterIdTerms(clusterSegmentQuery,
clusterSegment);
addSegmentIdTerms(clusterSegmentQuery,
clusterSegment);
booleanQuery.add(clusterSegmentQuery,
occur);
}
return booleanQuery;
}
private void addClusterIdTerms(final BooleanQuery query,
final ClusterSegment clusterSegment) {
if (clusterSegment.getClusterId() != null) {
final Query cluster = new TermQuery(new Term("cluster.id",
clusterSegment.getClusterId()));
query.add(cluster,
MUST);
}
}
private void addSegmentIdTerms(final BooleanQuery query,
final ClusterSegment clusterSegment) {
if (clusterSegment.segmentIds() == null || clusterSegment.segmentIds().length == 0) {
return;
}
if (clusterSegment.segmentIds().length == 1) {
final Query segment = new TermQuery(new Term("segment.id",
clusterSegment.segmentIds()[0]));
query.add(segment,
MUST);
} else {
final BooleanQuery segments = new BooleanQuery();
for (final String segmentId : clusterSegment.segmentIds()) {
final Query segment = new TermQuery(new Term("segment.id",
segmentId));
segments.add(segment,
SHOULD);
}
query.add(segments,
MUST);
}
}
private String format(final String term) {
return term.toLowerCase();
}
}