This project has retired. For details please refer to its Attic page.
InMemoryQueryProcessor 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   * Contributors:
20   *     Jens Huebel
21   *     Florent Guillaume, Nuxeo
22   */
23  package org.apache.chemistry.opencmis.inmemory.query;
24  
25  import java.math.BigDecimal;
26  import java.math.BigInteger;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.GregorianCalendar;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.regex.Pattern;
34  
35  import org.antlr.runtime.tree.Tree;
36  import org.apache.chemistry.opencmis.commons.data.ObjectData;
37  import org.apache.chemistry.opencmis.commons.data.ObjectList;
38  import org.apache.chemistry.opencmis.commons.data.PropertyData;
39  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
40  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
41  import org.apache.chemistry.opencmis.commons.enums.Cardinality;
42  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
43  import org.apache.chemistry.opencmis.commons.enums.PropertyType;
44  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
45  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Content;
46  import org.apache.chemistry.opencmis.inmemory.storedobj.api.DocumentVersion;
47  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Filing;
48  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Folder;
49  import org.apache.chemistry.opencmis.inmemory.storedobj.api.ObjectStore;
50  import org.apache.chemistry.opencmis.inmemory.storedobj.api.StoredObject;
51  import org.apache.chemistry.opencmis.inmemory.storedobj.api.VersionedDocument;
52  import org.apache.chemistry.opencmis.inmemory.storedobj.impl.ContentStreamDataImpl;
53  import org.apache.chemistry.opencmis.inmemory.storedobj.impl.ObjectStoreImpl;
54  import org.apache.chemistry.opencmis.inmemory.types.PropertyCreationHelper;
55  import org.apache.chemistry.opencmis.inmemory.types.PropertyUtil;
56  import org.apache.chemistry.opencmis.server.support.TypeManager;
57  import org.apache.chemistry.opencmis.server.support.query.AbstractPredicateWalker;
58  import org.apache.chemistry.opencmis.server.support.query.CmisQueryWalker;
59  import org.apache.chemistry.opencmis.server.support.query.CmisSelector;
60  import org.apache.chemistry.opencmis.server.support.query.ColumnReference;
61  import org.apache.chemistry.opencmis.server.support.query.QueryObject;
62  import org.apache.chemistry.opencmis.server.support.query.QueryObject.SortSpec;
63  import org.apache.chemistry.opencmis.server.support.query.QueryUtil;
64  import org.apache.chemistry.opencmis.server.support.query.StringUtil;
65  import org.apache.commons.logging.Log;
66  import org.apache.commons.logging.LogFactory;
67  
68  /**
69   * A processor for a CMIS query for the In-Memory server. During tree traversal
70   * conditions are checked against the data contained in the central hash map
71   * with all objects. In a first pass one time setup is performed, in a custom
72   * walk across the query expression tree an object is checked if it matches. In
73   * case of a match it is appended to a list of matching objects.
74   */
75  public class InMemoryQueryProcessor {
76  
77      private static final Log LOG = LogFactory.getLog(InMemoryQueryProcessor.class);
78  
79      private List<StoredObject> matches = new ArrayList<StoredObject>();
80      private QueryObject queryObj;
81      private Tree whereTree;
82      private ObjectStoreImpl objStore;
83      
84      public InMemoryQueryProcessor(ObjectStoreImpl objStore) {
85          this.objStore = objStore;
86      }
87  
88      /**
89       * Main entry function to process a query from discovery service
90       */
91      public ObjectList query(TypeManager tm, ObjectStore objectStore, String user, String repositoryId,
92              String statement, Boolean searchAllVersions, Boolean includeAllowableActions,
93              IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount) {
94  
95          queryObj = new QueryObject(tm);
96          processQueryAndCatchExc(statement); // calls query processor
97  
98          // iterate over all the objects and check for each if the query matches
99          for (String objectId : ((ObjectStoreImpl) objectStore).getIds()) {
100             StoredObject so = objectStore.getObjectById(objectId);
101             match(so, user, searchAllVersions == null ? true : searchAllVersions.booleanValue());
102         }
103 
104         ObjectList objList = buildResultList(tm, user, includeAllowableActions, includeRelationships, renditionFilter,
105                 maxItems, skipCount);
106         LOG.debug("Query result, number of matching objects: " + objList.getNumItems());
107         return objList;
108     }
109 
110     public void processQueryAndCatchExc(String statement) {
111         QueryUtil queryUtil = new QueryUtil();
112         CmisQueryWalker walker = queryUtil.traverseStatementAndCatchExc(statement, queryObj, null);
113         whereTree = walker.getWherePredicateTree();
114     }
115 
116     public ObjectList buildResultList(TypeManager tm, String user, Boolean includeAllowableActions,
117             IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount) {
118 
119         sortMatches();
120 
121         ObjectListImpl res = new ObjectListImpl();
122         res.setNumItems(BigInteger.valueOf(matches.size()));
123         int start = 0;
124         if (skipCount != null) {
125             start = (int) skipCount.longValue();
126         }
127         if (start < 0) {
128             start = 0;
129         }
130         if (start > matches.size()) {
131             start = matches.size();
132         }
133         int stop = 0;
134         if (maxItems != null) {
135             stop = start + (int) maxItems.longValue();
136         }
137         if (stop <= 0 || stop > matches.size()) {
138             stop = matches.size();
139         }
140         res.setHasMoreItems(stop < matches.size());
141         if (start > 0 || stop > 0) {
142             matches = matches.subList(start, stop);
143         }
144         List<ObjectData> objDataList = new ArrayList<ObjectData>();
145         Map<String, String> props = queryObj.getRequestedPropertiesByAlias();
146         Map<String, String> funcs = queryObj.getRequestedFuncsByAlias();
147         for (StoredObject so : matches) {
148             TypeDefinition td = tm.getTypeById(so.getTypeId()).getTypeDefinition();
149             ObjectData od = PropertyCreationHelper.getObjectDataQueryResult(td, so, user, props, funcs,
150                     includeAllowableActions, includeRelationships, renditionFilter);
151 
152             objDataList.add(od);
153         }
154         res.setObjects(objDataList);
155         return res;
156     }
157 
158     private boolean typeMatches(TypeDefinition td, StoredObject so) {
159         String typeId = so.getTypeId();
160         while (typeId != null) {
161             if (typeId.equals(td.getId())) {
162                 return true;
163             }
164             // check parent type
165             TypeDefinition parentTD = queryObj.getParentType(typeId);
166             typeId = parentTD == null ? null : parentTD.getId();
167         }
168         return false;
169     }
170 
171     private void sortMatches() {
172         final List<SortSpec> orderBy = queryObj.getOrderBys();
173         if (orderBy.size() > 1) {
174             LOG.warn("ORDER BY has more than one sort criterium, all but the first are ignored.");
175         }
176         class ResultComparator implements Comparator<StoredObject> {
177 
178             @SuppressWarnings("unchecked")
179             public int compare(StoredObject so1, StoredObject so2) {
180                 SortSpec s = orderBy.get(0);
181                 CmisSelector sel = s.getSelector();
182                 int result;
183 
184                 if (sel instanceof ColumnReference) {
185                     String propId = ((ColumnReference) sel).getPropertyId();
186                     PropertyDefinition<?> pd = ((ColumnReference) sel).getPropertyDefinition();
187                     
188                     Object propVal1 = PropertyUtil.getProperty(so1, propId, pd);
189                     Object propVal2 = PropertyUtil.getProperty(so2, propId, pd);
190 
191                     if (propVal1 == null && propVal2 == null) {
192                         result = 0;
193                     } else if (propVal1 == null) {
194                         result = -1;
195                     } else if (propVal2 == null) {
196                         result = 1;
197                     } else {
198                         result = ((Comparable<Object>) propVal1).compareTo(propVal2);
199                     }
200                 } else {
201                     // String funcName = ((FunctionReference) sel).getName();
202                     // evaluate function here, currently ignore
203                     result = 0;
204                 }
205                 if (!s.isAscending()) {
206                     result = -result;
207                 }
208                 return result;
209             }
210         }
211 
212         if (orderBy.size() > 0) {
213             Collections.sort(matches, new ResultComparator());
214         }
215 
216     }
217 
218     /**
219      * Check for each object contained in the in-memory repository if it matches
220      * the current query expression. If yes add it to the list of matched
221      * objects.
222      *
223      * @param so
224      *            object stored in the in-memory repository
225      */
226     private void match(StoredObject so, String user, boolean searchAllVersions) {
227         // log.debug("checkMatch() for object: " + so.getId());
228         // first check if type is matching...
229         String queryName = queryObj.getTypes().values().iterator().next(); // as
230                                                                            // we
231                                                                            // don't
232                                                                            // support
233                                                                            // JOINS
234                                                                            // take
235                                                                            // first
236                                                                            // type
237         TypeDefinition td = queryObj.getTypeDefinitionFromQueryName(queryName);
238         boolean skip = so instanceof VersionedDocument; // we are only
239                                                         // interested in
240                                                         // versions not in the
241                                                         // series
242         boolean typeMatches = typeMatches(td, so);
243         if (!searchAllVersions && so instanceof DocumentVersion
244                 && ((DocumentVersion) so).getParentDocument().getLatestVersion(false) != so) {
245             skip = true;
246         }
247         // ... then check expression...
248         if (typeMatches && !skip) {
249             evalWhereTree(whereTree, user, so);
250         }
251     }
252 
253     private void evalWhereTree(Tree node, String user, StoredObject so) {
254         boolean match = true;
255         if (null != node) {
256             match = evalWhereNode(so, user, node);
257         }
258         if (match && objStore.hasReadAccess(user, so))
259          {
260             matches.add(so); // add to list
261         }
262     }
263 
264     /**
265      * For each object check if it matches and append it to match-list if it
266      * does. We do here our own walking mechanism so that we can pass additional
267      * parameters and define the return types.
268      *
269      * @param node
270      *            node in where clause
271      * @return true if it matches, false if it not matches
272      */
273     boolean evalWhereNode(StoredObject so, String user, Tree node) {
274         return new InMemoryWhereClauseWalker(so, user).walkPredicate(node);
275     }
276 
277     public class InMemoryWhereClauseWalker extends AbstractPredicateWalker {
278 
279         protected final StoredObject so;
280         protected final String user;
281 
282         public InMemoryWhereClauseWalker(StoredObject so, String user) {
283             this.so = so;
284             this.user = user;
285         }
286 
287         @Override
288         public Boolean walkNot(Tree opNode, Tree node) {
289             boolean matches = walkPredicate(node);
290             return !matches;
291         }
292 
293         @Override
294         public Boolean walkAnd(Tree opNode, Tree leftNode, Tree rightNode) {
295             boolean matches1 = walkPredicate(leftNode);
296             boolean matches2 = walkPredicate(rightNode);
297             return matches1 && matches2;
298         }
299 
300         @Override
301         public Boolean walkOr(Tree opNode, Tree leftNode, Tree rightNode) {
302             boolean matches1 = walkPredicate(leftNode);
303             boolean matches2 = walkPredicate(rightNode);
304             return matches1 || matches2;
305         }
306 
307         @Override
308         public Boolean walkEquals(Tree opNode, Tree leftNode, Tree rightNode) {
309             Integer cmp = compareTo(leftNode, rightNode);
310             return cmp == null ? false : cmp == 0;
311         }
312 
313         @Override
314         public Boolean walkNotEquals(Tree opNode, Tree leftNode, Tree rightNode) {
315             Integer cmp = compareTo(leftNode, rightNode);
316             return cmp == null ? false : cmp != 0;
317         }
318 
319         @Override
320         public Boolean walkGreaterThan(Tree opNode, Tree leftNode, Tree rightNode) {
321             Integer cmp = compareTo(leftNode, rightNode);
322             return cmp == null ? false : cmp > 0;
323         }
324 
325         @Override
326         public Boolean walkGreaterOrEquals(Tree opNode, Tree leftNode, Tree rightNode) {
327             Integer cmp = compareTo(leftNode, rightNode);
328             return cmp == null ? false : cmp >= 0;
329         }
330 
331         @Override
332         public Boolean walkLessThan(Tree opNode, Tree leftNode, Tree rightNode) {
333             Integer cmp = compareTo(leftNode, rightNode);
334             return cmp == null ? false : cmp < 0;
335         }
336 
337         @Override
338         public Boolean walkLessOrEquals(Tree opNode, Tree leftNode, Tree rightNode) {
339             Integer cmp = compareTo(leftNode, rightNode);
340             return cmp == null ? false : cmp <= 0;
341         }
342 
343         @Override
344         public Boolean walkIn(Tree opNode, Tree colNode, Tree listNode) {
345             ColumnReference colRef = getColumnReference(colNode);
346             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
347             List<Object> literals = onLiteralList(listNode);
348             Object prop = PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
349 
350             if (pd.getCardinality() != Cardinality.SINGLE) {
351                 throw new IllegalStateException("Operator IN only is allowed on single-value properties ");
352             } else if (prop == null) {
353                 return false;
354             } else {
355                 return literals.contains(prop);
356             }
357         }
358 
359         @Override
360         public Boolean walkNotIn(Tree opNode, Tree colNode, Tree listNode) {
361             // Note just return !walkIn(node, colNode, listNode) is wrong,
362             // because
363             // then it evaluates to true for null values (not set properties).
364             ColumnReference colRef = getColumnReference(colNode);
365             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
366             Object prop = PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
367             List<Object> literals = onLiteralList(listNode);
368             if (pd.getCardinality() != Cardinality.SINGLE) {
369                 throw new IllegalStateException("Operator IN only is allowed on single-value properties ");
370             } else if (prop == null) {
371                 return false;
372             } else {
373                 return !literals.contains(prop);
374             }
375         }
376 
377         @Override
378         public Boolean walkInAny(Tree opNode, Tree colNode, Tree listNode) {
379             ColumnReference colRef = getColumnReference(colNode);
380             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
381             PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
382             List<Object> literals = onLiteralList(listNode);
383             if (pd.getCardinality() != Cardinality.MULTI) {
384                 throw new IllegalStateException("Operator ANY...IN only is allowed on multi-value properties ");
385             } else if (lVal == null) {
386                 return false;
387             } else {
388                 List<?> props = lVal.getValues();
389                 for (Object prop : props) {
390                     LOG.debug("comparing with: " + prop);
391                     if (literals.contains(prop)) {
392                         return true;
393                     }
394                 }
395                 return false;
396             }
397         }
398 
399         @Override
400         public Boolean walkNotInAny(Tree opNode, Tree colNode, Tree listNode) {
401             // Note just return !walkNotInAny(node, colNode, listNode) is
402             // wrong, because
403             // then it evaluates to true for null values (not set properties).
404             ColumnReference colRef = getColumnReference(colNode);
405             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
406             PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
407             List<Object> literals = onLiteralList(listNode);
408             if (pd.getCardinality() != Cardinality.MULTI) {
409                 throw new IllegalStateException("Operator ANY...IN only is allowed on multi-value properties ");
410             } else if (lVal == null) {
411                 return false;
412             } else {
413                 List<?> props = lVal.getValues();
414                 for (Object prop : props) {
415                     LOG.debug("comparing with: " + prop);
416                     if (literals.contains(prop)) {
417                         return false;
418                     }
419                 }
420                 return true;
421             }
422         }
423 
424         @Override
425         public Boolean walkEqAny(Tree opNode, Tree literalNode, Tree colNode) {
426             ColumnReference colRef = getColumnReference(colNode);
427             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
428             PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
429             Object literal = walkExpr(literalNode);
430             if (pd.getCardinality() != Cardinality.MULTI) {
431                 throw new IllegalStateException("Operator = ANY only is allowed on multi-value properties ");
432             } else if (lVal == null) {
433                 return false;
434             } else {
435                 List<?> props = lVal.getValues();
436                 return props.contains(literal);
437             }
438         }
439 
440         @Override
441         public Boolean walkIsNull(Tree opNode, Tree colNode) {
442             ColumnReference colRef = getColumnReference(colNode);
443             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
444             Object propVal = PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
445             return propVal == null;
446         }
447 
448         @Override
449         public Boolean walkIsNotNull(Tree opNode, Tree colNode) {
450             ColumnReference colRef = getColumnReference(colNode);
451             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
452             Object propVal = PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
453             return propVal != null;
454         }
455 
456         @Override
457         public Boolean walkLike(Tree opNode, Tree colNode, Tree stringNode) {
458             Object rVal = walkExpr(stringNode);
459             if (!(rVal instanceof String)) {
460                 throw new IllegalStateException("LIKE operator requires String literal on right hand side.");
461             }
462 
463             ColumnReference colRef = getColumnReference(colNode);
464             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
465             PropertyType propType = pd.getPropertyType();
466             if (propType != PropertyType.STRING && propType != PropertyType.HTML && propType != PropertyType.ID
467                     && propType != PropertyType.URI) {
468                 throw new IllegalStateException("Property type " + propType.value() + " is not allowed FOR LIKE");
469             }
470             if (pd.getCardinality() != Cardinality.SINGLE) {
471                 throw new IllegalStateException("LIKE is not allowed for multi-value properties ");
472             }
473 
474             String propVal = (String) PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
475             
476             String pattern = translatePattern((String) rVal); // SQL to Java
477                                                               // regex
478                                                               // syntax
479             Pattern p = Pattern.compile(pattern);
480             return p.matcher(propVal).matches();
481         }
482 
483         @Override
484         public Boolean walkNotLike(Tree opNode, Tree colNode, Tree stringNode) {
485             return !walkLike(opNode, colNode, stringNode);
486         }
487 
488         @Override
489         public Boolean walkInFolder(Tree opNode, Tree qualNode, Tree paramNode) {
490             if (null != qualNode) {
491                 getTableReference(qualNode);
492                 // just for error checking we do not evaluate this, there is
493                 // only one from without join support
494             }
495             Object lit = walkExpr(paramNode);
496             if (!(lit instanceof String)) {
497                 throw new IllegalStateException("Folder id in IN_FOLDER must be of type String");
498             }
499             String folderId = (String) lit;
500 
501             // check if object is in folder
502             if (so instanceof Filing) {
503                 return hasParent((Filing) so, folderId, user);
504             } else {
505                 return false;
506             }
507         }
508 
509         @Override
510         public Boolean walkInTree(Tree opNode, Tree qualNode, Tree paramNode) {
511             if (null != qualNode) {
512                 getTableReference(qualNode);
513                 // just for error checking we do not evaluate this, there is
514                 // only one from without join support
515             }
516             Object lit = walkExpr(paramNode);
517             if (!(lit instanceof String)) {
518                 throw new IllegalStateException("Folder id in IN_FOLDER must be of type String");
519             }
520             String folderId = (String) lit;
521 
522             // check if object is in folder
523             if (so instanceof Filing) {
524                 return hasAncestor((Filing) so, folderId, user);
525             } else {
526                 return false;
527             }
528         }
529 
530         protected Integer compareTo(Tree leftChild, Tree rightChild) {
531             Object rVal = walkExpr(rightChild);
532 
533             // log.debug("retrieve node from where: " +
534             // System.identityHashCode(leftChild) + " is " + leftChild);
535             ColumnReference colRef = getColumnReference(leftChild);
536             PropertyDefinition<?> pd = colRef.getPropertyDefinition();
537             Object val = PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
538             if (val==null) {
539                 return null;
540             } else if (val instanceof List<?>) {
541                 throw new IllegalStateException("You can't query operators <, <=, ==, !=, >=, > on multi-value properties ");
542             } else {
543                 return InMemoryQueryProcessor.this.compareTo(pd, val, rVal);
544             }
545         }
546 
547         @SuppressWarnings("unchecked")
548         public List<Object> onLiteralList(Tree node) {
549             return (List<Object>) walkExpr(node);
550         }
551         
552         @Override
553         protected Boolean walkTextAnd(Tree node) {
554             List<Tree> terms = getChildrenAsList(node);
555             for (Tree term: terms) {
556                 Boolean foundOnce = walkSearchExpr(term);
557                 if (foundOnce== null || !foundOnce)
558                     return false;
559             }
560             return true;
561         }
562         
563         @Override
564         protected Boolean walkTextOr(Tree node) {
565             List<Tree> terms = getChildrenAsList(node);
566             for (Tree term: terms) {
567                 Boolean foundOnce = walkSearchExpr(term);
568                 if (foundOnce!= null && foundOnce)
569                     return true;
570             }
571             return false;
572         }
573         
574         @Override
575         protected Boolean walkTextMinus(Tree node) {
576             return !findText(node.getChild(0).getText());
577         }
578         
579         @Override
580         protected Boolean walkTextWord(Tree node) {
581             return findText(node.getText());
582         }
583         
584         @Override
585         protected Boolean walkTextPhrase(Tree node) {
586             String phrase = node.getText();
587             return findText(phrase.substring(1, phrase.length()-1));
588         }
589         
590         private List<Tree> getChildrenAsList(Tree node) {
591             List<Tree> res = new ArrayList<Tree>(node.getChildCount());
592             for (int i=0; i<node.getChildCount(); i++) {
593                 Tree childNnode =  node.getChild(i);
594                 res.add(childNnode);
595             }
596             return res;
597         }
598         
599         private boolean findText(String nodeText) {
600             Content cont;
601             String pattern = StringUtil.unescape(nodeText, null);
602             if (so instanceof Content && (cont=(Content)so).hasContent()) {
603                 ContentStreamDataImpl cdi = (ContentStreamDataImpl) cont.getContent(0, -1);
604                 byte[] ba = cdi.getBytes();
605                 String text = new String(ba);
606                 int match = text.indexOf(pattern);
607                 return match >= 0;
608             }
609             return false;
610         }
611                 
612     }
613     
614     private static boolean hasParent(Filing objInFolder, String folderId, String user) {
615         List<Folder> parents = objInFolder.getParents(user);
616 
617         for (Folder folder : parents) {
618             if (folderId.equals(folder.getId())) {
619                 return true;
620             }
621         }
622         return false;
623     }
624 
625     private boolean hasAncestor(Filing objInFolder, String folderId, String user) {
626         List<Folder> parents = objInFolder.getParents(user);
627 
628         for (Folder folder : parents) {
629             if (folderId.equals(folder.getId())) {
630                 return true;
631             }
632         }
633         for (Folder folder : parents) {
634             if (hasAncestor(folder, folderId, user)) {
635                 return true;
636             }
637         }
638         return false;
639     }
640 
641     protected int compareTo(PropertyDefinition<?> td, Object lValue, Object rVal) {
642         switch (td.getPropertyType()) {
643         case BOOLEAN:
644             if (rVal instanceof Boolean) {
645                 return ((Boolean) lValue).compareTo((Boolean) rVal);
646             } else {
647                 throwIncompatibleTypesException(lValue, rVal);
648             }
649             break;
650         case INTEGER: {
651             Long lLongValue = ((BigInteger) lValue).longValue();
652             if (rVal instanceof Long) {
653                 return (lLongValue).compareTo((Long) rVal);
654             } else if (rVal instanceof Double) {
655                 return Double.valueOf(((Integer) lValue).doubleValue()).compareTo((Double) rVal);
656             } else {
657                 throwIncompatibleTypesException(lValue, rVal);
658             }
659             break;
660         }
661         case DATETIME:
662             if (rVal instanceof GregorianCalendar) {
663                 // LOG.debug("left:" +
664                 // CalendarHelper.toString((GregorianCalendar)lValue) +
665                 // " right: " +
666                 // CalendarHelper.toString((GregorianCalendar)rVal));
667                 return ((GregorianCalendar) lValue).compareTo((GregorianCalendar) rVal);
668             } else {
669                 throwIncompatibleTypesException(lValue, rVal);
670             }
671             break;
672         case DECIMAL: {
673             Double lDoubleValue = ((BigDecimal) lValue).doubleValue();
674             if (rVal instanceof Double) {
675                 return lDoubleValue.compareTo((Double) rVal);
676             } else if (rVal instanceof Long) {
677                 return Double.valueOf(((Integer) lValue).doubleValue()).compareTo(((Long)rVal).doubleValue());
678             } else {
679                 throwIncompatibleTypesException(lValue, rVal);
680             }
681             break;
682         }
683         case HTML:
684         case STRING:
685         case URI:
686         case ID:
687             if (rVal instanceof String) {
688                 LOG.debug("compare strings: " + lValue + " with " + rVal);
689                 return ((String) lValue).compareTo((String) rVal);
690             } else {
691                 throwIncompatibleTypesException(lValue, rVal);
692             }
693             break;
694         }
695         return 0;
696     }
697 
698     private ColumnReference getColumnReference(Tree columnNode) {
699         CmisSelector sel = queryObj.getColumnReference(columnNode.getTokenStartIndex());
700         if (null == sel) {
701             throw new IllegalStateException("Unknown property query name " + columnNode.getChild(0));
702         } else if (sel instanceof ColumnReference) {
703             return (ColumnReference) sel;
704         } else {
705             throw new IllegalStateException("Unexpected numerical value function in where clause");
706         }
707     }
708 
709     private String getTableReference(Tree tableNode) {
710         String typeQueryName = queryObj.getTypeQueryName(tableNode.getText());
711         if (null == typeQueryName) {
712             throw new IllegalStateException("Inavlid type in IN_FOLDER() or IN_TREE(), must be in FROM list: "
713                     + tableNode.getText());
714         }
715         return typeQueryName;
716     }
717 
718     private Object xgetPropertyValue(Tree columnNode, StoredObject so) {
719         ColumnReference colRef = getColumnReference(columnNode);
720         PropertyDefinition<?> pd = colRef.getPropertyDefinition();
721         return PropertyUtil.getProperty(so, colRef.getPropertyId(), pd);
722     }
723 
724     // translate SQL wildcards %, _ to Java regex syntax
725     public static String translatePattern(String wildcardString) {
726         int index = 0;
727         int start = 0;
728         StringBuffer res = new StringBuffer();
729 
730         while (index >= 0) {
731             index = wildcardString.indexOf('%', start);
732             if (index < 0) {
733                 res.append(wildcardString.substring(start));
734             } else if (index == 0 || index > 0 && wildcardString.charAt(index - 1) != '\\') {
735                 res.append(wildcardString.substring(start, index));
736                 res.append(".*");
737             } else {
738                 res.append(wildcardString.substring(start, index + 1));
739             }
740             start = index + 1;
741         }
742         wildcardString = res.toString();
743 
744         index = 0;
745         start = 0;
746         res = new StringBuffer();
747 
748         while (index >= 0) {
749             index = wildcardString.indexOf('_', start);
750             if (index < 0) {
751                 res.append(wildcardString.substring(start));
752             } else if (index == 0 || index > 0 && wildcardString.charAt(index - 1) != '\\') {
753                 res.append(wildcardString.substring(start, index));
754                 res.append(".");
755             } else {
756                 res.append(wildcardString.substring(start, index + 1));
757             }
758             start = index + 1;
759         }
760         return res.toString();
761     }
762 
763     private static void throwIncompatibleTypesException(Object o1, Object o2) {
764         throw new IllegalArgumentException("Incompatible Types to compare: " + o1 + " and " + o2);
765     }
766 
767 }