This project has retired. For details please refer to its Attic page.
EvaluatorXPath 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  
20  package org.apache.chemistry.opencmis.jcr.query;
21  
22  import org.apache.chemistry.opencmis.jcr.util.ISO8601;
23  import org.apache.chemistry.opencmis.jcr.util.Iterables;
24  
25  import java.util.GregorianCalendar;
26  import java.util.List;
27  
28  /**
29   * This implementation of {@link Evaluator} results in an instance of a {@link XPathBuilder} which
30   * can be used to validated the where clause of the original CMIS query and translate it to a
31   * corresponding (i.e. semantically equal) XPath condition. 
32   */
33  public class EvaluatorXPath extends EvaluatorBase<XPathBuilder> {
34  
35      @Override
36      public Evaluator<XPathBuilder> op() {
37          // New instance delegates these methods to this instance in order
38          // to account for the case where these methods are overridden.
39          return new EvaluatorXPath() {
40              @Override
41              protected String jcrPathFromId(String id) {
42                  return EvaluatorXPath.this.jcrPathFromId(id);
43              }
44  
45              @Override
46              protected String jcrPathFromCol(String name) {
47                  return EvaluatorXPath.this.jcrPathFromCol(name);
48              }
49          };
50      }
51  
52      @Override
53      public XPathBuilder not(final XPathBuilder op) {
54          return new XPathBuilder() {
55              public String xPath() {
56                  if (eval(true) != null) {
57                      return eval(true) ? "true()" : "false()";
58                  }
59                  else {
60                      return "not(" + op.xPath() + ")";
61                  }
62              }
63              public Boolean eval(Boolean folderPredicateValuation) {
64                  return not(op.eval(folderPredicateValuation));
65              }
66  
67              public Iterable<XPathBuilder> folderPredicates() {
68                  return op.folderPredicates();
69              }
70          };
71      }
72  
73      @Override
74      public XPathBuilder and(final XPathBuilder op1, final XPathBuilder op2) {
75          return new XPathBuilder() {
76              public String xPath() {
77                  if (eval(true) != null) {
78                      return eval(true) ? "true()" : "false()";
79                  }
80                  else if (op1.eval(true) != null) {  // if not null, op1 must be true -> shortcut evaluation to op2
81                      return op2.xPath();
82                  }
83                  else if (op2.eval(true) != null) {  // if not null, op2 must be true -> shortcut evaluation to op1
84                      return op1.xPath();
85                  }
86                  else {
87                      return op1.xPath() + " and " + op2.xPath();
88                  }
89              }
90              public Boolean eval(Boolean folderPredicateValuation) {
91                  return and(op1.eval(folderPredicateValuation), op2.eval(folderPredicateValuation));
92              }
93  
94              public Iterable<XPathBuilder> folderPredicates() {
95                  return Iterables.concat(op1.folderPredicates(), op2.folderPredicates());
96              }
97          };
98      }
99  
100     @Override
101     public XPathBuilder or(final XPathBuilder op1, final XPathBuilder op2) {
102         return new XPathBuilder() {
103             public String xPath() {
104                 if (eval(true) != null) {
105                     return eval(true) ? "true()" : "false()";
106                 }
107                 else if (op1.eval(true) != null) {  // if not null, op1 must be false -> shortcut evaluation to op2
108                     return op2.xPath();
109                 }
110                 else if (op2.eval(true) != null) {  // if not null, op2 must be false -> shortcut evaluation to op2
111                     return op1.xPath();
112                 }
113                 else {
114                     return "(" + op1.xPath() + " or " + op2.xPath() + ")";
115                 }
116             }
117             public Boolean eval(Boolean folderPredicateValuation) {
118                 return or(op1.eval(folderPredicateValuation), op2.eval(folderPredicateValuation));
119             }
120 
121             public Iterable<XPathBuilder> folderPredicates() {
122                 return Iterables.concat(op1.folderPredicates(), op2.folderPredicates());
123             }
124         };
125     }
126 
127     @Override
128     public XPathBuilder eq(XPathBuilder op1, XPathBuilder op2) {
129         return new RelOpBuilder(op1, " = ", op2);
130     }
131 
132     @Override
133     public XPathBuilder neq(XPathBuilder op1, XPathBuilder op2) {
134         return new RelOpBuilder(op1, " != ", op2);
135     }
136 
137     @Override
138     public XPathBuilder gt(XPathBuilder op1, XPathBuilder op2) {
139         return new RelOpBuilder(op1, " > ", op2);
140     }
141 
142     @Override
143     public XPathBuilder gteq(XPathBuilder op1, XPathBuilder op2) {
144         return new RelOpBuilder(op1, " >= ", op2);
145     }
146 
147     @Override
148     public XPathBuilder lt(XPathBuilder op1, XPathBuilder op2) {
149         return new RelOpBuilder(op1, " < ", op2);
150     }
151 
152     @Override
153     public XPathBuilder lteq(XPathBuilder op1, XPathBuilder op2) {
154         return new RelOpBuilder(op1, " <= ", op2);
155     }
156 
157     @Override
158     public XPathBuilder in(XPathBuilder op1, XPathBuilder op2) {
159         return super.in(op1, op2);    // todo implement in
160     }
161 
162     @Override
163     public XPathBuilder notIn(XPathBuilder op1, XPathBuilder op2) {
164         return super.notIn(op1, op2);    // todo implement notIn
165     }
166 
167     @Override
168     public XPathBuilder inAny(XPathBuilder op1, XPathBuilder op2) {
169         return super.inAny(op1, op2);    // todo implement inAny
170     }
171 
172     @Override
173     public XPathBuilder notInAny(XPathBuilder op1, XPathBuilder op2) {
174         return super.notInAny(op1, op2);    // todo implement notInAny
175     }
176 
177     @Override
178     public XPathBuilder eqAny(XPathBuilder op1, XPathBuilder op2) {
179         return super.eqAny(op1, op2);    // todo implement eqAny
180     }
181 
182     @Override
183     public XPathBuilder isNull(XPathBuilder op) {
184         return new FunctionBuilder(op);
185     }
186 
187     @Override
188     public XPathBuilder notIsNull(XPathBuilder op) {
189         return new FunctionBuilder("not", op);
190     }
191 
192     @Override
193     public XPathBuilder like(XPathBuilder op1, XPathBuilder op2) {
194         return new FunctionBuilder("jcr:like", op1, op2);
195     }
196 
197     @Override
198     public XPathBuilder notLike(XPathBuilder op1, XPathBuilder op2) {
199         return new FunctionBuilder("jcr:like", op1, op2) {
200             @Override
201             public String xPath() {
202                 return "not(" + super.xPath() + ")"; 
203             }
204         };
205     }
206 
207     @Override
208     public XPathBuilder contains(XPathBuilder op1, XPathBuilder op2) {
209         return new FunctionBuilder("jcr:contains", ".", op2);
210     }
211 
212     @Override
213     public XPathBuilder inFolder(XPathBuilder op1,XPathBuilder op2) {
214         return new FolderPredicateBuilder(op2.xPath(), false);
215     }
216 
217     @Override
218     public XPathBuilder inTree(XPathBuilder op1, XPathBuilder op2) {
219         return new FolderPredicateBuilder(op2.xPath(), true);
220     }
221 
222     @Override
223     public XPathBuilder list(List<XPathBuilder> ops) {
224         return super.list(ops);    // todo implement list
225     }
226 
227     @Override
228     public XPathBuilder value(boolean value) {
229         return new LiteralBuilder(value);
230     }
231 
232     @Override
233     public XPathBuilder value(double value) {
234         return new LiteralBuilder(value);
235     }
236 
237     @Override
238     public XPathBuilder value(long value) {
239         return new LiteralBuilder(value);
240     }
241 
242     @Override
243     public XPathBuilder value(String value) {
244         return new LiteralBuilder(value);
245     }
246 
247     @Override
248     public XPathBuilder value(GregorianCalendar value) {
249         return new LiteralBuilder(value);
250     }
251 
252     @Override
253     public XPathBuilder col(String name) {
254         return new ColRefBuilder(name);
255     }
256 
257     //------------------------------------------< protected >---
258 
259     /**
260      * Resolve from a CMIS object id to the corresponding absolute JCR path.
261      * This default implementations simply returns <code>id</code>.
262      */
263     protected String jcrPathFromId(String id) {
264         return id;
265     }
266 
267     /**
268      * Resolve from a column name in the query to the corresponding
269      * relative JCR path. The path must be relative to the context node.
270      * This default implementations simply returns <code>name</code>.
271      */
272     protected String jcrPathFromCol(String name) {
273         return name; 
274     }
275 
276     //------------------------------------------< private >---
277 
278     /**
279      * @return  <code>null</code> if <code>b</code> is <code>null</code>, <code>!b</code> otherwise.
280      */
281     private static Boolean not(Boolean b) {
282         return b == null ? null : !b;
283     }
284 
285     /**
286      * @return 
287      *  <ul><li><code>true</code> if either of <code>b1</code> and <code>b2</code> is <code>true</code>,</li>
288      *      <li><code>false</code> if both <code>b1</code> and <code>b2</code> are <code>false</code>,</li>
289      *      <li><code>null</code> otherwise.</li></ul>
290      */
291     private static Boolean or(Boolean b1, Boolean b2) {
292         return Boolean.TRUE.equals(b1) || Boolean.TRUE.equals(b2)
293                 ? Boolean.TRUE
294                 : Boolean.FALSE.equals(b1) && Boolean.FALSE.equals(b2)
295                         ? Boolean.FALSE
296                         : null;
297     }
298 
299     /**
300      * @return
301      *  <ul><li><code>false</code> if either of <code>b1</code> and <code>b2</code> is <code>false</code>,</li>
302      *      <li><code>true</code> if both <code>b1</code> and <code>b2</code> are <code>true</code>,</li>
303      *      <li><code>null</code> otherwise.</li></ul>
304      */
305     private static Boolean and(Boolean b1, Boolean b2) {
306         return Boolean.FALSE.equals(b1) || Boolean.FALSE.equals(b2)
307                 ? Boolean.FALSE
308                 : Boolean.TRUE.equals(b1) && Boolean.TRUE.equals(b2)
309                         ? Boolean.TRUE
310                         : null;
311     }
312 
313     private static class RelOpBuilder implements XPathBuilder {
314         private final String relOp;
315         private final XPathBuilder op1;
316         private final XPathBuilder op2;
317 
318         public RelOpBuilder(XPathBuilder op1, String relOp, XPathBuilder op2) {
319             this.relOp = relOp;
320             this.op1 = op1;
321             this.op2 = op2;
322         }
323 
324         public String xPath() {
325             return op1.xPath() + relOp + op2.xPath();
326         }
327 
328         public Boolean eval(Boolean folderPredicateValuation) {
329             return null;
330         }
331 
332         public Iterable<XPathBuilder> folderPredicates() {
333             return Iterables.concat(op1.folderPredicates(), op2.folderPredicates());
334         }
335     }
336 
337     private class FolderPredicateBuilder implements XPathBuilder {
338         private final String folderId;
339         private final boolean includeDescendants;
340         
341         public FolderPredicateBuilder(String folderId, boolean includeDescendants) {
342             this.folderId = stripQuotes(folderId);
343             this.includeDescendants = includeDescendants;
344         }
345 
346         public String xPath() {
347             return jcrPathFromId(folderId) + (includeDescendants ? "//" : "/");
348         }
349 
350         public Boolean eval(Boolean folderPredicateValuation) {
351             return folderPredicateValuation;
352         }
353 
354         public Iterable<XPathBuilder> folderPredicates() {
355             return Iterables.singleton((XPathBuilder) this);
356         }
357 
358         private String stripQuotes(String string) {
359             return (string.startsWith("'") || string.startsWith("\"")) && string.length() >= 2
360                     ? string.substring(1, string.length() - 1)
361                     : string;
362         }
363 
364     }
365 
366     private abstract static class PrimitiveBuilder implements XPathBuilder {
367         public Boolean eval(Boolean folderPredicateValuation) {
368             return null;
369         }
370 
371         public Iterable<XPathBuilder> folderPredicates() {
372             return Iterables.empty();
373         }
374     }
375 
376     private static class LiteralBuilder extends PrimitiveBuilder  {
377         private final String xPath;
378 
379         public LiteralBuilder(String value) {
380             xPath = "'" + value + "'";
381         }
382 
383         public LiteralBuilder(boolean value) {
384             xPath = Boolean.toString(value);
385         }
386 
387         public LiteralBuilder(long value) {
388             xPath = Long.toString(value);
389         }
390 
391         public LiteralBuilder(double value) {
392             xPath = Double.toString(value);
393         }
394 
395         public LiteralBuilder(GregorianCalendar value) {
396             xPath = "xs:dateTime('" + ISO8601.format(value) + "')";
397         }
398 
399         public String xPath() {
400             return xPath;
401         }
402     }
403 
404     private class ColRefBuilder extends PrimitiveBuilder  {
405         private final String colRef;
406 
407         public ColRefBuilder(String colRef) {
408             this.colRef = colRef;
409         }
410 
411         public String xPath() {
412             return jcrPathFromCol(colRef);   
413         }
414 
415     }
416 
417     private static class FunctionBuilder extends PrimitiveBuilder {
418         private final String function;
419         private String op1Str;
420         private final XPathBuilder op1;
421         private final XPathBuilder op2;
422 
423         private FunctionBuilder(String function, String op1Str, XPathBuilder op1, XPathBuilder op2) {
424             this.function = function;
425             this.op1Str = op1Str;
426             this.op1 = op1;
427             this.op2 = op2;
428         }
429 
430         public FunctionBuilder(String function, XPathBuilder op1, XPathBuilder op2) {
431             this(function, null, op1, op2);
432         }
433 
434         public FunctionBuilder(String function, XPathBuilder op1) {
435             this(function, null, op1, null);
436         }
437 
438         public FunctionBuilder(String function, String op1, XPathBuilder op2) {
439             this(function, op1, null, op2);
440 
441         }
442 
443         public FunctionBuilder(XPathBuilder op1) {
444             this(null, op1, null);
445         }
446 
447         public String xPath() {
448             if (op1Str == null) {
449                 op1Str = op1.xPath();
450             }
451 
452             return function == null
453                     ? op1Str
454                     : function + "(" + op1Str + (op2 == null ? "" : ", " + op2.xPath()) + ")";
455         }
456 
457     }
458 
459 }