上文最后提到jackrabbit的检索默认实现类QueryImpl,先熟悉一下该类的继承层次
QueryImpl继承自抽象类AbstractQueryImpl,而抽象类实现了Query接口(JCR的接口)
Query接口源码如下:
/** * A <code>Query</code> object. */ public interface Query { /** * A string constant representing the XPath query language as defined in JCR * 1.0. * * @deprecated As of JCR 2.0, this language is deprecated. */ public static final String XPATH = "xpath"; /** * A string constant representing the SQL query language as defined in JCR * 1.0. * * @deprecated As of JCR 2.0, this language is deprecated. */ public static final String SQL = "sql"; /** * A string constant representing the JCR-SQL2 query language. * * @since JCR 2.0 */ public static final String JCR_SQL2 = "JCR-SQL2"; /** * A string constant representing the JCR-JQOM query language. * * @since JCR 2.0 */ public static final String JCR_JQOM = "JCR-JQOM"; /** * Executes this query and returns a <code>{@link QueryResult}</code> * object. * <p> * If this <code>Query</code> contains a variable (see {@link * javax.jcr.query.qom.BindVariableValue BindVariableValue}) which has not * been bound to a value (see {@link Query#bindValue}) then this method * throws an <code>InvalidQueryException</code>. * * @return a <code>QueryResult</code> object * @throws InvalidQueryException if the query contains an unbound variable. * @throws RepositoryException if another error occurs. */ public QueryResult execute() throws InvalidQueryException, RepositoryException; /** * Sets the maximum size of the result set to <code>limit</code>. * * @param limit a <code>long</code> * @since JCR 2.0 */ public void setLimit(long limit); /** * Sets the start offset of the result set to <code>offset</code>. * * @param offset a <code>long</code> * @since JCR 2.0 */ public void setOffset(long offset); /** * Returns the statement defined for this query. * <p> * If the language of this query is JCR-SQL2 or another string-based * language, this method will return the statement that was used to create * this query. * <p> * If the language of this query is JCR-JQOM, this method will return the * JCR-SQL2 equivalent of the JCR-JQOM object tree. This is the standard * serialization of JCR-JQOM and is also the string stored in the * <code>jcr:statement</code> property if the query is persisted. See {@link * #storeAsNode(String)}. * * @return the query statement. */ public String getStatement(); /** * Returns the language set for this query. This will be one of the query * language constants returned by {@link QueryManager#getSupportedQueryLanguages}. * * @return the query language. */ public String getLanguage(); /** * If this is a <code>Query</code> object that has been stored using {@link * Query#storeAsNode} (regardless of whether it has been <code>save</code>d * yet) or retrieved using {@link QueryManager#getQuery}), then this method * returns the path of the <code>nt:query</code> node that stores the * query. * * @return path of the node representing this query. * @throws ItemNotFoundException if this query is not a stored query. * @throws RepositoryException if another error occurs. */ public String getStoredQueryPath() throws ItemNotFoundException, RepositoryException; /** * Creates a node of type <code>nt:query</code> holding this query at * <code>absPath</code> and returns that node. * <p> * This is a session-write method and therefore requires a * <code>Session.save()</code> to dispatch the change. * <p> * The <code>absPath</code> provided must not have an index on its final * element. If ordering is supported by the node type of the parent node * then the new node is appended to the end of the child node list. * <p> * An <code>ItemExistsException</code> will be thrown either immediately, on * dispatch or on persists, if an item at the specified path already exists * and same-name siblings are not allowed. Implementations may differ on * when this validation is performed. * <p> * A <code>PathNotFoundException</code> will be thrown either immediately, * on dispatch or on persists, if the specified path implies intermediary * nodes that do not exist. Implementations may differ on when this * validation is performed. * <p> * A <code>ConstraintViolationException</code>will be thrown either * immediately, on dispatch or on persists, if adding the node would violate * a node type or implementation-specific constraint or if an attempt is * made to add a node as the child of a property. Implementations may differ * on when this validation is performed. * <p> * A <code>VersionException</code> will be thrown either immediately, on * dispatch or on persists, if the node to which the new child is being * added is read-only due to a checked-in node. Implementations may differ * on when this validation is performed. * <p> * A <code>LockException</code> will be thrown either immediately, on * dispatch or on persists, if a lock prevents the addition of the node. * Implementations may differ on when this validation is performed. * * @param absPath absolute path the query should be stored at * @return the newly created node. * @throws ItemExistsException if an item at the specified path already * exists, same-name siblings are not allowed and this implementation * performs this validation immediately. * @throws PathNotFoundException if the specified path implies intermediary * <code>Node</code>s that do not exist or the last element of * <code>relPath</code> has an index, and this implementation performs this * validation immediately. * @throws ConstraintViolationException if a node type or * implementation-specific constraint is violated or if an attempt is made * to add a node as the child of a property and this implementation performs * this validation immediately. * @throws VersionException if the node to which the new child is being * added is read-only due to a checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the addition of the node and * this implementation performs this validation immediately. * @throws UnsupportedRepositoryOperationException * if persistent queries are * not supported. * @throws RepositoryException if another error occurs or if the * <code>absPath</code> provided has an index on its final element. */ public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, UnsupportedRepositoryOperationException, RepositoryException; /** * Binds the given <code>value</code> to the variable named * <code>varName</code>. * * @param varName name of variable in query * @param value value to bind * @throws IllegalArgumentException if <code>varName</code> is not a valid * variable in this query. * @throws javax.jcr.RepositoryException if an error occurs. * @since JCR 2.0 */ public void bindValue(String varName, Value value) throws IllegalArgumentException, RepositoryException; /** * Returns the names of the bind variables in this query. If this query does * not contains any bind variables then an empty array is returned. * * @return the names of the bind variables in this query. * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public String[] getBindVariableNames() throws RepositoryException; }
抽象类AbstractQueryImpl只有一个init抽象方法,显然是要求子类实现
/** * Defines common initialisation methods for all query implementations. */ public abstract class AbstractQueryImpl implements Query { /** * Initialises a query instance from a query string. * * @param sessionContext component context of the current session * @param handler the query handler of the search index. * @param statement the query statement. * @param language the syntax of the query statement. * @param node a nt:query node where the query was read from or * <code>null</code> if it is not a stored query. * @throws InvalidQueryException if the query statement is invalid according * to the specified <code>language</code>. */ public abstract void init( SessionContext sessionContext, QueryHandler handler, String statement, String language, Node node) throws InvalidQueryException; }
QueryImpl类的源码如下:
/** * Provides the default implementation for a JCR query. */ public class QueryImpl extends AbstractQueryImpl { /** * The logger instance for this class */ private static final Logger log = LoggerFactory.getLogger(QueryImpl.class); /** * Component context of the current session */ protected SessionContext sessionContext; /** * The query statement */ protected String statement; /** * The syntax of the query statement */ protected String language; /** * The actual query implementation that can be executed */ protected ExecutableQuery query; /** * The node where this query is persisted. Only set when this is a persisted * query. */ protected Node node; /** * The query handler for this query. */ protected QueryHandler handler; /** * Flag indicating whether this query is initialized. */ private boolean initialized = false; /** * The maximum result size */ protected long limit = -1; /** * The offset in the total result set */ protected long offset = 0; /** * @inheritDoc */ public void init( SessionContext sessionContext, QueryHandler handler, String statement, String language, Node node) throws InvalidQueryException { checkNotInitialized(); this.sessionContext = sessionContext; this.statement = statement; this.language = language; this.handler = handler; this.node = node; this.query = handler.createExecutableQuery(sessionContext, statement, language); setInitialized(); } /** * This method simply forwards the <code>execute</code> call to the * {@link ExecutableQuery} object returned by * {@link QueryHandler#createExecutableQuery}. * {@inheritDoc} */ public QueryResult execute() throws RepositoryException { checkInitialized(); long time = System.currentTimeMillis(); QueryResult result = sessionContext.getSessionState().perform( new SessionOperation<QueryResult>() { public QueryResult perform(SessionContext context) throws RepositoryException { return query.execute(offset, limit); } public String toString() { return "query.execute(" + statement + ")"; } }); if (log.isDebugEnabled()) { time = System.currentTimeMillis() - time; NumberFormat format = NumberFormat.getNumberInstance(); format.setMinimumFractionDigits(2); format.setMaximumFractionDigits(2); String seconds = format.format((double) time / 1000); log.debug("executed in " + seconds + " s. (" + statement + ")"); } return result; } /** * {@inheritDoc} */ public String getStatement() { checkInitialized(); return statement; } /** * {@inheritDoc} */ public String getLanguage() { checkInitialized(); return language; } /** * {@inheritDoc} */ public String getStoredQueryPath() throws ItemNotFoundException, RepositoryException { checkInitialized(); if (node == null) { throw new ItemNotFoundException("not a persistent query"); } return node.getPath(); } /** * {@inheritDoc} */ public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, UnsupportedRepositoryOperationException, RepositoryException { checkInitialized(); try { Path p = sessionContext.getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException(absPath + " is not an absolute path"); } String relPath = sessionContext.getJCRPath(p).substring(1); Node queryNode = sessionContext.getSessionImpl().getRootNode().addNode( relPath, sessionContext.getJCRName(NT_QUERY)); // set properties queryNode.setProperty(sessionContext.getJCRName(JCR_LANGUAGE), language); queryNode.setProperty(sessionContext.getJCRName(JCR_STATEMENT), statement); node = queryNode; return node; } catch (NameException e) { throw new RepositoryException(e.getMessage(), e); } } /** * {@inheritDoc} */ public String[] getBindVariableNames() { return new String[0]; } /** * Throws an {@link IllegalArgumentException} as XPath and SQL1 queries * have no bind variables. * * @throws IllegalArgumentException always thrown */ public void bindValue(String varName, Value value) throws IllegalArgumentException { throw new IllegalArgumentException("No such bind variable: " + varName); } /** * Sets the maximum size of the result set. * * @param limit new maximum size of the result set */ public void setLimit(long limit) { if (limit < 0) { throw new IllegalArgumentException("limit must not be negativ"); } this.limit = limit; } /** * Sets the start offset of the result set. * * @param offset new start offset of the result set */ public void setOffset(long offset) { if (offset < 0) { throw new IllegalArgumentException("offset must not be negativ"); } this.offset = offset; } //-----------------------------< internal >--------------------------------- /** * Sets the initialized flag. */ protected void setInitialized() { initialized = true; } /** * Checks if this query is not yet initialized and throws an * <code>IllegalStateException</code> if it is already initialized. */ protected void checkNotInitialized() { if (initialized) { throw new IllegalStateException("already initialized"); } } /** * Checks if this query is initialized and throws an * <code>IllegalStateException</code> if it is not yet initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("not initialized"); } } }
先看一下它的初始化方法
/** * @inheritDoc */ public void init( SessionContext sessionContext, QueryHandler handler, String statement, String language, Node node) throws InvalidQueryException { checkNotInitialized(); this.sessionContext = sessionContext; this.statement = statement; this.language = language; this.handler = handler; this.node = node; this.query = handler.createExecutableQuery(sessionContext, statement, language); setInitialized(); }
这里的handler还是SearchManager初始化该类时传过来的SearchIndex类型的对象,我们从这里可以看到,ExecutableQuery类型的query成员变量时通过handler(SearchIndex类型)创建的
/** * The actual query implementation that can be executed */ protected ExecutableQuery query;
QueryImpl类的真正检索方法如下:
/** * This method simply forwards the <code>execute</code> call to the * {@link ExecutableQuery} object returned by * {@link QueryHandler#createExecutableQuery}. * {@inheritDoc} */ public QueryResult execute() throws RepositoryException { checkInitialized(); long time = System.currentTimeMillis(); QueryResult result = sessionContext.getSessionState().perform( new SessionOperation<QueryResult>() { public QueryResult perform(SessionContext context) throws RepositoryException { return query.execute(offset, limit); } public String toString() { return "query.execute(" + statement + ")"; } }); if (log.isDebugEnabled()) { time = System.currentTimeMillis() - time; NumberFormat format = NumberFormat.getNumberInstance(); format.setMinimumFractionDigits(2); format.setMaximumFractionDigits(2); String seconds = format.format((double) time / 1000); log.debug("executed in " + seconds + " s. (" + statement + ")"); } return result; }
我们可以看到是调用ExecutableQuery query成员变量的execute方法
现在回顾头来查看一下SearchIndex的createExecutableQuery方法
/** * Creates a new query by specifying the query statement itself and the * language in which the query is stated. If the query statement is * syntactically invalid, given the language specified, an * InvalidQueryException is thrown. <code>language</code> must specify a query language * string from among those returned by QueryManager.getSupportedQueryLanguages(); if it is not * then an <code>InvalidQueryException</code> is thrown. * * @param sessionContext component context of the current session * @param statement the query statement. * @param language the syntax of the query statement. * @throws InvalidQueryException if statement is invalid or language is unsupported. * @return A <code>Query</code> object. */ public ExecutableQuery createExecutableQuery( SessionContext sessionContext, String statement, String language) throws InvalidQueryException { QueryImpl query = new QueryImpl( sessionContext, this, getContext().getPropertyTypeRegistry(), statement, language, getQueryNodeFactory()); query.setRespectDocumentOrder(documentOrder); return query; }
可以看到,该方法实际返回的是org.apache.jackrabbit.core.query.lucene.QueryImpl类型的对象
org.apache.jackrabbit.core.query.lucene.QueryImpl类继承自抽象类org.apache.jackrabbit.core.query.lucene.AbstractQueryImpl,而抽象类org.apache.jackrabbit.core.query.lucene.AbstractQueryImpl实现了org.apache.jackrabbit.core.query.ExecutableQuery接口
(这里面命名与org.apache.jackrabbit.core.query包命名相同,容易使人混淆)
ExecutableQuery接口的源码如下:
/** * Specifies an interface for a query object implementation that can just be * executed. * @see QueryImpl */ public interface ExecutableQuery { /** * 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 */ QueryResult execute(long offset, long limit) throws RepositoryException; }
抽象类org.apache.jackrabbit.core.query.lucene.AbstractQueryImpl源码如下:
/** * <code>AbstractQueryImpl</code> provides a base class for executable queries * based on {@link SearchIndex}. */ public abstract class AbstractQueryImpl implements ExecutableQuery { /** * Component context of the current session */ protected final SessionContext sessionContext; /** * The actual search index */ protected final SearchIndex index; /** * The property type registry for type lookup. */ protected final PropertyTypeRegistry propReg; /** * If <code>true</code> the default ordering of the result nodes is in * document order. */ private boolean documentOrder = true; protected final PerQueryCache cache = new PerQueryCache(); /** * 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. */ public AbstractQueryImpl( SessionContext sessionContext, SearchIndex index, PropertyTypeRegistry propReg) { this.sessionContext = sessionContext; this.index = index; this.propReg = propReg; } /** * If set <code>true</code> the result nodes will be in document order * per default (if no order by clause is specified). If set to * <code>false</code> the result nodes are returned in whatever sequence * the index has stored the nodes. That sequence is stable over multiple * invocations of the same query, but will change when nodes get added or * removed from the index. * <p/> * The default value for this property is <code>true</code>. * @return the current value of this property. */ public boolean getRespectDocumentOrder() { return documentOrder; } /** * Sets a new value for this property. * * @param documentOrder if <code>true</code> the result nodes are in * document order per default. * * @see #getRespectDocumentOrder() */ public void setRespectDocumentOrder(boolean documentOrder) { this.documentOrder = documentOrder; } /** * @return the query object model factory. * @throws RepositoryException if an error occurs. */ protected QueryObjectModelFactory getQOMFactory() throws RepositoryException { Workspace workspace = sessionContext.getSessionImpl().getWorkspace(); return workspace.getQueryManager().getQOMFactory(); } /** * 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 abstract boolean needsSystemTree(); }
org.apache.jackrabbit.core.query.lucene.QueryImpl类的源码如下:
/** * 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]; for (int i = 0; i < orderSpecs.length; i++) { orderProperties[i] = orderSpecs[i].getPropertyPath(); ascSpecs[i] = orderSpecs[i].isAscending(); } return new SingleColumnQueryResult( index, sessionContext, this, query, new SpellSuggestion(index.getSpellChecker(), root), getColumns(), orderProperties, ascSpecs, 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); } }
最重要的查询方法是
/** * 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]; for (int i = 0; i < orderSpecs.length; i++) { orderProperties[i] = orderSpecs[i].getPropertyPath(); ascSpecs[i] = orderSpecs[i].isAscending(); } return new SingleColumnQueryResult( index, sessionContext, this, query, new SpellSuggestion(index.getSpellChecker(), root), getColumns(), orderProperties, ascSpecs, orderProperties.length == 0 && getRespectDocumentOrder(), offset, limit); }
---------------------------------------------------------------------------
本系列Apache Jackrabbit源码研究系本人原创
转载请注明出处 博客园 刺猬的温驯
本文链接 http://www.cnblogs.com/chenying99/archive/2013/04/07/3003304.html