This project has retired. For details please refer to its
Attic page.
QueryTranslator xref
1/*2 * Licensed to the Apache Software Foundation (ASF) under one3 * or more contributor license agreements. See the NOTICE file4 * distributed with this work for additional information5 * regarding copyright ownership. The ASF licenses this file6 * to you under the Apache License, Version 2.0 (the7 * "License"); you may not use this file except in compliance8 * with the License. You may obtain a copy of the License at9 *10 * http://www.apache.org/licenses/LICENSE-2.011 *12 * Unless required by applicable law or agreed to in writing,13 * software distributed under the License is distributed on an14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY15 * KIND, either express or implied. See the License for the16 * specific language governing permissions and limitations17 * under the License.18 */1920package org.apache.chemistry.opencmis.jcr.query;
2122import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
23import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
24import org.apache.chemistry.opencmis.jcr.JcrTypeManager;
25import org.apache.chemistry.opencmis.server.support.query.CmisQueryWalker;
26import org.apache.chemistry.opencmis.server.support.query.QueryObject;
27import org.apache.chemistry.opencmis.server.support.query.QueryObject.SortSpec;
28import org.apache.chemistry.opencmis.server.support.query.QueryUtil;
2930import java.util.List;
3132/**33 * Abstract base class for translating a CMIS query statement to a JCR XPath34 * query statement.35 * Overriding class need to implement methods for mapping CMIS ids to JCR paths,36 * CMIS property names to JCR property names, CMIS type names to JCR type name and37 * in addition a method for adding further constraints to a query based on a CMIS38 * type. 39 */40publicabstractclassQueryTranslator {
41privatefinalJcrTypeManager typeManager;
42privatefinalEvaluatorXPath evaluator;
43private QueryObject queryObject;
4445/**46 * Create a new query translator which uses the provided <code>typeManager</code>47 * to resolve CMIS type names to CMIS types.48 * 49 * @param typeManager50 */51protectedQueryTranslator(JcrTypeManager typeManager) {
52this.typeManager = typeManager;
53 evaluator = newEvaluatorXPath() {
5455 @Override
56protected String jcrPathFromId(String id) {
57return QueryTranslator.this.jcrPathFromId(id);
58 }
5960 @Override
61protected String jcrPathFromCol(String name) {
62return QueryTranslator.this.jcrPathFromCol(queryObject.getMainFromName(), name);
63 }
64 };
65 }
6667/**68 * @return the {@link QueryObject} from the last translation performed through69 * {@link QueryTranslator#translateToXPath(String)}.70 */71public QueryObject getQueryObject() {
72return queryObject;
73 }
7475/**76 * Translate a CMIS query statement to a JCR XPath query statement.77 * 78 * @param statement79 * @return80 */81public String translateToXPath(String statement) {
82 QueryUtil queryUtil = new QueryUtil();
83 queryObject = new QueryObject(typeManager);
84 ParseTreeWalker<XPathBuilder> parseTreeWalker = new ParseTreeWalker<XPathBuilder>(evaluator);
85 CmisQueryWalker walker = queryUtil.traverseStatementAndCatchExc(statement, queryObject, parseTreeWalker);
86 walker.setDoFullTextParse(false);
87XPathBuilder parseResult = parseTreeWalker.getResult();
88 TypeDefinition fromType = getFromName(queryObject);
8990 String pathExpression = buildPathExpression(fromType, getFolderPredicate(parseResult));
91 String elementTest = buildElementTest(fromType);
92 String predicates = buildPredicates(fromType, getCondition(parseResult));
93 String orderByClause = buildOrderByClause(fromType, queryObject.getOrderBys());
94return"/jcr:root" + pathExpression + elementTest + predicates + orderByClause;
95 }
9697//------------------------------------------< protected >---9899/**100 * Map a CMIS objectId to an absolute JCR path. This method is called to101 * resolve the folder if of folder predicates (i.e. IN_FOLDER, IN_TREE).102 *103 * @param id objectId104 * @return absolute JCR path corresponding to <code>id</code>.105 */106protectedabstract String jcrPathFromId(String id);
107108/**109 * Map a column name in the CMIS query to the corresponding relative JCR path.110 * The path must be relative to the context node.111 *112 * @param fromType Type on which the CMIS query is performed113 * @param name column name114 * @return relative JCR path 115 */116protectedabstract String jcrPathFromCol(TypeDefinition fromType, String name);
117118/**119 * Map a CMIS type to the corresponding JCR type name.120 * @see #jcrTypeCondition(TypeDefinition)121 *122 * @param fromType CMIS type123 * @return name of the JCR type corresponding to <code>fromType</code>124 */125protectedabstract String jcrTypeName(TypeDefinition fromType);
126127/**128 * Create and additional condition in order for the query to only return nodes129 * of the right type. This condition and-ed to the condition determined by the130 * CMIS query's where clause.131 * <p/>132 * A CMIS query for non versionable documents should for example result in the133 * following XPath query:134 * <p/>135 * <pre>136 * element(*, nt:file)[not(@jcr:mixinTypes = 'mix:simpleVersionable')]137 * </pre>138 * Here the element test is covered by {@link #jcrTypeName(TypeDefinition)}139 * while the predicate is covered by this method. 140 *141 * @see #jcrTypeName(TypeDefinition)142 *143 * @param fromType144 * @return Additional condition or <code>null</code> if none. 145 */146protectedabstract String jcrTypeCondition(TypeDefinition fromType);
147148/**149 * Build a XPath path expression for the CMIS type queried for and a folder predicate.150 *151 * @param fromType CMIS type queried for152 * @param folderPredicate folder predicate153 * @return a valid XPath path expression or <code>null</code> if none.154 */155protected String buildPathExpression(TypeDefinition fromType, String folderPredicate) {
156return folderPredicate == null ? "//" : folderPredicate;
157 }
158159/**160 * Build a XPath element test for the given CMIS type.161 *162 * @param fromType CMIS type queried for163 * @return a valid XPath element test. 164 */165protected String buildElementTest(TypeDefinition fromType) {
166return"element(*," + jcrTypeName(fromType) + ")";
167 }
168169/**170 * Build a XPath predicate for the given CMIS type and an additional condition.171 * The additional condition should be and-ed to the condition resulting from172 * evaluating <code>fromType</code>.173 *174 * @param fromType CMIS type queried for175 * @param condition additional condition.176 * @return a valid XPath predicate or <code>null</code> if none. 177 */178protected String buildPredicates(TypeDefinition fromType, String condition) {
179 String typeCondition = jcrTypeCondition(fromType);
180181if (typeCondition == null) {
182return condition == null ? "" : "[" + condition + "]";
183 }
184elseif (condition == null) {
185return"[" + typeCondition + "]";
186 }
187else {
188return"[" + typeCondition + " and " + condition + "]";
189 }
190 }
191192/**193 * Build a XPath order by clause for the given CMIS type and a list of {@link SortSpec}s.194 *195 * @param fromType CMIS type queried for196 * @param orderBys <code>SortSpec</code>s197 * @return a valid XPath order by clause 198 */199protected String buildOrderByClause(TypeDefinition fromType, List<SortSpec> orderBys) {
200 StringBuilder orderSpecs = new StringBuilder();
201202for (SortSpec orderBy : orderBys) {
203 String selector = jcrPathFromCol(fromType, orderBy.getSelector().getName());
204boolean ascending = orderBy.isAscending();
205206if (orderSpecs.length() > 0) {
207 orderSpecs.append(',');
208 }
209210 orderSpecs
211 .append(selector)
212 .append(' ')
213 .append(ascending ? "ascending" : "descending");
214 }
215216return orderSpecs.length() > 0
217 ? "order by " + orderSpecs
218 : "";
219 }
220221//------------------------------------------< private >---222223privatestatic String getFolderPredicate(XPathBuilder parseResult) {
224if (parseResult == null) {
225returnnull;
226 }
227228 String folderPredicate = null;
229for (XPathBuilder p : parseResult.folderPredicates()) {
230if (folderPredicate == null) {
231 folderPredicate = p.xPath();
232 }
233else {
234thrownew CmisInvalidArgumentException("Query may only contain a single folder predicate");
235 }
236 }
237238// See the class comment on XPathBuilder for details on affirmative literals239if (folderPredicate != null && // IF has single folder predicate240 !Boolean.FALSE.equals(parseResult.eval(false))) { // AND folder predicate is not affirmative241thrownew CmisInvalidArgumentException("Folder predicate " + folderPredicate + " is not affirmative.");
242 }
243244return folderPredicate;
245 }
246247privatestatic TypeDefinition getFromName(QueryObject queryObject) {
248if (queryObject.getTypes().size() != 1) {
249thrownew CmisInvalidArgumentException("From must contain one single reference");
250 }
251return queryObject.getMainFromName();
252 }
253254privatestatic String getCondition(XPathBuilder parseResult) {
255// No condition if either parseResult is null or when it evaluates to true under256// the valuation which assigns true to the folder predicate.257return parseResult == null || Boolean.TRUE.equals(parseResult.eval(true)) ? null : parseResult.xPath();
258 }
259260 }