/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xcmis.search.lucene.search;
import java.io.IOException;
import java.util.Set;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.Weight;
import org.xcmis.search.lucene.index.FieldNames;
import org.xcmis.spi.utils.Logger;
/**
* Created by The eXo Platform SAS.
*
* @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
* @version $Id: ChildTraversingQueryNode.java 2 2010-02-04 17:21:49Z andrew00x $
*/
public class ChildTraversingQueryNode extends Query
{
/**
* Serial version UID required for safe serialization.
*/
private static final long serialVersionUID = 7265002058181097050L;
/**
* Class logger.
*/
private final static Logger log = Logger.getLogger(ChildTraversingQueryNode.class);
/**
* Query what return parent node.
*/
private final Query parentQuery;
/**
* If isDeep=true return only first level child.
*/
private final boolean isDeep;
private final boolean isIncludeParent;
/**
* @param parentQuery - parent query.
* @param isDeep - if true return only first level child.
*/
public ChildTraversingQueryNode(Query parentQuery, boolean isDeep)
{
super();
this.parentQuery = parentQuery;
this.isDeep = isDeep;
this.isIncludeParent = false;
}
/**
* @param parentQuery - parent query.
* @param isDeep - if true return only first level child.
* @param isIncludeParent
*/
private ChildTraversingQueryNode(Query parentQuery, boolean isDeep, boolean isIncludeParent)
{
super();
this.parentQuery = parentQuery;
this.isDeep = isDeep;
this.isIncludeParent = isIncludeParent;
}
/**
* {@inheritDoc}
*/
public void extractTerms(Set terms)
{
parentQuery.extractTerms(terms);
}
/**
* {@inheritDoc}
*/
public Query rewrite(IndexReader reader) throws IOException
{
Query newParentQuery = parentQuery.rewrite(reader);
if (newParentQuery.equals(parentQuery))
{
return this;
}
return new ChildTraversingQueryNode(newParentQuery, isDeep);
}
/**
* {@inheritDoc}
*/
public String toString()
{
return "(ChildTraversingQueryNode:" + parentQuery + "isDeep" + isDeep + ")";
}
/**
* {@inheritDoc}
*/
/**
* {@inheritDoc}
*/
public String toString(String field)
{
return "(ChildTraversingQueryNode:" + parentQuery + "isDeep" + isDeep + ")";
}
/**
* {@inheritDoc}
*/
@Override
public Weight createWeight(Searcher searcher) throws IOException
{
return new ChildTraversingQueryNodeWeight(searcher);
}
/**
* Scorer for ChildTraversingQuery.
*
*/
private class ChildTraversingQueryNodeScorer extends Scorer
{
/**
* Query searcher.
*/
private Searcher searcher;
/**
* Parent query scorer.
*/
private Scorer parentScorer;
/**
* Query reader.
*/
private IndexReader reader;
/**
* Child Scorer.
*/
private Scorer childScorer;
private boolean scoreDocsInOrder;
private boolean topScorer;
/**
* @param searcher - query searcher.
* @param parentScorer - parent query scorer.
* @param reader - query reader.
*/
public ChildTraversingQueryNodeScorer(Searcher searcher, Scorer parentScorer, IndexReader reader,
boolean scoreDocsInOrder, boolean topScorer)
{
super(searcher.getSimilarity());
this.searcher = searcher;
this.parentScorer = parentScorer;
this.reader = reader;
this.scoreDocsInOrder = scoreDocsInOrder;
this.topScorer = topScorer;
}
/**
* {@inheritDoc}
*/
public int docID()
{
return childScorer.docID();
}
/**
* {@inheritDoc}
*/
public int nextDoc() throws IOException
{
// no parent selected
if (childScorer == null)
{
// search for parent
if (parentScorer == null || parentScorer.nextDoc() == Scorer.NO_MORE_DOCS)
{
if (log.isDebugEnabled())
{
log.debug("Childs not found");
}
return Scorer.NO_MORE_DOCS;
}
// load childs of current parent
reloadChildScorer();
}
return childScorer.nextDoc();
}
/**
* {@inheritDoc}
*/
public float score() throws IOException
{
return childScorer.score();
}
/**
* {@inheritDoc}
*/
public int advance(int target) throws IOException
{
return childScorer.advance(target);
}
private Query createOrQuery(Query first, Query second)
{
if (first == null)
{
return second;
}
else if (second == null)
{
return first;
}
BooleanQuery bq = new BooleanQuery();
bq.add(first, Occur.SHOULD);
bq.add(second, Occur.SHOULD);
return bq;
}
/**
* Create childScorer according to the result of parent query.
*
* @throws CorruptIndexException
* @throws IOException
*/
private void reloadChildScorer() throws CorruptIndexException, IOException
{
Query childQuery = null;
do
{
int parentDoc = parentScorer.docID();
Document parentDocument = reader.document(parentDoc, new UUIDFieldSelector());
Query parentTermQuery = new TermQuery(new Term(FieldNames.PARENT, parentDocument.get(FieldNames.UUID)));
if (isDeep)
{
childQuery = createOrQuery(childQuery, new ChildTraversingQueryNode(parentTermQuery, true, true));
if (isIncludeParent)
{
childQuery =
createOrQuery(childQuery, new TermQuery(new Term(FieldNames.UUID, parentDocument
.get(FieldNames.UUID))));
}
}
else
{
childQuery = createOrQuery(childQuery, parentTermQuery);
}
}
while (parentScorer.nextDoc() < Scorer.NO_MORE_DOCS);
if (log.isDebugEnabled())
{
log.debug("Sub query " + childQuery);
}
childScorer = childQuery.createWeight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
}
}
/**
* Weight for ChildTraversingQuery.
*
*/
private class ChildTraversingQueryNodeWeight extends Weight
{
/**
* Serial version UID required for safe serialization.
*/
private static final long serialVersionUID = -6839886829560442055L;
/**
* Query searcher.
*/
private final Searcher searcher;
/**
* @param searcher - Query searcher.
*/
public ChildTraversingQueryNodeWeight(Searcher searcher)
{
this.searcher = searcher;
}
/**
* {@inheritDoc}
*/
public Explanation explain(IndexReader reader, int doc) throws IOException
{
return new Explanation();
}
/**
* {@inheritDoc}
*/
public Query getQuery()
{
return ChildTraversingQueryNode.this;
}
/**
* {@inheritDoc}
*/
public float getValue()
{
return 1.0f;
}
/**
* {@inheritDoc}
*/
public void normalize(float norm)
{
}
/**
* {@inheritDoc}
*/
@Override
public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException
{
Scorer parentScorer = parentQuery.createWeight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
return new ChildTraversingQueryNodeScorer(searcher, parentScorer, reader, scoreDocsInOrder, topScorer);
}
/**
* {@inheritDoc}
*/
public float sumOfSquaredWeights() throws IOException
{
return 1.0f;
}
}
}