Package org.apache.jackrabbit.core.query.lucene

Source Code of org.apache.jackrabbit.core.query.lucene.JQOM2LuceneQueryBuilder

/*
* 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.
*/
package org.apache.jackrabbit.core.query.lucene;

import org.apache.jackrabbit.core.query.PropertyTypeRegistry;
import org.apache.jackrabbit.core.query.lucene.fulltext.QueryParser;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.HierarchyManager;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.HierarchyManagerImpl;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.state.ItemStateManager;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.query.qom.QOMTreeVisitor;
import org.apache.jackrabbit.spi.commons.query.qom.QueryObjectModelTree;
import org.apache.jackrabbit.spi.commons.query.qom.AndImpl;
import org.apache.jackrabbit.spi.commons.query.qom.ConstraintImpl;
import org.apache.jackrabbit.spi.commons.query.qom.BindVariableValueImpl;
import org.apache.jackrabbit.spi.commons.query.qom.ChildNodeImpl;
import org.apache.jackrabbit.spi.commons.query.qom.ChildNodeJoinConditionImpl;
import org.apache.jackrabbit.spi.commons.query.qom.ColumnImpl;
import org.apache.jackrabbit.spi.commons.query.qom.ComparisonImpl;
import org.apache.jackrabbit.spi.commons.query.qom.StaticOperandImpl;
import org.apache.jackrabbit.spi.commons.query.qom.DynamicOperandImpl;
import org.apache.jackrabbit.spi.commons.query.qom.PropertyValueImpl;
import org.apache.jackrabbit.spi.commons.query.qom.LengthImpl;
import org.apache.jackrabbit.spi.commons.query.qom.NodeLocalNameImpl;
import org.apache.jackrabbit.spi.commons.query.qom.NodeNameImpl;
import org.apache.jackrabbit.spi.commons.query.qom.FullTextSearchScoreImpl;
import org.apache.jackrabbit.spi.commons.query.qom.UpperCaseImpl;
import org.apache.jackrabbit.spi.commons.query.qom.LowerCaseImpl;
import org.apache.jackrabbit.spi.commons.query.qom.DescendantNodeImpl;
import org.apache.jackrabbit.spi.commons.query.qom.DescendantNodeJoinConditionImpl;
import org.apache.jackrabbit.spi.commons.query.qom.EquiJoinConditionImpl;
import org.apache.jackrabbit.spi.commons.query.qom.FullTextSearchImpl;
import org.apache.jackrabbit.spi.commons.query.qom.JoinImpl;
import org.apache.jackrabbit.spi.commons.query.qom.LiteralImpl;
import org.apache.jackrabbit.spi.commons.query.qom.NotImpl;
import org.apache.jackrabbit.spi.commons.query.qom.OrderingImpl;
import org.apache.jackrabbit.spi.commons.query.qom.OrImpl;
import org.apache.jackrabbit.spi.commons.query.qom.PropertyExistenceImpl;
import org.apache.jackrabbit.spi.commons.query.qom.SameNodeImpl;
import org.apache.jackrabbit.spi.commons.query.qom.SameNodeJoinConditionImpl;
import org.apache.jackrabbit.spi.commons.query.qom.SelectorImpl;
import org.apache.jackrabbit.spi.commons.query.jsr283.qom.QueryObjectModelConstants;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.index.Term;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.PropertyType;
import javax.jcr.PathNotFoundException;
import javax.jcr.NodeIterator;
import javax.jcr.Node;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeIterator;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;

/**
* Implements a query builder that takes an JQOM and creates a lucene {@link
* org.apache.lucene.search.Query} tree that can be executed on an index.
*/
public class JQOM2LuceneQueryBuilder implements QOMTreeVisitor, QueryObjectModelConstants {

    /**
     * Logger for this class
     */
    private static final Logger log = LoggerFactory.getLogger(JQOM2LuceneQueryBuilder.class);

    /**
     * The root of the query object model tree.
     */
    private final QueryObjectModelTree qomTree;

    /**
     * Session of the user executing this query
     */
    private final SessionImpl session;

    /**
     * The item state manager of the workspace.
     */
    private final ItemStateManager ism;

    /**
     * A hierarchy manager based on {@link #ism} to resolve paths.
     */
    private final HierarchyManager hmgr;

    /**
     * Namespace mappings to internal prefixes
     */
    private final NamespaceMappings nsMappings;

    /**
     * NamePathResolver to map namespace mappings to internal prefixes
     */
    private final NamePathResolver npResolver;

    /**
     * The analyzer instance to use for contains function query parsing
     */
    private final Analyzer analyzer;

    /**
     * The property type registry.
     */
    private final PropertyTypeRegistry propRegistry;

    /**
     * The synonym provider or <code>null</code> if none is configured.
     */
    private final SynonymProvider synonymProvider;

    /**
     * Maps variable names to values.
     */
    private final Map bindVariableValues;

    /**
     * The selector queries that have already been translated into lucene
     * queries. Key=Name (selectorName).
     */
    private final Map selectors = new HashMap();

    /**
     * The index format version.
     */
    private final IndexFormatVersion version;

    /**
     * Creates a new <code>LuceneQueryBuilder</code> instance.
     *
     * @param qomTree            the root of the query object model.
     * @param session            of the user executing this query.
     * @param ism                the item state manager of the workspace.
     * @param hmgr               a hierarchy manager based on ism.
     * @param nsMappings         namespace resolver for internal prefixes.
     * @param analyzer           for parsing the query statement of the contains
     *                           function.
     * @param propReg            the property type registry.
     * @param synonymProvider    the synonym provider or <code>null</code> if
     *                           node is configured.
     * @param bindVariableValues the bind variable values.
     * @param version            the index format version.
     */
    private JQOM2LuceneQueryBuilder(QueryObjectModelTree qomTree,
                                    SessionImpl session,
                                    ItemStateManager ism,
                                    HierarchyManager hmgr,
                                    NamespaceMappings nsMappings,
                                    Analyzer analyzer,
                                    PropertyTypeRegistry propReg,
                                    SynonymProvider synonymProvider,
                                    Map bindVariableValues,
                                    IndexFormatVersion version) {
        this.qomTree = qomTree;
        this.session = session;
        this.ism = ism;
        this.hmgr = hmgr;
        this.nsMappings = nsMappings;
        this.npResolver = NamePathResolverImpl.create(nsMappings);
        this.analyzer = analyzer;
        this.propRegistry = propReg;
        this.synonymProvider = synonymProvider;
        this.bindVariableValues = bindVariableValues;
        this.version = version;
    }

    /**
     * Creates a lucene {@link org.apache.lucene.search.Query} tree from an
     * abstract query tree.
     *
     * @param qomTree            the root of the query object model.
     * @param session            of the user executing the query.
     * @param sharedItemMgr      the shared item state manager of the
     *                           workspace.
     * @param nsMappings         namespace resolver for internal prefixes.
     * @param analyzer           for parsing the query statement of the contains
     *                           function.
     * @param propReg            the property type registry to lookup type
     *                           information.
     * @param synonymProvider    the synonym provider or <code>null</code> if
     *                           node is configured.
     * @param bindVariableValues the bind variable values.
     * @param version            the index format version.
     * @return the lucene query tree.
     * @throws RepositoryException if an error occurs during the translation.
     */
    public static Query createQuery(QueryObjectModelTree qomTree,
                                    SessionImpl session,
                                    ItemStateManager sharedItemMgr,
                                    NamespaceMappings nsMappings,
                                    Analyzer analyzer,
                                    PropertyTypeRegistry propReg,
                                    SynonymProvider synonymProvider,
                                    Map bindVariableValues,
                                    IndexFormatVersion version)
            throws RepositoryException {

        HierarchyManager hmgr = new HierarchyManagerImpl(
                RepositoryImpl.ROOT_NODE_ID, sharedItemMgr);
        JQOM2LuceneQueryBuilder builder = new JQOM2LuceneQueryBuilder(
                qomTree, session, sharedItemMgr, hmgr, nsMappings, analyzer,
                propReg, synonymProvider, bindVariableValues, version);

        return builder.createLuceneQuery();
    }

    private Query createLuceneQuery() throws InvalidQueryException {
        try {
            return (Query) qomTree.accept(this, null);
        } catch (InvalidQueryException e) {
            throw e;
        } catch (Exception e) {
            throw new InvalidQueryException(e.getMessage(), e);
        }
    }

    //----------------------------< QOMTreeVisitor >----------------------------

    public Object visit(AndImpl node, Object data) throws Exception {
        BooleanQuery b = new BooleanQuery();
        b.add((Query) ((ConstraintImpl) node.getConstraint1()).accept(this, data),
                BooleanClause.Occur.MUST);
        b.add((Query) ((ConstraintImpl) node.getConstraint2()).accept(this, data),
                BooleanClause.Occur.MUST);
        return b;
    }

    /**
     * @return the {@link Value} for the passed bind variable value node.
     * @throws InvalidQueryException if there is no value bound for the passed
     *                               bind variable.
     */
    public Object visit(BindVariableValueImpl node, Object data)
            throws InvalidQueryException {
        Value v = (Value) bindVariableValues.get(node.getBindVariableQName());
        if (v == null) {
            throw new InvalidQueryException("No value bound for variable "
                    + node.getBindVariableName());
        } else {
            return v;
        }
    }

    public Object visit(ChildNodeImpl node, Object data) {
        Name ntName = qomTree.getSelector(node.getSelectorQName()).getNodeTypeQName();
        List scoreNodes = new ArrayList();
        try {
            Node parent = session.getNode(node.getPath());
            for (NodeIterator it = parent.getNodes(); it.hasNext(); ) {
                NodeImpl n = (NodeImpl) it.nextNode();
                if (n.isNodeType(ntName)) {
                    scoreNodes.add(new ScoreNode(n.getNodeId(), 1.0f));
                }
            }
            return new QueryHitsQuery(new DefaultQueryHits(scoreNodes));
        } catch (PathNotFoundException e) {
            // node does not exist
        } catch (RepositoryException e) {
            log.warn("Exception while constructing query: " + e);
            log.debug("Stacktrace: ", e);
        }
        // return a dummy query, which does not match any nodes
        return new BooleanQuery();
    }

    public Object visit(ChildNodeJoinConditionImpl node, Object data) {
        // TODO: implement
        throw new UnsupportedOperationException("not yet implemented");
    }

    public Object visit(ColumnImpl node, Object data) {
        // query builder should not use this method
        throw new IllegalStateException();
    }

    public Object visit(ComparisonImpl node, Object data) throws Exception {
        return ((DynamicOperandImpl) node.getOperand1()).accept(this, node);
    }

    public Object visit(DescendantNodeImpl node, Object data) throws Exception {
        // TODO simplify, is there a way to aggregate constraints for the same selector?
        Query selectorQuery = (Query) qomTree.getSelector(node.getSelectorQName()).accept(this, null);
        try {
            NodeImpl n = (NodeImpl) session.getNode(node.getPath());
            ScoreNode sn = new ScoreNode(n.getNodeId(), 1.0f);
            Query context = new QueryHitsQuery(new DefaultQueryHits(
                    Collections.singletonList(sn)));
            return new DescendantSelfAxisQuery(context, selectorQuery, false);
        } catch (PathNotFoundException e) {
            // node does not exist
        } catch (RepositoryException e) {
            log.warn("Exception while constructing query: " + e);
            log.debug("Stacktrace: ", e);
        }
        // return a dummy query, which does not match any nodes
        return new BooleanQuery();
    }

    public Object visit(DescendantNodeJoinConditionImpl node, Object data) {
        // TODO: implement
        throw new UnsupportedOperationException("not yet implemented");
    }

    public Object visit(EquiJoinConditionImpl node, Object data) {
        // TODO: implement
        throw new UnsupportedOperationException("not yet implemented");
    }

    public Object visit(FullTextSearchImpl node, Object data) throws Exception {
        String fieldname;
        if (node.getPropertyName() == null) {
            // fulltext on node
            fieldname = FieldNames.FULLTEXT;
        } else {
            // final path element is a property name
            Name propName = node.getPropertyQName();
            StringBuffer tmp = new StringBuffer();
            tmp.append(nsMappings.getPrefix(propName.getNamespaceURI()));
            tmp.append(":").append(FieldNames.FULLTEXT_PREFIX);
            tmp.append(propName.getLocalName());
            fieldname = tmp.toString();
        }
        QueryParser parser = new QueryParser(
                fieldname, analyzer, synonymProvider);
        parser.setOperator(QueryParser.DEFAULT_OPERATOR_AND);
        // replace escaped ' with just '
        StringBuffer query = new StringBuffer();
        String textsearch = node.getFullTextSearchExpression();
        // the default lucene query parser recognizes 'AND' and 'NOT' as
        // keywords.
        textsearch = textsearch.replaceAll("AND", "and");
        textsearch = textsearch.replaceAll("NOT", "not");
        boolean escaped = false;
        for (int i = 0; i < textsearch.length(); i++) {
            if (textsearch.charAt(i) == '\\') {
                if (escaped) {
                    query.append("\\\\");
                    escaped = false;
                } else {
                    escaped = true;
                }
            } else if (textsearch.charAt(i) == '\'') {
                if (escaped) {
                    escaped = false;
                }
                query.append(textsearch.charAt(i));
            } else {
                if (escaped) {
                    query.append('\\');
                    escaped = false;
                }
                query.append(textsearch.charAt(i));
            }
        }
        return parser.parse(query.toString());
    }

    public Object visit(FullTextSearchScoreImpl node, Object data) {
        // TODO: implement
        throw new UnsupportedOperationException("not yet implemented");
    }

    public Object visit(JoinImpl node, Object data) {
        // TODO: implement
        throw new UnsupportedOperationException("not yet implemented");
    }

    public Object visit(LengthImpl node, Object data) throws Exception {
        if (version.getVersion() < IndexFormatVersion.V3.getVersion()) {
            throw new InvalidQueryException("Length operator is only " +
                    "available with index version >= 3. Please re-index " +
                    "repository and execute query again.");
        }
        PropertyValueImpl pv = (PropertyValueImpl) node.getPropertyValue();
        String propName = npResolver.getJCRName(pv.getPropertyQName());
        if (data instanceof ComparisonImpl) {
            ComparisonImpl comp = (ComparisonImpl) data;
            int operator = comp.getOperator();
            Value v = (Value) ((StaticOperandImpl) comp.getOperand2()).accept(this, data);
            String namedLength = FieldNames.createNamedLength(propName, v.getLong());

            switch (operator) {
                case OPERATOR_EQUAL_TO:
                    return new TermQuery(new Term(FieldNames.PROPERTY_LENGTHS, namedLength));
                case OPERATOR_GREATER_THAN:
                    Term lower = new Term(FieldNames.PROPERTY_LENGTHS, namedLength);
                    Term upper = new Term(FieldNames.PROPERTY_LENGTHS,
                            FieldNames.createNamedLength(propName, Long.MAX_VALUE));
                    return new RangeQuery(lower, upper, false);
                case OPERATOR_GREATER_THAN_OR_EQUAL_TO:
                    lower = new Term(FieldNames.PROPERTY_LENGTHS, namedLength);
                    upper = new Term(FieldNames.PROPERTY_LENGTHS,
                            FieldNames.createNamedLength(propName, Long.MAX_VALUE));
                    return new RangeQuery(lower, upper, true);
                case OPERATOR_LESS_THAN:
                    lower = new Term(FieldNames.PROPERTY_LENGTHS,
                            FieldNames.createNamedLength(propName, -1));
                    upper = new Term(FieldNames.PROPERTY_LENGTHS, namedLength);
                    return new RangeQuery(lower, upper, false);
                case OPERATOR_LESS_THAN_OR_EQUAL_TO:
                    lower = new Term(FieldNames.PROPERTY_LENGTHS,
                            FieldNames.createNamedLength(propName, -1));
                    upper = new Term(FieldNames.PROPERTY_LENGTHS, namedLength);
                    return new RangeQuery(lower, upper, true);
                case OPERATOR_LIKE:
                    throw new InvalidQueryException("Like operator cannot be used with length operand");
                case OPERATOR_NOT_EQUAL_TO:
                    Query all = Util.createMatchAllQuery(propName, version);
                    BooleanQuery b = new BooleanQuery();
                    b.add(all, BooleanClause.Occur.SHOULD);
                    b.add(new TermQuery(new Term(FieldNames.PROPERTY_LENGTHS, namedLength)),
                            BooleanClause.Occur.MUST_NOT);
                    return b;
                default:
                    throw new InvalidQueryException(
                            "Unknown operator " + operator);
            }
        } else {
            throw new UnsupportedOperationException("not yet implemented");
        }
    }

    /**
     * @return the {@link Value} of the literal <code>node</code>.
     */
    public Object visit(LiteralImpl node, Object data) {
        return node.getValue();
    }

    public Object visit(LowerCaseImpl node, Object data) throws Exception {
        Object obj = ((DynamicOperandImpl) node.getOperand()).accept(this, data);
        return transformCase(obj, data, false);
    }

    public Object visit(NodeLocalNameImpl node, Object data) throws Exception {
        if (version.getVersion() < IndexFormatVersion.V3.getVersion()) {
            throw new InvalidQueryException("NodeLocalName operand is only " +
                    "available with index version >= 3. Please re-index " +
                    "repository and execute query again.");
        }
        if (data instanceof ComparisonImpl) {
            ComparisonImpl comp = ((ComparisonImpl) data);
            int operator = comp.getOperator();
            Value v = (Value) ((StaticOperandImpl) comp.getOperand2()).accept(this, data);
            String value = v.getString();

            switch (operator) {
                case OPERATOR_EQUAL_TO:
                    return new TermQuery(new Term(FieldNames.LOCAL_NAME, value));
                case OPERATOR_GREATER_THAN:
                    return new LocalNameRangeQuery(value, null, false);
                case OPERATOR_GREATER_THAN_OR_EQUAL_TO:
                    return new LocalNameRangeQuery(value, null, true);
                case OPERATOR_LESS_THAN:
                    return new LocalNameRangeQuery(null, value, false);
                case OPERATOR_LESS_THAN_OR_EQUAL_TO:
                    return new LocalNameRangeQuery(null, value, true);
                case OPERATOR_LIKE:
                    if (value.equals("%")) {
                        return new MatchAllDocsQuery();
                    } else {
                        return new WildcardQuery(FieldNames.LOCAL_NAME, null, value);
                    }
                case OPERATOR_NOT_EQUAL_TO:
                    MatchAllDocsQuery all = new MatchAllDocsQuery();
                    BooleanQuery b = new BooleanQuery();
                    b.add(all, BooleanClause.Occur.SHOULD);
                    b.add(new TermQuery(new Term(FieldNames.LOCAL_NAME, value)),
                            BooleanClause.Occur.MUST_NOT);
                    return b;
                default:
                    throw new InvalidQueryException(
                            "Unknown operator " + operator);
            }
        } else {
            // TODO
            throw new InvalidQueryException("not yet implemented");
        }
    }

    public Object visit(NodeNameImpl node, Object data) throws Exception {
        if (data instanceof ComparisonImpl) {
            ComparisonImpl comp = ((ComparisonImpl) data);
            int operator = comp.getOperator();
            Value v = (Value) ((StaticOperandImpl) comp.getOperand2()).accept(this, data);
            switch (v.getType()) {
                case PropertyType.DATE:
                case PropertyType.DOUBLE:
                // TODO case PropertyType.DECIMAL:
                case PropertyType.LONG:
                case PropertyType.BOOLEAN:
                case PropertyType.REFERENCE:
                // TODO case PropertyType.WEAKREFERENCE:
                // TODO case PropertyType.URI
                    throw new InvalidQueryException(v.getString() +
                            " cannot be converted into a NAME value");
            }

            Name value;
            try {
                value = JQOM2LuceneQueryBuilder.this.session.getQName(v.getString());
            } catch (RepositoryException e) {
                throw new InvalidQueryException(v.getString() +
                        " cannot be converted into a NAME value");
            }

            switch (operator) {
                case OPERATOR_EQUAL_TO:
                    return new NameQuery(value, version, nsMappings);
                case OPERATOR_GREATER_THAN:
                    return new NameRangeQuery(value, null, false, version, nsMappings);
                case OPERATOR_GREATER_THAN_OR_EQUAL_TO:
                    return new NameRangeQuery(value, null, true, version, nsMappings);
                case OPERATOR_LESS_THAN:
                    return new NameRangeQuery(null, value, false, version, nsMappings);
                case OPERATOR_LESS_THAN_OR_EQUAL_TO:
                    return new NameRangeQuery(null, value, true, version, nsMappings);
                case OPERATOR_LIKE:
                    throw new InvalidQueryException("Operator LIKE is not supported with NAME operands");
                case OPERATOR_NOT_EQUAL_TO:
                    MatchAllDocsQuery all = new MatchAllDocsQuery();
                    BooleanQuery b = new BooleanQuery();
                    b.add(all, BooleanClause.Occur.SHOULD);
                    b.add(new NameQuery(value, version, nsMappings),
                            BooleanClause.Occur.MUST_NOT);
                    return b;
                default:
                    throw new InvalidQueryException(
                            "Unknown operator " + operator);
            }
        } else {
            // TODO
            throw new InvalidQueryException("not yet implemented");
        }
    }

    public Object visit(NotImpl node, Object data) throws Exception {
        Query c = (Query) ((ConstraintImpl) node.getConstraint()).accept(this, data);
        return new NotQuery(c);
    }

    public Object visit(OrderingImpl node, Object data) {
        // query builder should not use this method
        throw new IllegalStateException();
    }

    public Object visit(OrImpl node, Object data) throws Exception {
        BooleanQuery b = new BooleanQuery();
        b.add((Query) ((ConstraintImpl) node.getConstraint1()).accept(this, data),
                BooleanClause.Occur.SHOULD);
        b.add((Query) ((ConstraintImpl) node.getConstraint2()).accept(this, data),
                BooleanClause.Occur.SHOULD);
        return b;
    }

    public Object visit(PropertyExistenceImpl node, Object data) throws Exception {
        String propName = npResolver.getJCRName(node.getPropertyQName());
        return Util.createMatchAllQuery(propName, version);
    }

    public Object visit(PropertyValueImpl node, Object data) throws Exception {
        if (data instanceof ComparisonImpl) {
            ComparisonImpl comp = ((ComparisonImpl) data);
            int operator = comp.getOperator();
            Value v = (Value) ((StaticOperandImpl) comp.getOperand2()).accept(this, data);
            String stringValue = stringValueOf(v);
            String propName = npResolver.getJCRName(node.getPropertyQName());
            String text = FieldNames.createNamedValue(propName, stringValue);
            switch (operator) {
                case OPERATOR_EQUAL_TO:
                    return new TermQuery(new Term(FieldNames.PROPERTIES, text));
                case OPERATOR_GREATER_THAN:
                    Term lower = new Term(FieldNames.PROPERTIES, text);
                    Term upper = new Term(FieldNames.PROPERTIES,
                            FieldNames.createNamedValue(propName, "\uFFFF"));
                    return new RangeQuery(lower, upper, false);
                case OPERATOR_GREATER_THAN_OR_EQUAL_TO:
                    lower = new Term(FieldNames.PROPERTIES, text);
                    upper = new Term(FieldNames.PROPERTIES,
                            FieldNames.createNamedValue(propName, "\uFFFF"));
                    return new RangeQuery(lower, upper, true);
                case OPERATOR_LESS_THAN:
                    lower = new Term(FieldNames.PROPERTIES,
                            FieldNames.createNamedValue(propName, ""));
                    upper = new Term(FieldNames.PROPERTIES, text);
                    return new RangeQuery(lower, upper, false);
                case OPERATOR_LESS_THAN_OR_EQUAL_TO:
                    lower = new Term(FieldNames.PROPERTIES,
                            FieldNames.createNamedValue(propName, ""));
                    upper = new Term(FieldNames.PROPERTIES, text);
                    return new RangeQuery(lower, upper, true);
                case OPERATOR_LIKE:
                    if (stringValue.equals("%")) {
                        return Util.createMatchAllQuery(propName, version);
                    } else {
                        return new WildcardQuery(FieldNames.PROPERTIES,
                                propName, stringValue);
                    }
                case OPERATOR_NOT_EQUAL_TO:
                    Query all = Util.createMatchAllQuery(propName, version);
                    BooleanQuery b = new BooleanQuery();
                    b.add(all, BooleanClause.Occur.SHOULD);
                    b.add(new TermQuery(new Term(FieldNames.PROPERTIES, text)),
                            BooleanClause.Occur.MUST_NOT);
                    return b;
                default:
                    throw new InvalidQueryException(
                            "Unknown operator " + operator);
            }
        } else {
            // TODO
            throw new InvalidQueryException("not yet implemented");
        }
    }

    public Object visit(QueryObjectModelTree node, Object data)
            throws Exception {
        Query source = (Query) node.getSource().accept(this, data);
        if (node.getConstraint() == null) {
            return source;
        } else {
            Query constraint = (Query) node.getConstraint().accept(this, data);
            BooleanQuery b = new BooleanQuery();
            b.add(source, BooleanClause.Occur.MUST);
            b.add(constraint, BooleanClause.Occur.MUST);
            return b;
        }
    }

    public Object visit(SameNodeImpl node, Object data) {
        Name ntName = qomTree.getSelector(node.getSelectorQName()).getNodeTypeQName();
        try {
            NodeImpl n = (NodeImpl) session.getNode(node.getPath());
            if (n.isNodeType(ntName)) {
                ScoreNode sn = new ScoreNode(n.getNodeId(), 1.0f);
                return new QueryHitsQuery(new DefaultQueryHits(
                        Collections.singletonList(sn)));
            }
        } catch (PathNotFoundException e) {
            // node does not exist
        } catch (RepositoryException e) {
            log.warn("Exception while constructing query: " + e);
            log.debug("Stacktrace: ", e);
        }
        // return a dummy query, which does not match any nodes
        return new BooleanQuery();
    }

    public Object visit(SameNodeJoinConditionImpl node, Object data) {
        // TODO: implement
        throw new UnsupportedOperationException("not yet implemented");
    }

    public Object visit(SelectorImpl node, Object data) throws Exception {
        List terms = new ArrayList();
        String mixinTypesField = npResolver.getJCRName(NameConstants.JCR_MIXINTYPES);
        String primaryTypeField = npResolver.getJCRName(NameConstants.JCR_PRIMARYTYPE);

        NodeTypeManager ntMgr = session.getWorkspace().getNodeTypeManager();
        NodeType base = null;
        try {
            base = ntMgr.getNodeType(session.getJCRName(node.getNodeTypeQName()));
        } catch (RepositoryException e) {
            // node type does not exist
        }

        if (base != null && base.isMixin()) {
            // search for nodes where jcr:mixinTypes is set to this mixin
            Term t = new Term(FieldNames.PROPERTIES,
                    FieldNames.createNamedValue(mixinTypesField,
                            npResolver.getJCRName(node.getNodeTypeQName())));
            terms.add(t);
        } else {
            // search for nodes where jcr:primaryType is set to this type
            Term t = new Term(FieldNames.PROPERTIES,
                    FieldNames.createNamedValue(primaryTypeField,
                            npResolver.getJCRName(node.getNodeTypeQName())));
            terms.add(t);
        }

        // now search for all node types that are derived from base
        if (base != null) {
            NodeTypeIterator allTypes = ntMgr.getAllNodeTypes();
            while (allTypes.hasNext()) {
                NodeType nt = allTypes.nextNodeType();
                NodeType[] superTypes = nt.getSupertypes();
                if (Arrays.asList(superTypes).contains(base)) {
                    Name n = session.getQName(nt.getName());
                    String ntName = nsMappings.translatePropertyName(n);
                    Term t;
                    if (nt.isMixin()) {
                        // search on jcr:mixinTypes
                        t = new Term(FieldNames.PROPERTIES,
                                FieldNames.createNamedValue(mixinTypesField, ntName));
                    } else {
                        // search on jcr:primaryType
                        t = new Term(FieldNames.PROPERTIES,
                                FieldNames.createNamedValue(primaryTypeField, ntName));
                    }
                    terms.add(t);
                }
            }
        }
        Query q;
        if (terms.size() == 1) {
            q = new TermQuery((Term) terms.get(0));
        } else {
            BooleanQuery b = new BooleanQuery();
            for (Iterator it = terms.iterator(); it.hasNext();) {
                b.add(new TermQuery((Term) it.next()), BooleanClause.Occur.SHOULD);
            }
            q = b;
        }
        selectors.put(node.getSelectorQName(), q);
        return q;
    }

    public Object visit(UpperCaseImpl node, Object data) throws Exception {
        Object obj = ((DynamicOperandImpl) node.getOperand()).accept(this, data);
        return transformCase(obj, data, true);
    }

    //------------------------------< internal >--------------------------------

    private String stringValueOf(Value value) throws RepositoryException {
        switch (value.getType()) {
            case PropertyType.BINARY:
                return value.getString();
            case PropertyType.BOOLEAN:
                return value.getString();
            case PropertyType.DATE:
                return DateField.dateToString(value.getDate().getTime());
            case PropertyType.DOUBLE:
                return DoubleField.doubleToString(value.getDouble());
            case PropertyType.LONG:
                return LongField.longToString(value.getLong());
            case PropertyType.NAME:
                Name n = session.getQName(value.getString());
                return nsMappings.translatePropertyName(n);
            case PropertyType.PATH:
                Path p = session.getQPath(value.getString());
                return npResolver.getJCRPath(p);
            case PropertyType.REFERENCE:
                return value.getString();
            case PropertyType.STRING:
                return value.getString();
            default:
                // TODO: support for new types defined in JSR 283
                throw new InvalidQueryException("Unsupported property type "
                        + PropertyType.nameFromValue(value.getType()));
        }
    }

    private Query transformTermQuery(TermQuery query, boolean toUpper)
            throws InvalidQueryException {
        if (query.getTerm().field() == FieldNames.PROPERTIES) {
            if (toUpper) {
                return new CaseTermQuery.Upper(query.getTerm());
            } else {
                return new CaseTermQuery.Lower(query.getTerm());
            }
        } else if (query.getTerm().field() == FieldNames.PROPERTIES_SET) {
            return query;
        } else {
            throw new InvalidQueryException(
                    "Upper/LowerCase not supported on field "
                    + query.getTerm().field());
        }
    }

    private Object transformCase(Object operand, Object data, boolean toUpperCase)
            throws InvalidQueryException, Exception {
        if (operand instanceof Transformable) {
            ((Transformable) operand).setTransformation(toUpperCase ?
                    TransformConstants.TRANSFORM_UPPER_CASE :
                    TransformConstants.TRANSFORM_LOWER_CASE);
            return operand;
        } else if (operand instanceof TermQuery) {
            return transformTermQuery((TermQuery) operand, toUpperCase);
        } else if (operand instanceof LowerCaseImpl) {
            LowerCaseImpl lc = (LowerCaseImpl) operand;
            if (toUpperCase) {
                // upper-case operand, ignore lower-case
                return transformCase(lc.getOperand(), data, true);
            } else {
                // lower-cased twice
                return ((DynamicOperandImpl) lc.getOperand()).accept(this, data);
            }
        } else if (operand instanceof UpperCaseImpl) {
            UpperCaseImpl oc = (UpperCaseImpl) operand;
            if (toUpperCase) {
                // upper-cased twice
                return ((DynamicOperandImpl) oc.getOperand()).accept(this, data);
            } else {
                // lower-case operand, ignore upper-case
                return transformCase(oc.getOperand(), data, false);
            }
        } else if (operand instanceof CaseTermQuery) {
            CaseTermQuery ctq = (CaseTermQuery) operand;
            return transformTermQuery(new TermQuery(ctq.getTerm()), toUpperCase);
        } else if (operand instanceof MatchAllQuery) {
            return operand;
        } else if (operand instanceof BooleanQuery) {
            BooleanQuery original = (BooleanQuery) operand;
            BooleanQuery transformed = new BooleanQuery();
            BooleanClause[] clauses = original.getClauses();
            for (int i = 0; i < clauses.length; i++) {
                Query q = (Query) transformCase(clauses[i].getQuery(),
                        data, toUpperCase);
                transformed.add(q, clauses[i].getOccur());
            }
            return transformed;
        } else {
            throw new InvalidQueryException(
                    "lower/upper-case not supported on operand "
                    + operand.getClass().getName());
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.query.lucene.JQOM2LuceneQueryBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.