package org.apache.blur.lucene.search; /** * 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.Arrays; import org.apache.blur.log.Log; import org.apache.blur.log.LogFactory; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Weight; import org.apache.lucene.util.Bits; import org.apache.lucene.util.OpenBitSet; public class FacetQuery extends AbstractWrapperQuery { private static final Log LOG = LogFactory.getLog(FacetQuery.class); private final Query[] _facets; private final FacetExecutor _executor; public FacetQuery(Query query, Query[] facets, FacetExecutor executor) { super(query, false); _facets = facets; _executor = executor; } public FacetQuery(Query query, Query[] facets, FacetExecutor executor, boolean rewritten) { super(query, rewritten); _facets = facets; _executor = executor; } public String toString() { return "facet:{" + _query.toString() + "}"; } public String toString(String field) { return "facet:{" + _query.toString(field) + "}"; } public Query[] getFacets() { return _facets; } @Override public Query clone() { return new FacetQuery((Query) _query.clone(), _facets, _executor, _rewritten); } @Override public Query rewrite(IndexReader reader) throws IOException { if (_rewritten) { return this; } Query[] facets = new Query[_facets.length]; for (int i = 0; i < _facets.length; i++) { facets[i] = _facets[i].rewrite(reader); } return new FacetQuery(_query.rewrite(reader), facets, _executor, true); } @Override public Weight createWeight(IndexSearcher searcher) throws IOException { Weight weight = _query.createWeight(searcher); return new FacetWeight(weight, getWeights(searcher), _executor); } private Weight[] getWeights(IndexSearcher searcher) throws IOException { Weight[] weights = new Weight[_facets.length]; for (int i = 0; i < weights.length; i++) { weights[i] = _facets[i].createWeight(searcher); } return weights; } public static class FacetWeight extends Weight { private final Weight _weight; private final Weight[] _facets; private FacetExecutor _executor; public FacetWeight(Weight weight, Weight[] facets, FacetExecutor executor) { _weight = weight; _facets = facets; _executor = executor; } @Override public Explanation explain(AtomicReaderContext reader, int doc) throws IOException { return _weight.explain(reader, doc); } @Override public Query getQuery() { return _weight.getQuery(); } @Override public void normalize(float norm, float topLevelBoost) { _weight.normalize(norm, topLevelBoost); } @Override public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, Bits acceptDocs) throws IOException { Scorer scorer = _weight.scorer(context, true, topScorer, acceptDocs); if (scorer == null) { return null; } if (!_executor.scorersAlreadyAdded(context)) { Scorer[] scorers = getScorers(context, true, topScorer, acceptDocs); LOG.debug(_executor.getPrefix("Adding scorers for context [{0}] scorers [{1}]"), context, Arrays.asList(scorers)); _executor.addScorers(context, scorers); } else { LOG.debug(_executor.getPrefix("Scorers already added for context [{0}]"), context); } return new FacetScorer(scorer, _executor, context); } private Scorer[] getScorers(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, Bits acceptDocs) throws IOException { Scorer[] scorers = new Scorer[_facets.length]; for (int i = 0; i < scorers.length; i++) { scorers[i] = _facets[i].scorer(context, scoreDocsInOrder, topScorer, acceptDocs); } return scorers; } @Override public float getValueForNormalization() throws IOException { return _weight.getValueForNormalization(); } } public static class FacetScorer extends Scorer { private static final Log LOG = LogFactory.getLog(FacetScorer.class); private final Scorer _baseScorer; private final OpenBitSet _hit; public FacetScorer(Scorer scorer, FacetExecutor executor, AtomicReaderContext context) throws IOException { super(scorer.getWeight()); _baseScorer = scorer; _hit = executor.getBitSet(context); } private int processFacets(int doc) throws IOException { if (doc == NO_MORE_DOCS) { return doc; } if (doc < 0) { LOG.error("DocId from base scorer < 0 [{0}]", _baseScorer); return doc; } _hit.fastSet(doc); return doc; } @Override public float score() throws IOException { return _baseScorer.score(); } @Override public int advance(int target) throws IOException { return processFacets(_baseScorer.advance(target)); } @Override public int docID() { return _baseScorer.docID(); } @Override public int nextDoc() throws IOException { return processFacets(_baseScorer.nextDoc()); } @Override public int freq() throws IOException { return _baseScorer.freq(); } @Override public long cost() { return _baseScorer.cost(); } } }