|
上文最后提到jackrabbit的检索默认实现类QueryImpl,先熟悉一下该类的继承层次
QueryImpl继承自抽象类AbstractQueryImpl,而抽象类实现了Query接口(JCR的接口)
Query接口源码如下:
/**
* A Query 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 {@link QueryResult}
* object.
*
* If this Query 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 InvalidQueryException.
*
* @return a QueryResult 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 limit.
*
* @param limit a long
* @since JCR 2.0
*/
public void setLimit(long limit);
/**
* Sets the start offset of the result set to offset.
*
* @param offset a long
* @since JCR 2.0
*/
public void setOffset(long offset);
/**
* Returns the statement defined for this query.
*
* 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.
*
* 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
* jcr:statement 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 Query object that has been stored using {@link
* Query#storeAsNode} (regardless of whether it has been saved
* yet) or retrieved using {@link QueryManager#getQuery}), then this method
* returns the path of the nt:query 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 nt:query holding this query at
* absPath and returns that node.
*
* This is a session-write method and therefore requires a
* Session.save() to dispatch the change.
*
* The absPath 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.
*
* An ItemExistsException 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.
*
* A PathNotFoundException 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.
*
* A ConstraintViolationExceptionwill 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.
*
* A VersionException 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.
*
* A LockException 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
* Nodes that do not exist or the last element of
* relPath 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
* absPath provided has an index on its final element.
*/
public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, UnsupportedRepositoryOperationException, RepositoryException;
/**
* Binds the given value to the variable named
* varName.
*
* @param varName name of variable in query
* @param value value to bind
* @throws IllegalArgumentException if varName 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
* null if it is not a stored query.
* @throws InvalidQueryException if the query statement is invalid according
* to the specified language.
*/
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 execute 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() {
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
* IllegalStateException if it is already initialized.
*/
protected void checkNotInitialized() {
if (initialized) {
throw new IllegalStateException("already initialized");
}
}
/**
* Checks if this query is initialized and throws an
* IllegalStateException 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 execute 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() {
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. language must specify a query language
* string from among those returned by QueryManager.getSupportedQueryLanguages(); if it is not
* then an InvalidQueryException 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 Query 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 {@link QueryResult}.
* @param offset the offset in the total result set
* @param limit the maximum result size
*
* @return a QueryResult
* @throws RepositoryException if an error occurs
*/
QueryResult execute(long offset, long limit) throws RepositoryException;
}
抽象类org.apache.jackrabbit.core.query.lucene.AbstractQueryImpl源码如下:
/**
* AbstractQueryImpl 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 true 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 true the result nodes will be in document order
* per default (if no order by clause is specified). If set to
* false 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.
*
* The default value for this property is true.
* @return the current value of this property.
*/
public boolean getRespectDocumentOrder() {
return documentOrder;
}
/**
* Sets a new value for this property.
*
* @param documentOrder if true 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 true if this query node needs items under
* /jcr:system to be queried.
*
* @return true if this query node needs content under
* /jcr:system to be queried; false 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 language.
*/
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 {@link QueryResult}.
*
* @param offset the offset in the total result set
* @param limit the maximum result size
* @return a QueryResult
* @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 = orderSpecs.getPropertyPath();
ascSpecs = orderSpecs.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 columns = new LinkedHashMap();
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 true if this query node needs items under
* /jcr:system to be queried.
*
* @return true if this query node needs content under
* /jcr:system to be queried; false 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 {@link QueryResult}.
*
* @param offset the offset in the total result set
* @param limit the maximum result size
* @return a QueryResult
* @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 = orderSpecs.getPropertyPath();
ascSpecs = orderSpecs.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.iyunv.com/chenying99/archive/2013/04/07/3003304.html |
|