All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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