
com.adobe.cq.searchcollections.lucene.LuceneQueryFactory Maven / Gradle / Ivy
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2012 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.adobe.cq.searchcollections.lucene;
import static com.adobe.cq.searchcollections.lucene.TransformConstants.TRANSFORM_LOWER_CASE;
import static com.adobe.cq.searchcollections.lucene.TransformConstants.TRANSFORM_NONE;
import static com.adobe.cq.searchcollections.lucene.TransformConstants.TRANSFORM_UPPER_CASE;
import static javax.jcr.PropertyType.DATE;
import static javax.jcr.PropertyType.DECIMAL;
import static javax.jcr.PropertyType.DOUBLE;
import static javax.jcr.PropertyType.LONG;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_LIKE;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO;
import static org.apache.lucene.search.BooleanClause.Occur.MUST;
import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Row;
import javax.jcr.query.qom.And;
import javax.jcr.query.qom.ChildNode;
import javax.jcr.query.qom.Comparison;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.DescendantNode;
import javax.jcr.query.qom.DynamicOperand;
import javax.jcr.query.qom.FullTextSearch;
import javax.jcr.query.qom.FullTextSearchScore;
import javax.jcr.query.qom.Length;
import javax.jcr.query.qom.LowerCase;
import javax.jcr.query.qom.NodeLocalName;
import javax.jcr.query.qom.NodeName;
import javax.jcr.query.qom.Not;
import javax.jcr.query.qom.Or;
import javax.jcr.query.qom.Ordering;
import javax.jcr.query.qom.PropertyExistence;
import javax.jcr.query.qom.PropertyValue;
import javax.jcr.query.qom.QueryObjectModelConstants;
import javax.jcr.query.qom.SameNode;
import javax.jcr.query.qom.Selector;
import javax.jcr.query.qom.StaticOperand;
import javax.jcr.query.qom.UpperCase;
import org.apache.jackrabbit.commons.predicate.Predicate;
import org.apache.jackrabbit.commons.predicate.Predicates;
import org.apache.jackrabbit.commons.predicate.RowPredicate;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;
import com.adobe.cq.searchcollections.qom.OperandEvaluator;
import com.adobe.cq.searchcollections.qom.SelectorRow;
import com.adobe.cq.searchcollections.qom.ValueComparator;
/**
* Factory that creates Lucene queries from QOM elements.
*
* @deprecated
*/
public class LuceneQueryFactory {
private static final boolean ASCENDING = false;
private static final boolean DESCENDING = true;
/** Lucene directory */
private final Directory directory;
/** Lucene analyzer */
private final Analyzer analyzer;
/**
* Session of the user executing this query
*/
private final Session session;
/**
* Node type manager
*/
private final NodeTypeManager ntManager;
/** Operand evaluator */
private final OperandEvaluator evaluator;
/**
* Creates a new lucene query factory.
*
* @param session the session that executes the query.
* @param bindVariables the bind variable values of the query
*/
public LuceneQueryFactory(
Directory directory, Analyzer analyzer,
Session session, Map bindVariables)
throws RepositoryException {
this.directory = directory;
this.analyzer = analyzer;
this.session = session;
this.ntManager = session.getWorkspace().getNodeTypeManager();
this.evaluator =
new OperandEvaluator(session.getValueFactory(), bindVariables);
}
public SearchResults execute(
Map columns, Selector selector,
Constraint constraint,
Ordering[] orderings,
long offset,
long maxResults) throws RepositoryException, IOException {
Predicate filter = Predicate.TRUE;
BooleanQuery query = new BooleanQuery();
query.add(create(selector), MUST);
if (constraint != null) {
String name = selector.getSelectorName();
NodeType type =
ntManager.getNodeType(selector.getNodeTypeName());
filter = mapConstraintToQueryAndFilter(
query, constraint, Collections.singletonMap(name, type));
}
int totalNumberOfResults;
List rows = new ArrayList();
IndexSearcher searcher = new IndexSearcher(directory);
try {
Sort sort = null;
if (null != orderings) {
// TODO JH this needs to be configurable...
List sortFields = new ArrayList();
for (Ordering ordering : orderings) {
DynamicOperand operand = ordering.getOperand();
if (operand instanceof PropertyValue) {
PropertyValue propertyValue = (PropertyValue) operand;
if (QueryObjectModelConstants.JCR_ORDER_ASCENDING.equals(ordering.getOrder())) {
sortFields.add(new SortField(propertyValue.getPropertyName(), SortField.STRING, ASCENDING));
} else {
sortFields.add(new SortField(propertyValue.getPropertyName(), SortField.STRING, DESCENDING));
}
}
}
if (sortFields.size() > 0) {
sort = new Sort(sortFields.toArray(new SortField[0]));
}
}
// Sort sort = new Sort(new SortField(":modifiedDate", SortField.STRING, true));
int numResults = (int) (offset + maxResults);
int resultSize;
if (numResults < 0) {
numResults = Integer.MAX_VALUE;
}
int offsetInt = (int) offset;
if (offset < 1) {
offsetInt = 0;
}
TopDocs hits;
if (null == sort) {
hits = searcher.search(query, numResults);
} else {
hits = searcher.search(query, null, numResults, sort);
}
ScoreDoc[] docs = hits.scoreDocs;
resultSize = Math.min(numResults, docs.length);
for (int i = offsetInt; i < resultSize; i++) {
ScoreDoc doc = docs[i];
final String path = searcher.doc(doc.doc).get(":path");
Node n = null;
try {
n = session.getNode(path);
} catch (ItemNotFoundException e) {
// skip the node
} catch (PathNotFoundException e) {
// skip the node
}
if (n == null) {
continue;
}
final Row row = new SelectorRow(columns, evaluator,
selector.getSelectorName(), n, doc.score);
if (filter.evaluate(row)) {
rows.add(row);
}
}
totalNumberOfResults = hits.totalHits;
} finally {
searcher.close();
}
return new ListSearchResults(rows, totalNumberOfResults);
}
/**
* Creates a lucene query for the given QOM selector.
*
* @param selector the selector.
* @return a lucene query for the given selector.
* @throws RepositoryException if an error occurs while creating the query.
*/
public Query create(Selector selector) throws RepositoryException {
List terms = new ArrayList();
String name = selector.getNodeTypeName();
NodeTypeIterator allTypes = ntManager.getAllNodeTypes();
while (allTypes.hasNext()) {
NodeType nt = allTypes.nextNodeType();
if (nt.isNodeType(name)) {
terms.add(createNodeTypeTerm(nt));
}
}
if (terms.size() == 1) {
return new TermQuery(terms.get(0));
} else {
BooleanQuery b = new BooleanQuery();
for (Term term : terms) {
b.add(new TermQuery(term), SHOULD);
}
return b;
}
}
protected Term createNodeTypeTerm(NodeType type) throws RepositoryException {
if (type.isMixin()) {
// search for nodes where jcr:mixinTypes is set to this mixin
return new Term("jcr:mixinTypes", type.getName());
} else {
// search for nodes where jcr:primaryType is set to this type
return new Term("jcr:primaryType", type.getName());
}
}
/**
* Creates a lucene query for the given QOM full text search.
*
* @param constraint the full text search constraint.
* @return the lucene query for the given constraint.
* @throws RepositoryException if an error occurs while creating the query.
*/
public Query create(FullTextSearch fts) throws RepositoryException {
String field = fts.getPropertyName();
if (field == null) {
field = ":fulltext"; // fulltext on node
} else {
field = ":fulltext:" + field; // fulltext on property
}
final StaticOperand expr = fts.getFullTextSearchExpression();
final QueryParser parser = new QueryParser(Version.LUCENE_36, field,
analyzer);
parser.setAllowLeadingWildcard(true);
try {
return parser.parse(evaluator.getValue(expr).getString());
} catch (ParseException e) {
throw new RepositoryException(e);
}
}
/**
* Creates a lucene query for the given QOM property existence constraint.
*
* @param constraint the QOM constraint.
* @return the lucene query for the given constraint.
* @throws RepositoryException if an error occurs while creating the query.
*/
public Query create(PropertyExistence prop) throws RepositoryException {
String propName = prop.getPropertyName();
return new TermQuery(new Term(":properties", propName));
}
protected Predicate mapConstraintToQueryAndFilter(
BooleanQuery query, Constraint constraint,
Map selectorMap)
throws RepositoryException, IOException {
Predicate filter = Predicate.TRUE;
if (constraint instanceof And) {
And and = (And) constraint;
filter = mapConstraintToQueryAndFilter(
query, and.getConstraint1(), selectorMap);
Predicate other = mapConstraintToQueryAndFilter(
query, and.getConstraint2(), selectorMap);
if (filter == Predicate.TRUE) {
filter = other;
} else if (other != Predicate.TRUE) {
filter = Predicates.and(filter, other);
}
} else if (constraint instanceof Comparison) {
Comparison c = (Comparison) constraint;
Transform transform = new Transform(c.getOperand1());
DynamicOperand left = transform.operand;
final String operator = c.getOperator();
StaticOperand right = c.getOperand2();
if (left instanceof Length
|| left instanceof FullTextSearchScore
|| ((!JCR_OPERATOR_EQUAL_TO.equals(operator)
|| transform.transform != TRANSFORM_NONE)
&& (left instanceof NodeName
|| left instanceof NodeLocalName))) {
try {
int type = PropertyType.UNDEFINED;
if (left instanceof Length) {
type = PropertyType.LONG;
} else if (left instanceof FullTextSearchScore) {
type = PropertyType.DOUBLE;
}
final DynamicOperand operand = c.getOperand1();
final Value value = evaluator.getValue(right, type);
filter = new RowPredicate() {
@Override
protected boolean evaluate(Row row)
throws RepositoryException {
return new ValueComparator().evaluate(
operator,
evaluator.getValue(operand, row), value);
}
};
} catch (ValueFormatException e) {
throw new InvalidQueryException(e);
}
} else {
Query cq = getComparisonQuery(
left, transform.transform, operator, right, selectorMap);
query.add(cq, MUST);
}
} else if (constraint instanceof DescendantNode) {
DescendantNode descendantNode = (DescendantNode) constraint;
String path = descendantNode.getAncestorPath();
if (path.equals("/")) {
query.add(new TermQuery(new Term(":path", path)), MUST_NOT);
} else {
query.add(new PrefixQuery(new Term(":path", path + "/")), MUST);
}
} else if (constraint instanceof Not){
query.add(create(constraint, selectorMap), MUST_NOT);
} else {
query.add(create(constraint, selectorMap), MUST);
}
return filter;
}
protected Query create(
Constraint constraint, Map selectorMap)
throws RepositoryException, IOException {
if (constraint instanceof And) {
return getAndQuery((And) constraint, selectorMap);
} else if (constraint instanceof Or) {
return getOrQuery((Or) constraint, selectorMap);
} else if (constraint instanceof Not) {
return getNotQuery((Not) constraint, selectorMap);
} else if (constraint instanceof PropertyExistence) {
return create((PropertyExistence) constraint);
} else if (constraint instanceof Comparison) {
Comparison c = (Comparison) constraint;
Transform left = new Transform(c.getOperand1());
return getComparisonQuery(
left.operand, left.transform, c.getOperator(),
c.getOperand2(), selectorMap);
} else if (constraint instanceof FullTextSearch) {
return create((FullTextSearch) constraint);
} else if (constraint instanceof SameNode) {
SameNode sn = (SameNode) constraint;
return new TermQuery(new Term(":path", sn.getPath()));
} else if (constraint instanceof ChildNode) {
ChildNode cn = (ChildNode) constraint;
return new TermQuery(new Term(":parent", cn.getParentPath()));
} else if (constraint instanceof DescendantNode) {
DescendantNode dn = (DescendantNode) constraint;
return new SubtreeQuery(dn.getAncestorPath(), false);
} else {
throw new UnsupportedRepositoryOperationException(
"Unknown constraint type: " + constraint);
}
}
protected BooleanQuery getAndQuery(
And and, Map selectorMap)
throws RepositoryException, IOException {
BooleanQuery query = new BooleanQuery();
addBooleanConstraint(query, and.getConstraint1(), MUST, selectorMap);
addBooleanConstraint(query, and.getConstraint2(), MUST, selectorMap);
return query;
}
protected BooleanQuery getOrQuery(
Or or, Map selectorMap)
throws RepositoryException, IOException {
BooleanQuery query = new BooleanQuery();
addBooleanConstraint(query, or.getConstraint1(), SHOULD, selectorMap);
addBooleanConstraint(query, or.getConstraint2(), SHOULD, selectorMap);
return query;
}
protected BooleanQuery getNotQuery(
Not not, Map selectorMap)
throws RepositoryException, IOException {
BooleanQuery query = new BooleanQuery();
addBooleanConstraint(query, not.getConstraint(), SHOULD, selectorMap);
return query;
}
protected void addBooleanConstraint(
BooleanQuery query, Constraint constraint, Occur occur,
Map selectorMap)
throws RepositoryException, IOException {
if (occur == MUST && constraint instanceof And) {
And and = (And) constraint;
addBooleanConstraint(
query, and.getConstraint1(), occur, selectorMap);
addBooleanConstraint(
query, and.getConstraint2(), occur, selectorMap);
} else if (occur == SHOULD && constraint instanceof Or) {
Or or = (Or) constraint;
addBooleanConstraint(
query, or.getConstraint1(), occur, selectorMap);
addBooleanConstraint(
query, or.getConstraint2(), occur, selectorMap);
} else {
query.add(create(constraint, selectorMap), occur);
}
}
protected static class Transform {
private final DynamicOperand operand;
private final int transform;
public Transform(DynamicOperand operand) {
// Check the transformation type
if (operand instanceof UpperCase) {
this.transform = TRANSFORM_UPPER_CASE;
} else if (operand instanceof LowerCase) {
this.transform = TRANSFORM_LOWER_CASE;
} else {
this.transform = TRANSFORM_NONE;
}
// Unwrap any nested transformations
while (true) {
if (operand instanceof UpperCase) {
operand = ((UpperCase) operand).getOperand();
} else if (operand instanceof LowerCase) {
operand = ((LowerCase) operand).getOperand();
} else {
break;
}
}
this.operand = operand;
}
}
protected Query getComparisonQuery(
DynamicOperand left, int transform, String operator,
StaticOperand rigth, Map selectorMap)
throws RepositoryException {
if (left instanceof PropertyValue) {
PropertyValue pv = (PropertyValue) left;
String field = pv.getPropertyName();
int type = PropertyType.UNDEFINED;
NodeType nt = selectorMap.get(pv.getSelectorName());
if (nt != null) {
for (PropertyDefinition pd : nt.getPropertyDefinitions()) {
if (pd.getName().equals(pv.getPropertyName())) {
type = pd.getRequiredType();
}
}
}
return getPropertyValueQuery(
field, operator, evaluator.getValue(rigth), type, transform);
} else if (left instanceof NodeName) {
return getNodeNameQuery(transform, operator, rigth);
} else if (left instanceof NodeLocalName) {
return getNodeLocalNameQuery(transform, operator, rigth);
} else {
throw new UnsupportedRepositoryOperationException(
"Unknown operand type: " + left); // FIXME
}
}
protected Query getNodeNameQuery(
int transform, String operator, StaticOperand right)
throws RepositoryException {
String name = evaluator.getValue(right).getString();
return new TermQuery(new Term(":name", name));
}
protected Query getNodeLocalNameQuery(
int transform, String operator, StaticOperand right)
throws RepositoryException {
String name = evaluator.getValue(right).getString();
return new TermQuery(new Term(":local", name));
}
protected Query getPropertyValueQuery(
String field, String operator, Value value,
int type, int transform) throws RepositoryException {
String string = getValueString(value, type);
Term term = new Term(field, string);
if (JCR_OPERATOR_LIKE.equals(operator)) {
return new WildcardQuery(term);
} else if (JCR_OPERATOR_EQUAL_TO.equals(operator)) {
return new TermQuery(term);
} else if (JCR_OPERATOR_GREATER_THAN.equals(operator)) {
return new TermRangeQuery(field, string, "\uFFFF", false, true);
} else if (JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO.equals(operator)) {
return new TermRangeQuery(field, string, "\uFFFF", true, true);
} else if (JCR_OPERATOR_LESS_THAN.equals(operator)) {
return new TermRangeQuery(field, "", string, true, false);
} else if (JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO.equals(operator)) {
return new TermRangeQuery(field, "", string, true, true);
} else if (JCR_OPERATOR_NOT_EQUAL_TO.equals(operator)) {
BooleanQuery query = new BooleanQuery();
query.add(new TermQuery(term), MUST_NOT);
return query;
} else {
throw new UnsupportedRepositoryOperationException(); // FIXME
}
}
private String getValueString(Value value, int type)
throws RepositoryException {
switch (value.getType()) {
case DATE:
return DateField.dateToString(value.getDate().getTime());
case DOUBLE:
return DoubleField.doubleToString(value.getDouble());
case LONG:
return LongField.longToString(value.getLong());
case DECIMAL:
return DecimalField.decimalToString(value.getDecimal());
default:
return value.getString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy