This project has retired. For details please refer to its Attic page.
QueryObject xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.server.support.query;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.LinkedHashMap;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  
30  import org.antlr.runtime.tree.Tree;
31  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
32  import org.apache.chemistry.opencmis.server.support.TypeManager;
33  import org.apache.chemistry.opencmis.server.support.TypeValidator;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  
37  
38  /**
39   * QueryObject is a class used to encapsulate a CMIS query. It is created from
40   * an ANTLR parser on an incoming query string. During parsing various
41   * informations are collected and stored in objects suitable for evaluating the
42   * query (like selected properties, effected types and order statements. A query
43   * evaluator can use this information to perform the query and build the result.
44   */
45  public class QueryObject {
46  
47      private static final Log LOG = LogFactory.getLog(QueryObject.class);
48  
49      // For error handling see:
50      // http://www.antlr.org/pipermail/antlr-interest/2008-April/027600.html
51      // select part
52      protected TypeManager typeMgr;
53      protected final List<CmisSelector> selectReferences = new ArrayList<CmisSelector>();
54      protected final List<CmisSelector> whereReferences = new ArrayList<CmisSelector>();
55      protected final List<CmisSelector> joinReferences = new ArrayList<CmisSelector>();
56      // --> Join not implemented yet
57      protected final Map<String, CmisSelector> colOrFuncAlias = new HashMap<String, CmisSelector>();
58  
59      // from part
60      /** map from alias name to type query name */
61      protected final Map<String, String> froms = new LinkedHashMap<String, String>();
62  
63      /** main from alias name */
64      protected String from = null;
65  
66      protected final List<JoinSpec> joinSpecs = new LinkedList<JoinSpec>();
67  
68      // where part
69      protected final Map<Integer, CmisSelector> columnReferences = new HashMap<Integer, CmisSelector>();
70      protected final Map<Integer, String> typeReferences = new HashMap<Integer, String>();
71  
72      // order by part
73      protected final List<SortSpec> sortSpecs = new ArrayList<SortSpec>();
74  
75      private String errorMessage;
76  
77      public static class JoinSpec {
78  
79          /** INNER / LEFT / RIGHT */
80          public final String kind;
81  
82          /** Alias or full table type */
83          public final String alias;
84  
85          public ColumnReference onLeft;
86  
87          public ColumnReference onRight;
88  
89          public JoinSpec(String kind, String alias) {
90              this.kind = kind;
91              this.alias = alias;
92          }
93  
94          public void setSelectors(ColumnReference onLeft, ColumnReference onRight) {
95              this.onLeft = onLeft;
96              this.onRight = onRight;
97          }
98  
99          @Override
100         public String toString() {
101             return "JoinReference(" + kind + "," + alias + "," + onLeft + ","
102                     + onRight + ")";
103         }
104     }
105 
106     public class SortSpec {
107         public final boolean ascending;
108         public final Integer colRefKey; // key in columnReferencesMap point to column
109                                     // descriptions
110 
111         public SortSpec(Integer key, boolean ascending) {
112             this.colRefKey = key;
113             this.ascending = ascending;
114         }
115 
116         public CmisSelector getSelector() {
117             return columnReferences.get(colRefKey);
118         }
119 
120         public boolean isAscending() {
121             return ascending;
122         }
123     }
124 
125     public QueryObject() {
126     }
127 
128     public QueryObject(TypeManager tm) {
129         typeMgr = tm;
130     }
131 
132     public Map<Integer, CmisSelector> getColumnReferences() {
133         return Collections.unmodifiableMap(columnReferences);
134     }
135 
136     public CmisSelector getColumnReference(Integer token) {
137         return columnReferences.get(token);
138     }
139 
140     public String getTypeReference(Integer token) {
141         return typeReferences.get(token);
142     }
143 
144     public String getErrorMessage() {
145         return errorMessage;
146     }
147 
148     // ///////////////////////////////////////////////////////
149     // SELECT part
150 
151     // public accessor methods
152     public List<CmisSelector> getSelectReferences() {
153         return selectReferences;
154     }
155 
156     public void addSelectReference(Tree node, CmisSelector selRef) {
157         selectReferences.add(selRef);
158         columnReferences.put(node.getTokenStartIndex(), selRef);
159     }
160 
161     public void addAlias(String aliasName, CmisSelector aliasRef) {
162         LOG.debug("add alias: " + aliasName + " for: " + aliasRef);
163         if (colOrFuncAlias.containsKey(aliasName)) {
164             throw new CmisQueryException("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     }
171 
172     public CmisSelector getSelectAlias(String aliasName) {
173         return colOrFuncAlias.get(aliasName);
174     }
175 
176     // ///////////////////////////////////////////////////////
177     // FROM part
178 
179     public String addType(String aliasName, String typeQueryName) {
180         try {
181             LOG.debug("add alias: " + aliasName + " for: " + typeQueryName);
182             if (froms.containsKey(aliasName)) {
183                 throw new CmisQueryException("You cannot use name " + aliasName
184                         + " more than once as alias in a from part.");
185             }
186             if (aliasName == null) {
187                 aliasName = typeQueryName;
188             }
189             froms.put(aliasName, typeQueryName);
190             if (from == null) {
191                 from = aliasName;
192             }
193             return aliasName;
194         } catch (CmisQueryException cqe) {
195             errorMessage = cqe.getMessage(); // preserve message
196             return null; // indicate an error to ANTLR so that it generates FailedPredicateException
197         }
198     }
199 
200     public String getMainTypeAlias() {
201         return from;
202     }
203 
204     public Map<String, String> getTypes() {
205         return Collections.unmodifiableMap(froms);
206     }
207 
208     public String getTypeQueryName(String qualifier) {
209         return froms.get(qualifier);
210     }
211 
212     public TypeDefinition getTypeDefinitionFromQueryName(String queryName) {
213         return typeMgr.getTypeByQueryName(queryName);
214     }
215 
216     public TypeDefinition getParentType(TypeDefinition td) {
217         String parentType = td.getParentTypeId();
218         return parentType == null ? null : typeMgr.getTypeById(parentType).getTypeDefinition();
219     }
220 
221     public TypeDefinition getParentType(String typeId) {
222         TypeDefinition td = typeMgr.getTypeById(typeId).getTypeDefinition();
223         String parentType = td == null ? null : td.getParentTypeId();
224         return parentType == null ? null : typeMgr.getTypeById(parentType).getTypeDefinition();
225     }
226 
227     public TypeDefinition getMainFromName() {
228         // as we don't support JOINS take first type
229         String queryName = froms.values().iterator().next();
230         TypeDefinition td = getTypeDefinitionFromQueryName(queryName);
231         return td;
232     }
233 
234     /**
235      * return a map of all columns that have been requested in the SELECT part
236      * 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      */
242     public Map<String, String> getRequestedPropertiesByAlias() {
243         return getRequestedProperties(true);
244     }
245     
246     /**
247      * return a map of all columns that have been requested in the SELECT part
248      * of the statement.
249      *
250      * @return a map with a String as a key and value. key is the query name of
251      *         the property, value is the alias if an alias was given or the
252      *         query name otherwise.
253      *         
254      * @deprecated  Use getRequestedPropertiesByAlias instead.
255      */
256     @Deprecated
257     public Map<String, String> getRequestedProperties() {
258         return getRequestedProperties(false);
259     }
260     
261     private Map<String, String> getRequestedProperties(boolean byAlias) {
262 
263         Map<String, String> res = new HashMap<String, String>();
264         for (CmisSelector sel : selectReferences) {
265             if (sel instanceof ColumnReference) {
266                 ColumnReference colRef = (ColumnReference) sel;
267                 String key = colRef.getPropertyId();
268                 if (null == key)
269                  {
270                     key = colRef.getPropertyQueryName(); // happens for *
271                 }
272                 String propDescr = colRef.getAliasName() == null ? colRef.getPropertyQueryName() : colRef
273                         .getAliasName();
274                 if (byAlias)
275                     res.put(propDescr, key);
276                 else
277                     res.put(key, propDescr);
278             }
279         }
280         return res;
281     }
282 
283     /**
284      * return a map of all functions that have been requested in the SELECT part
285      * of the statement.
286      *
287      * @return a map with a String as a key and value. key is the function name
288      *         of the property, value is the alias if an alias was given or the
289      *         function name otherwise.
290      *         
291      * @deprecated  Use getRequestedPropertiesByAlias instead.
292      */
293     @Deprecated
294     public Map<String, String> getRequestedFuncs() {
295         return getRequestedFuncs(false);
296     }
297     
298     /**
299      * return a map of all functions that have been requested in the SELECT part
300      * 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 name
304      * of the property. 
305      */
306     public Map<String, String> getRequestedFuncsByAlias() {
307         return getRequestedFuncs(true);
308     }
309 
310     private Map<String, String> getRequestedFuncs(boolean byAlias) {
311 
312         Map<String, String> res = new HashMap<String, String>();
313         for (CmisSelector sel : selectReferences) {
314             if (sel instanceof FunctionReference) {
315                 FunctionReference funcRef = (FunctionReference) sel;
316                 String propDescr = funcRef.getAliasName() == null ? funcRef.getName() : funcRef.getAliasName();
317                 if (byAlias)
318                     res.put(propDescr, funcRef.getName());
319                 else
320                     res.put(funcRef.getName(), propDescr);
321             }
322         }
323         return res;
324     }
325 
326     // ///////////////////////////////////////////////////////
327     // JOINS
328 
329     public void addJoinReference(Tree node, CmisSelector reference) {
330         columnReferences.put(node.getTokenStartIndex(), reference);
331         joinReferences.add(reference);
332     }
333 
334     public List<CmisSelector> getJoinReferences() {
335         return Collections.unmodifiableList(joinReferences);
336     }
337 
338     public void addJoin(String kind, String alias, boolean hasSpec) {
339         JoinSpec join = new JoinSpec(kind, alias);
340         if (hasSpec) {
341             // get columns from last added references
342             int n = joinReferences.size();
343             ColumnReference onLeft = (ColumnReference) joinReferences.get(n - 2);
344             ColumnReference onRight = (ColumnReference) joinReferences.get(n - 1);
345             join.setSelectors(onLeft, onRight);
346         }
347         joinSpecs.add(join);
348     }
349 
350     public List<JoinSpec> getJoins() {
351         return joinSpecs;
352     }
353 
354     // ///////////////////////////////////////////////////////
355     // WHERE part
356 
357 
358     public void 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     }
363 
364     public List<CmisSelector> getWhereReferences() {
365         return Collections.unmodifiableList(whereReferences);
366     }
367 
368     public void addWhereTypeReference(Tree node, String qualifier) {
369         if (node != null) {
370             typeReferences.put(node.getTokenStartIndex(), qualifier);
371         }
372     }
373 
374     // ///////////////////////////////////////////////////////
375     // ORDER_BY part
376 
377     public List<SortSpec> getOrderBys() {
378         return Collections.unmodifiableList(sortSpecs);
379     }
380 
381     public void addSortCriterium(Tree node, ColumnReference colRef, boolean ascending) {
382         LOG.debug("addSortCriterium: " + colRef + " ascending: " + ascending);
383         columnReferences.put(node.getTokenStartIndex(), colRef);
384         sortSpecs.add(new SortSpec(node.getTokenStartIndex(), ascending));
385     }
386 
387     // ///////////////////////////////////////////////////////
388     // resolve types after first pass traversing the AST is complete
389 
390     public boolean resolveTypes() {
391         try {
392             LOG.debug("First pass of query traversal is complete, resolving types");
393             if (null == typeMgr) {
394                 return true;
395             }
396 
397             // First resolve all alias names defined in SELECT:
398             for (CmisSelector alias : colOrFuncAlias.values()) {
399                 if (alias instanceof ColumnReference) {
400                     ColumnReference colRef = ((ColumnReference) alias);
401                     resolveTypeForAlias(colRef);
402                 }
403             }
404 
405             // Then replace all aliases used somewhere by their resolved column
406             // reference:
407             for (Integer obj : columnReferences.keySet()) {
408                 CmisSelector selector = columnReferences.get(obj);
409                 String key = selector.getName();
410                 if (colOrFuncAlias.containsKey(key)) { // it is an alias
411                     CmisSelector resolvedReference = colOrFuncAlias.get(key);
412                     columnReferences.put(obj, resolvedReference);
413                     // Note: ^ This may replace the value in the map with the same
414                     // value, but this does not harm.
415                     // Otherwise we need to check if it is resolved or not which
416                     // causes two more ifs:
417                     // if (selector instanceof ColumnReference) {
418                     // ColumnReference colRef = ((ColumnReference) selector);
419                     // if (colRef.getTypeDefinition() == null) // it is not yet
420                     // resolved
421                     // // replace unresolved column reference by resolved on from
422                     // alias map
423                     // columnReferences.put(obj,
424                     // colOrFuncAlias.get(selector.getAliasName()));
425                     // } else
426                     // columnReferences.put(obj,
427                     // colOrFuncAlias.get(selector.getAliasName()));
428                     if (whereReferences.remove(selector)) {
429                         // replace unresolved by resolved reference
430                         whereReferences.add(resolvedReference);
431                     }
432                     if (joinReferences.remove(selector)) {
433                         // replace unresolved by resolved reference
434                         joinReferences.add(resolvedReference);
435                     }
436                 }
437             }
438 
439             // The replace all remaining column references not using an alias
440             for (CmisSelector select : columnReferences.values()) {
441                 // ignore functions here
442                 if (select instanceof ColumnReference) {
443                     ColumnReference colRef = ((ColumnReference) select);
444                     if (colRef.getTypeDefinition() == null) { // not yet resolved
445                         if (colRef.getQualifier() == null) {
446                             // unqualified select: SELECT p FROM
447                             resolveTypeForColumnReference(colRef);
448                         } else {
449                             // qualified select: SELECT t.p FROM
450                             validateColumnReferenceAndResolveType(colRef);
451                         }
452                     }
453                 }
454             }
455 
456             // Replace types used as qualifiers (IN_TREE, IN_FOLDER,
457             // CONTAINS) by their corresponding alias (correlation name)
458             for (Entry<Integer, String> en: typeReferences.entrySet()) {
459                 Integer obj = en.getKey();
460                 String qualifier = en.getValue();
461                 String typeQueryName = getReferencedTypeQueryName(qualifier);
462                 if (typeQueryName == null) {
463                     throw new CmisQueryException(qualifier
464                             + " is neither a type query name nor an alias.");
465                 }
466                 if (typeQueryName.equals(qualifier)) {
467                     // try to find an alias for it
468                     String alias = null;
469                     for (Entry<String, String> e : froms.entrySet()) {
470                         String q = e.getKey();
471                         String tqn = e.getValue();
472                         if (!tqn.equals(q) && typeQueryName.equals(tqn)) {
473                             alias = q;
474                             break;
475                         }
476                     }
477                     if (alias != null) {
478                         typeReferences.put(obj, alias);
479                     }
480                 }
481             }
482 
483             return true;
484         } catch (CmisQueryException cqe) {
485             errorMessage = cqe.getMessage(); // preserve message
486             return false; // indicate an error to ANTLR so that it generates FailedPredicateException
487         }
488     }
489 
490     protected void resolveTypeForAlias(ColumnReference colRef) {
491         String aliasName = colRef.getAliasName();
492 
493         if (colOrFuncAlias.containsKey(aliasName)) {
494             CmisSelector selector = colOrFuncAlias.get(aliasName);
495             if (selector instanceof ColumnReference) {
496                 colRef = (ColumnReference) selector; // alias target
497                 if (colRef.getQualifier() == null) {
498                     // unqualified select: SELECT p FROM
499                     resolveTypeForColumnReference(colRef);
500                 } else {
501                     // qualified select: SELECT t.p FROM
502                     validateColumnReferenceAndResolveType(colRef);
503                 }
504             }
505             // else --> ignore FunctionReference
506         }
507     }
508 
509     // for a select x from y, z ... find the type in type manager for x
510     protected void resolveTypeForColumnReference(ColumnReference colRef) {
511         String propName = colRef.getPropertyQueryName();
512         boolean isStar = propName.equals("*");
513 
514         // it is property query name without a type, so find type
515         int noFound = 0;
516         TypeDefinition tdFound = null;
517         for (String typeQueryName : froms.values()) {
518             TypeDefinition td = typeMgr.getTypeByQueryName(typeQueryName);
519             if (null == td) {
520                 throw new CmisQueryException(typeQueryName + " is neither a type query name nor an alias.");
521             } else if (isStar) {
522                 ++noFound;
523                 tdFound = null;
524             } else if (TypeValidator.typeContainsPropertyWithQueryName(td, propName)) {
525                 ++noFound;
526                 tdFound = td;
527             }
528         }
529         if (noFound == 0) {
530             throw new CmisQueryException(propName
531                     + " is not a property query name in any of the types in from ...");
532         } else if (noFound > 1 && !isStar) {
533             throw new CmisQueryException(propName
534                     + " is not a unique property query name within the types in from ...");
535         } else {
536             if (null != tdFound) {
537                 validateColumnReferenceAndResolveType(tdFound, colRef);
538             }
539         }
540     }
541 
542     // for a select x.y from x ... check that x has property y and that x is in
543     // from
544     protected void validateColumnReferenceAndResolveType(ColumnReference colRef) {
545         // either same name or mapped alias
546         String typeQueryName = getReferencedTypeQueryName(colRef.getQualifier());
547         TypeDefinition td = typeMgr.getTypeByQueryName(typeQueryName);
548         if (null == td) {
549             throw new CmisQueryException(colRef.getQualifier()
550                     + " is neither a type query name nor an alias.");
551         }
552 
553         validateColumnReferenceAndResolveType(td, colRef);
554     }
555 
556     protected void validateColumnReferenceAndResolveType(TypeDefinition td, ColumnReference colRef) {
557 
558         // type found, check if property exists
559         boolean hasProp;
560         if (colRef.getPropertyQueryName().equals("*")) {
561             hasProp = true;
562         } else {
563             hasProp = TypeValidator.typeContainsPropertyWithQueryName(td, colRef.getPropertyQueryName());
564         }
565         if (!hasProp) {
566             throw new CmisQueryException(colRef.getPropertyQueryName()
567                     + " is not a valid property query name in type " + td.getId() + ".");
568         }
569 
570         colRef.setTypeDefinition(typeMgr.getPropertyIdForQueryName(td, colRef.getPropertyQueryName()), td);
571     }
572 
573     // return type query name for a referenced column (which can be the name
574     // itself or an alias
575     protected String getReferencedTypeQueryName(String qualifier) {
576         String typeQueryName = froms.get(qualifier);
577         if (null == typeQueryName) {
578             // if an alias was defined but still the original is used we have to
579             // search case: SELECT T.p FROM T AS TAlias
580             String q = null;
581             for (String tqn : froms.values()) {
582                 if (qualifier.equals(tqn)) {
583                     if (q != null) {
584                         throw new CmisQueryException(qualifier
585                                 + " is an ambiguous type query name.");
586                     }
587                     q = tqn;
588                 }
589             }
590             return q;
591         } else {
592             return typeQueryName;
593         }
594     }
595 
596 }