This project has retired. For details please refer to its
Attic page.
QueryObject 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 */19package org.apache.chemistry.opencmis.server.support.query;
2021import java.util.ArrayList;
22import java.util.Collections;
23import java.util.HashMap;
24import java.util.LinkedHashMap;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
28import java.util.Map.Entry;
2930import org.antlr.runtime.tree.Tree;
31import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
32import org.apache.chemistry.opencmis.server.support.TypeManager;
33import org.apache.chemistry.opencmis.server.support.TypeValidator;
34import org.apache.commons.logging.Log;
35import org.apache.commons.logging.LogFactory;
363738/**39 * QueryObject is a class used to encapsulate a CMIS query. It is created from40 * an ANTLR parser on an incoming query string. During parsing various41 * informations are collected and stored in objects suitable for evaluating the42 * query (like selected properties, effected types and order statements. A query43 * evaluator can use this information to perform the query and build the result.44 */45publicclassQueryObject {
4647privatestaticfinal Log LOG = LogFactory.getLog(QueryObject.class);
4849// For error handling see:50// http://www.antlr.org/pipermail/antlr-interest/2008-April/027600.html51// select part52protectedTypeManager typeMgr;
53protectedfinal List<CmisSelector> selectReferences = new ArrayList<CmisSelector>();
54protectedfinal List<CmisSelector> whereReferences = new ArrayList<CmisSelector>();
55protectedfinal List<CmisSelector> joinReferences = new ArrayList<CmisSelector>();
56// --> Join not implemented yet57protectedfinal Map<String, CmisSelector> colOrFuncAlias = new HashMap<String, CmisSelector>();
5859// from part60/** map from alias name to type query name */61protectedfinal Map<String, String> froms = new LinkedHashMap<String, String>();
6263/** main from alias name */64protected String from = null;
6566protectedfinal List<JoinSpec> joinSpecs = new LinkedList<JoinSpec>();
6768// where part69protectedfinal Map<Integer, CmisSelector> columnReferences = new HashMap<Integer, CmisSelector>();
70protectedfinal Map<Integer, String> typeReferences = new HashMap<Integer, String>();
7172// order by part73protectedfinal List<SortSpec> sortSpecs = new ArrayList<SortSpec>();
7475private String errorMessage;
7677publicstaticclassJoinSpec {
7879/** INNER / LEFT / RIGHT */80publicfinal String kind;
8182/** Alias or full table type */83publicfinal String alias;
8485publicColumnReference onLeft;
8687publicColumnReference onRight;
8889publicJoinSpec(String kind, String alias) {
90this.kind = kind;
91this.alias = alias;
92 }
9394publicvoid setSelectors(ColumnReference onLeft, ColumnReference onRight) {
95this.onLeft = onLeft;
96this.onRight = onRight;
97 }
9899 @Override
100public String toString() {
101return"JoinReference(" + kind + "," + alias + "," + onLeft + ","102 + onRight + ")";
103 }
104 }
105106publicclassSortSpec {
107publicfinalboolean ascending;
108publicfinal Integer colRefKey; // key in columnReferencesMap point to column109// descriptions110111publicSortSpec(Integer key, boolean ascending) {
112this.colRefKey = key;
113this.ascending = ascending;
114 }
115116publicCmisSelector getSelector() {
117return columnReferences.get(colRefKey);
118 }
119120publicboolean isAscending() {
121return ascending;
122 }
123 }
124125publicQueryObject() {
126 }
127128publicQueryObject(TypeManager tm) {
129 typeMgr = tm;
130 }
131132public Map<Integer, CmisSelector> getColumnReferences() {
133return Collections.unmodifiableMap(columnReferences);
134 }
135136publicCmisSelector getColumnReference(Integer token) {
137return columnReferences.get(token);
138 }
139140public String getTypeReference(Integer token) {
141return typeReferences.get(token);
142 }
143144public String getErrorMessage() {
145return errorMessage;
146 }
147148// ///////////////////////////////////////////////////////149// SELECT part150151// public accessor methods152public List<CmisSelector> getSelectReferences() {
153return selectReferences;
154 }
155156publicvoid addSelectReference(Tree node, CmisSelector selRef) {
157 selectReferences.add(selRef);
158 columnReferences.put(node.getTokenStartIndex(), selRef);
159 }
160161publicvoid addAlias(String aliasName, CmisSelector aliasRef) {
162 LOG.debug("add alias: " + aliasName + " for: " + aliasRef);
163if (colOrFuncAlias.containsKey(aliasName)) {
164thrownewCmisQueryException("You cannot use name " + aliasName
165 + " more than once as alias in a select.");
166 } else {
167 aliasRef.setAliasName(aliasName);
168 colOrFuncAlias.put(aliasName, aliasRef);
169 }
170 }
171172publicCmisSelector getSelectAlias(String aliasName) {
173return colOrFuncAlias.get(aliasName);
174 }
175176// ///////////////////////////////////////////////////////177// FROM part178179public String addType(String aliasName, String typeQueryName) {
180try {
181 LOG.debug("add alias: " + aliasName + " for: " + typeQueryName);
182if (froms.containsKey(aliasName)) {
183thrownewCmisQueryException("You cannot use name " + aliasName
184 + " more than once as alias in a from part.");
185 }
186if (aliasName == null) {
187 aliasName = typeQueryName;
188 }
189 froms.put(aliasName, typeQueryName);
190if (from == null) {
191 from = aliasName;
192 }
193return aliasName;
194 } catch (CmisQueryException cqe) {
195 errorMessage = cqe.getMessage(); // preserve message196returnnull; // indicate an error to ANTLR so that it generates FailedPredicateException197 }
198 }
199200public String getMainTypeAlias() {
201return from;
202 }
203204public Map<String, String> getTypes() {
205return Collections.unmodifiableMap(froms);
206 }
207208public String getTypeQueryName(String qualifier) {
209return froms.get(qualifier);
210 }
211212public TypeDefinition getTypeDefinitionFromQueryName(String queryName) {
213return typeMgr.getTypeByQueryName(queryName);
214 }
215216public TypeDefinition getParentType(TypeDefinition td) {
217 String parentType = td.getParentTypeId();
218return parentType == null ? null : typeMgr.getTypeById(parentType).getTypeDefinition();
219 }
220221public TypeDefinition getParentType(String typeId) {
222 TypeDefinition td = typeMgr.getTypeById(typeId).getTypeDefinition();
223 String parentType = td == null ? null : td.getParentTypeId();
224return parentType == null ? null : typeMgr.getTypeById(parentType).getTypeDefinition();
225 }
226227public TypeDefinition getMainFromName() {
228// as we don't support JOINS take first type229 String queryName = froms.values().iterator().next();
230 TypeDefinition td = getTypeDefinitionFromQueryName(queryName);
231return td;
232 }
233234/**235 * return a map of all columns that have been requested in the SELECT part236 * of the statement.237 *238 * @return a map with a String as a key and value. key is the alias if 239 * an alias was given or the query name otherwise. value is the query 240 * name of the property.241 */242public Map<String, String> getRequestedPropertiesByAlias() {
243return getRequestedProperties(true);
244 }
245246/**247 * return a map of all columns that have been requested in the SELECT part248 * of the statement.249 *250 * @return a map with a String as a key and value. key is the query name of251 * the property, value is the alias if an alias was given or the252 * query name otherwise.253 * 254 * @deprecated Use getRequestedPropertiesByAlias instead.255 */256 @Deprecated
257public Map<String, String> getRequestedProperties() {
258return getRequestedProperties(false);
259 }
260261private Map<String, String> getRequestedProperties(boolean byAlias) {
262263 Map<String, String> res = new HashMap<String, String>();
264for (CmisSelector sel : selectReferences) {
265if (sel instanceof ColumnReference) {
266ColumnReference colRef = (ColumnReference) sel;
267 String key = colRef.getPropertyId();
268if (null == key)
269 {
270 key = colRef.getPropertyQueryName(); // happens for *271 }
272 String propDescr = colRef.getAliasName() == null ? colRef.getPropertyQueryName() : colRef
273 .getAliasName();
274if (byAlias)
275 res.put(propDescr, key);
276else277 res.put(key, propDescr);
278 }
279 }
280return res;
281 }
282283/**284 * return a map of all functions that have been requested in the SELECT part285 * of the statement.286 *287 * @return a map with a String as a key and value. key is the function name288 * of the property, value is the alias if an alias was given or the289 * function name otherwise.290 * 291 * @deprecated Use getRequestedPropertiesByAlias instead.292 */293 @Deprecated
294public Map<String, String> getRequestedFuncs() {
295return getRequestedFuncs(false);
296 }
297298/**299 * return a map of all functions that have been requested in the SELECT part300 * of the statement.301 *302 * @return a map with a String as a key and value. key is the alias if an 303 * alias was given or the function name otherwise, value is the a name304 * of the property. 305 */306public Map<String, String> getRequestedFuncsByAlias() {
307return getRequestedFuncs(true);
308 }
309310private Map<String, String> getRequestedFuncs(boolean byAlias) {
311312 Map<String, String> res = new HashMap<String, String>();
313for (CmisSelector sel : selectReferences) {
314if (sel instanceof FunctionReference) {
315FunctionReference funcRef = (FunctionReference) sel;
316 String propDescr = funcRef.getAliasName() == null ? funcRef.getName() : funcRef.getAliasName();
317if (byAlias)
318 res.put(propDescr, funcRef.getName());
319else320 res.put(funcRef.getName(), propDescr);
321 }
322 }
323return res;
324 }
325326// ///////////////////////////////////////////////////////327// JOINS328329publicvoid addJoinReference(Tree node, CmisSelector reference) {
330 columnReferences.put(node.getTokenStartIndex(), reference);
331 joinReferences.add(reference);
332 }
333334public List<CmisSelector> getJoinReferences() {
335return Collections.unmodifiableList(joinReferences);
336 }
337338publicvoid addJoin(String kind, String alias, boolean hasSpec) {
339JoinSpec join = newJoinSpec(kind, alias);
340if (hasSpec) {
341// get columns from last added references342int n = joinReferences.size();
343ColumnReference onLeft = (ColumnReference) joinReferences.get(n - 2);
344ColumnReference onRight = (ColumnReference) joinReferences.get(n - 1);
345 join.setSelectors(onLeft, onRight);
346 }
347 joinSpecs.add(join);
348 }
349350public List<JoinSpec> getJoins() {
351return joinSpecs;
352 }
353354// ///////////////////////////////////////////////////////355// WHERE part356357358publicvoid addWhereReference(Tree node, CmisSelector reference) {
359 LOG.debug("add node to where: " + System.identityHashCode(node));
360 columnReferences.put(node.getTokenStartIndex(), reference);
361 whereReferences.add(reference);
362 }
363364public List<CmisSelector> getWhereReferences() {
365return Collections.unmodifiableList(whereReferences);
366 }
367368publicvoid addWhereTypeReference(Tree node, String qualifier) {
369if (node != null) {
370 typeReferences.put(node.getTokenStartIndex(), qualifier);
371 }
372 }
373374// ///////////////////////////////////////////////////////375// ORDER_BY part376377public List<SortSpec> getOrderBys() {
378return Collections.unmodifiableList(sortSpecs);
379 }
380381publicvoid addSortCriterium(Tree node, ColumnReference colRef, boolean ascending) {
382 LOG.debug("addSortCriterium: " + colRef + " ascending: " + ascending);
383 columnReferences.put(node.getTokenStartIndex(), colRef);
384 sortSpecs.add(newSortSpec(node.getTokenStartIndex(), ascending));
385 }
386387// ///////////////////////////////////////////////////////388// resolve types after first pass traversing the AST is complete389390publicboolean resolveTypes() {
391try {
392 LOG.debug("First pass of query traversal is complete, resolving types");
393if (null == typeMgr) {
394returntrue;
395 }
396397// First resolve all alias names defined in SELECT:398for (CmisSelector alias : colOrFuncAlias.values()) {
399if (alias instanceof ColumnReference) {
400ColumnReference colRef = ((ColumnReference) alias);
401 resolveTypeForAlias(colRef);
402 }
403 }
404405// Then replace all aliases used somewhere by their resolved column406// reference:407for (Integer obj : columnReferences.keySet()) {
408CmisSelector selector = columnReferences.get(obj);
409 String key = selector.getName();
410if (colOrFuncAlias.containsKey(key)) { // it is an alias411CmisSelector resolvedReference = colOrFuncAlias.get(key);
412 columnReferences.put(obj, resolvedReference);
413// Note: ^ This may replace the value in the map with the same414// value, but this does not harm.415// Otherwise we need to check if it is resolved or not which416// causes two more ifs:417// if (selector instanceof ColumnReference) {418// ColumnReference colRef = ((ColumnReference) selector);419// if (colRef.getTypeDefinition() == null) // it is not yet420// resolved421// // replace unresolved column reference by resolved on from422// alias map423// columnReferences.put(obj,424// colOrFuncAlias.get(selector.getAliasName()));425// } else426// columnReferences.put(obj,427// colOrFuncAlias.get(selector.getAliasName()));428if (whereReferences.remove(selector)) {
429// replace unresolved by resolved reference430 whereReferences.add(resolvedReference);
431 }
432if (joinReferences.remove(selector)) {
433// replace unresolved by resolved reference434 joinReferences.add(resolvedReference);
435 }
436 }
437 }
438439// The replace all remaining column references not using an alias440for (CmisSelector select : columnReferences.values()) {
441// ignore functions here442if (select instanceof ColumnReference) {
443ColumnReference colRef = ((ColumnReference) select);
444if (colRef.getTypeDefinition() == null) { // not yet resolved445if (colRef.getQualifier() == null) {
446// unqualified select: SELECT p FROM447 resolveTypeForColumnReference(colRef);
448 } else {
449// qualified select: SELECT t.p FROM450 validateColumnReferenceAndResolveType(colRef);
451 }
452 }
453 }
454 }
455456// Replace types used as qualifiers (IN_TREE, IN_FOLDER,457// CONTAINS) by their corresponding alias (correlation name)458for (Entry<Integer, String> en: typeReferences.entrySet()) {
459 Integer obj = en.getKey();
460 String qualifier = en.getValue();
461 String typeQueryName = getReferencedTypeQueryName(qualifier);
462if (typeQueryName == null) {
463thrownewCmisQueryException(qualifier
464 + " is neither a type query name nor an alias.");
465 }
466if (typeQueryName.equals(qualifier)) {
467// try to find an alias for it468 String alias = null;
469for (Entry<String, String> e : froms.entrySet()) {
470 String q = e.getKey();
471 String tqn = e.getValue();
472if (!tqn.equals(q) && typeQueryName.equals(tqn)) {
473 alias = q;
474break;
475 }
476 }
477if (alias != null) {
478 typeReferences.put(obj, alias);
479 }
480 }
481 }
482483returntrue;
484 } catch (CmisQueryException cqe) {
485 errorMessage = cqe.getMessage(); // preserve message486return false; // indicate an error to ANTLR so that it generates FailedPredicateException487 }
488 }
489490protectedvoid resolveTypeForAlias(ColumnReference colRef) {
491 String aliasName = colRef.getAliasName();
492493if (colOrFuncAlias.containsKey(aliasName)) {
494CmisSelector selector = colOrFuncAlias.get(aliasName);
495if (selector instanceof ColumnReference) {
496 colRef = (ColumnReference) selector; // alias target497if (colRef.getQualifier() == null) {
498// unqualified select: SELECT p FROM499 resolveTypeForColumnReference(colRef);
500 } else {
501// qualified select: SELECT t.p FROM502 validateColumnReferenceAndResolveType(colRef);
503 }
504 }
505// else --> ignore FunctionReference506 }
507 }
508509// for a select x from y, z ... find the type in type manager for x510protectedvoid resolveTypeForColumnReference(ColumnReference colRef) {
511 String propName = colRef.getPropertyQueryName();
512boolean isStar = propName.equals("*");
513514// it is property query name without a type, so find type515int noFound = 0;
516 TypeDefinition tdFound = null;
517for (String typeQueryName : froms.values()) {
518 TypeDefinition td = typeMgr.getTypeByQueryName(typeQueryName);
519if (null == td) {
520thrownewCmisQueryException(typeQueryName + " is neither a type query name nor an alias.");
521 } elseif (isStar) {
522 ++noFound;
523 tdFound = null;
524 } elseif (TypeValidator.typeContainsPropertyWithQueryName(td, propName)) {
525 ++noFound;
526 tdFound = td;
527 }
528 }
529if (noFound == 0) {
530thrownewCmisQueryException(propName
531 + " is not a property query name in any of the types in from ...");
532 } elseif (noFound > 1 && !isStar) {
533thrownewCmisQueryException(propName
534 + " is not a unique property query name within the types in from ...");
535 } else {
536if (null != tdFound) {
537 validateColumnReferenceAndResolveType(tdFound, colRef);
538 }
539 }
540 }
541542// for a select x.y from x ... check that x has property y and that x is in543// from544protectedvoid validateColumnReferenceAndResolveType(ColumnReference colRef) {
545// either same name or mapped alias546 String typeQueryName = getReferencedTypeQueryName(colRef.getQualifier());
547 TypeDefinition td = typeMgr.getTypeByQueryName(typeQueryName);
548if (null == td) {
549thrownewCmisQueryException(colRef.getQualifier()
550 + " is neither a type query name nor an alias.");
551 }
552553 validateColumnReferenceAndResolveType(td, colRef);
554 }
555556protectedvoid validateColumnReferenceAndResolveType(TypeDefinition td, ColumnReference colRef) {
557558// type found, check if property exists559boolean hasProp;
560if (colRef.getPropertyQueryName().equals("*")) {
561 hasProp = true;
562 } else {
563 hasProp = TypeValidator.typeContainsPropertyWithQueryName(td, colRef.getPropertyQueryName());
564 }
565if (!hasProp) {
566thrownewCmisQueryException(colRef.getPropertyQueryName()
567 + " is not a valid property query name in type " + td.getId() + ".");
568 }
569570 colRef.setTypeDefinition(typeMgr.getPropertyIdForQueryName(td, colRef.getPropertyQueryName()), td);
571 }
572573// return type query name for a referenced column (which can be the name574// itself or an alias575protected String getReferencedTypeQueryName(String qualifier) {
576 String typeQueryName = froms.get(qualifier);
577if (null == typeQueryName) {
578// if an alias was defined but still the original is used we have to579// search case: SELECT T.p FROM T AS TAlias580 String q = null;
581for (String tqn : froms.values()) {
582if (qualifier.equals(tqn)) {
583if (q != null) {
584thrownewCmisQueryException(qualifier
585 + " is an ambiguous type query name.");
586 }
587 q = tqn;
588 }
589 }
590return q;
591 } else {
592return typeQueryName;
593 }
594 }
595596 }