/* * 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.SessionImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.query.PropertyTypeRegistry; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.spi.commons.query.AndQueryNode; import org.apache.jackrabbit.spi.commons.query.DefaultQueryNodeVisitor; import org.apache.jackrabbit.spi.commons.query.LocationStepQueryNode; import org.apache.jackrabbit.spi.commons.query.NodeTypeQueryNode; import org.apache.jackrabbit.spi.commons.query.OrderQueryNode; import org.apache.jackrabbit.spi.commons.query.QueryNodeFactory; import org.apache.jackrabbit.spi.commons.query.QueryParser; import org.apache.jackrabbit.spi.commons.query.QueryRootNode; import org.apache.jackrabbit.spi.commons.query.qom.ColumnImpl; import org.apache.lucene.search.Query; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.RepositoryException; import javax.jcr.Workspace; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.InvalidQueryException; import javax.jcr.query.QueryResult; import javax.jcr.query.qom.QueryObjectModelFactory; import java.util.LinkedHashMap; import java.util.Map; /** * Implements the {@link org.apache.jackrabbit.core.query.ExecutableQuery} * interface. */ public class QueryImpl extends AbstractQueryImpl { /** * The logger instance for this class */ private static final Logger log = LoggerFactory.getLogger(QueryImpl.class); /** * The default selector name 's'. */ public static final Name DEFAULT_SELECTOR_NAME = NameFactoryImpl.getInstance().create("", "s"); /** * The root node of the query tree */ protected final QueryRootNode root; /** * Creates a new query instance from a query string. * * @param sessionContext component context of the current session * @param index the search index. * @param propReg the property type registry. * @param statement the query statement. * @param language the syntax of the query statement. * @param factory the query node factory. * @throws InvalidQueryException if the query statement is invalid according * to the specified <code>language</code>. */ public QueryImpl( SessionContext sessionContext, SearchIndex index, PropertyTypeRegistry propReg, String statement, String language, QueryNodeFactory factory) throws InvalidQueryException { super(sessionContext, index, propReg); // parse query according to language // build query tree using the passed factory this.root = QueryParser.parse( statement, language, sessionContext, factory); } /** * Executes this query and returns a <code>{@link QueryResult}</code>. * * @param offset the offset in the total result set * @param limit the maximum result size * @return a <code>QueryResult</code> * @throws RepositoryException if an error occurs */ public QueryResult execute(long offset, long limit) throws RepositoryException { if (log.isDebugEnabled()) { log.debug("Executing query: \n" + root.dump()); } // build lucene query Query query = LuceneQueryBuilder.createQuery( root, sessionContext.getSessionImpl(), index.getContext().getItemStateManager(), index.getNamespaceMappings(), index.getTextAnalyzer(), propReg, index.getSynonymProvider(), index.getIndexFormatVersion(), cache); OrderQueryNode orderNode = root.getOrderNode(); OrderQueryNode.OrderSpec[] orderSpecs; if (orderNode != null) { orderSpecs = orderNode.getOrderSpecs(); } else { orderSpecs = new OrderQueryNode.OrderSpec[0]; } Path[] orderProperties = new Path[orderSpecs.length]; boolean[] ascSpecs = new boolean[orderSpecs.length]; String[] orderFuncs = new String[orderSpecs.length]; for (int i = 0; i < orderSpecs.length; i++) { orderProperties[i] = orderSpecs[i].getPropertyPath(); ascSpecs[i] = orderSpecs[i].isAscending(); orderFuncs[i] = orderSpecs[i].getFunction(); } return new SingleColumnQueryResult( index, sessionContext, this, query, new SpellSuggestion(index.getSpellChecker(), root), getColumns(), orderProperties, ascSpecs, orderFuncs, orderProperties.length == 0 && getRespectDocumentOrder(), offset, limit); } /** * Returns the columns for this query. * * @return array of columns. * @throws RepositoryException if an error occurs. */ protected ColumnImpl[] getColumns() throws RepositoryException { SessionImpl session = sessionContext.getSessionImpl(); QueryObjectModelFactory qomFactory = session.getWorkspace().getQueryManager().getQOMFactory(); // get columns Map<Name, ColumnImpl> columns = new LinkedHashMap<Name, ColumnImpl>(); for (Name name : root.getSelectProperties()) { String pn = sessionContext.getJCRName(name); ColumnImpl col = (ColumnImpl) qomFactory.column( sessionContext.getJCRName(DEFAULT_SELECTOR_NAME), pn, pn); columns.put(name, col); } if (columns.size() == 0) { // use node type constraint LocationStepQueryNode[] steps = root.getLocationNode().getPathSteps(); final Name[] ntName = new Name[1]; steps[steps.length - 1].acceptOperands(new DefaultQueryNodeVisitor() { public Object visit(AndQueryNode node, Object data) throws RepositoryException { return node.acceptOperands(this, data); } public Object visit(NodeTypeQueryNode node, Object data) { ntName[0] = node.getValue(); return data; } }, null); if (ntName[0] == null) { ntName[0] = NameConstants.NT_BASE; } NodeTypeImpl nt = session.getNodeTypeManager().getNodeType(ntName[0]); PropertyDefinition[] propDefs = nt.getPropertyDefinitions(); for (PropertyDefinition pd : propDefs) { QPropertyDefinition propDef = ((PropertyDefinitionImpl) pd).unwrap(); if (!propDef.definesResidual() && !propDef.isMultiple()) { columns.put(propDef.getName(), columnForName(propDef.getName())); } } } // add jcr:path and jcr:score if not selected already if (!columns.containsKey(NameConstants.JCR_PATH)) { columns.put(NameConstants.JCR_PATH, columnForName(NameConstants.JCR_PATH)); } if (!columns.containsKey(NameConstants.JCR_SCORE)) { columns.put(NameConstants.JCR_SCORE, columnForName(NameConstants.JCR_SCORE)); } return columns.values().toArray(new ColumnImpl[columns.size()]); } /** * Returns <code>true</code> if this query node needs items under * /jcr:system to be queried. * * @return <code>true</code> if this query node needs content under * /jcr:system to be queried; <code>false</code> otherwise. */ public boolean needsSystemTree() { return this.root.needsSystemTree(); } /** * Returns a column for the given property name and the default selector * name. * * @param propertyName the name of the property as well as the column. * @return a column. * @throws RepositoryException if an error occurs while creating the column. */ protected ColumnImpl columnForName(Name propertyName) throws RepositoryException { Workspace workspace = sessionContext.getSessionImpl().getWorkspace(); QueryObjectModelFactory qomFactory = workspace.getQueryManager().getQOMFactory(); String name = sessionContext.getJCRName(propertyName); return (ColumnImpl) qomFactory.column( sessionContext.getJCRName(DEFAULT_SELECTOR_NAME), name, name); } }