1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.jcr.xpath;
25
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import org.modeshape.graph.property.PropertyType;
36 import org.modeshape.graph.query.QueryBuilder;
37 import org.modeshape.graph.query.QueryBuilder.ConstraintBuilder;
38 import org.modeshape.graph.query.QueryBuilder.OrderByBuilder;
39 import org.modeshape.graph.query.QueryBuilder.OrderByOperandBuilder;
40 import org.modeshape.graph.query.model.AllNodes;
41 import org.modeshape.graph.query.model.Operator;
42 import org.modeshape.graph.query.model.Query;
43 import org.modeshape.graph.query.model.QueryCommand;
44 import org.modeshape.graph.query.model.TypeSystem;
45 import org.modeshape.graph.query.parse.InvalidQueryException;
46 import org.modeshape.jcr.xpath.XPath.And;
47 import org.modeshape.jcr.xpath.XPath.AttributeNameTest;
48 import org.modeshape.jcr.xpath.XPath.AxisStep;
49 import org.modeshape.jcr.xpath.XPath.BinaryComponent;
50 import org.modeshape.jcr.xpath.XPath.Comparison;
51 import org.modeshape.jcr.xpath.XPath.Component;
52 import org.modeshape.jcr.xpath.XPath.ContextItem;
53 import org.modeshape.jcr.xpath.XPath.DescendantOrSelf;
54 import org.modeshape.jcr.xpath.XPath.ElementTest;
55 import org.modeshape.jcr.xpath.XPath.Except;
56 import org.modeshape.jcr.xpath.XPath.FilterStep;
57 import org.modeshape.jcr.xpath.XPath.FunctionCall;
58 import org.modeshape.jcr.xpath.XPath.Intersect;
59 import org.modeshape.jcr.xpath.XPath.Literal;
60 import org.modeshape.jcr.xpath.XPath.NameTest;
61 import org.modeshape.jcr.xpath.XPath.NodeTest;
62 import org.modeshape.jcr.xpath.XPath.Or;
63 import org.modeshape.jcr.xpath.XPath.OrderBy;
64 import org.modeshape.jcr.xpath.XPath.OrderBySpec;
65 import org.modeshape.jcr.xpath.XPath.ParenthesizedExpression;
66 import org.modeshape.jcr.xpath.XPath.PathExpression;
67 import org.modeshape.jcr.xpath.XPath.StepExpression;
68 import org.modeshape.jcr.xpath.XPath.Union;
69
70
71
72
73
74 public class XPathToQueryTranslator {
75
76 protected static final Map<NameTest, String> CAST_FUNCTION_NAME_TO_TYPE;
77
78 static {
79 Map<NameTest, String> map = new HashMap<NameTest, String>();
80 map.put(new NameTest("fn", "string"), PropertyType.STRING.getName().toUpperCase());
81 map.put(new NameTest("xs", "string"), PropertyType.STRING.getName().toUpperCase());
82 map.put(new NameTest("xs", "base64Binary"), PropertyType.BINARY.getName().toUpperCase());
83 map.put(new NameTest("xs", "double"), PropertyType.DOUBLE.getName().toUpperCase());
84 map.put(new NameTest("xs", "long"), PropertyType.LONG.getName().toUpperCase());
85 map.put(new NameTest("xs", "boolean"), PropertyType.BOOLEAN.getName().toUpperCase());
86 map.put(new NameTest("xs", "dateTime"), PropertyType.DATE.getName().toUpperCase());
87 map.put(new NameTest("xs", "string"), PropertyType.PATH.getName().toUpperCase());
88 map.put(new NameTest("xs", "string"), PropertyType.NAME.getName().toUpperCase());
89 map.put(new NameTest("xs", "IDREF"), PropertyType.REFERENCE.getName().toUpperCase());
90 CAST_FUNCTION_NAME_TO_TYPE = Collections.unmodifiableMap(map);
91 }
92
93 private final String query;
94 private final TypeSystem typeSystem;
95 private final QueryBuilder builder;
96 private final Set<String> aliases = new HashSet<String>();
97
98 public XPathToQueryTranslator( TypeSystem context,
99 String query ) {
100 this.query = query;
101 this.typeSystem = context;
102 this.builder = new QueryBuilder(this.typeSystem);
103 }
104
105 public QueryCommand createQuery( Component xpath ) {
106 if (xpath instanceof BinaryComponent) {
107 BinaryComponent binary = (BinaryComponent)xpath;
108 if (binary instanceof Union) {
109 createQuery(binary.getLeft());
110 builder.union();
111 createQuery(binary.getRight());
112 return builder.query();
113 } else if (binary instanceof Intersect) {
114 createQuery(binary.getLeft());
115 builder.intersect();
116 createQuery(binary.getRight());
117 return builder.query();
118 } else if (binary instanceof Except) {
119 createQuery(binary.getLeft());
120 builder.except();
121 createQuery(binary.getRight());
122 return builder.query();
123 }
124 } else if (xpath instanceof PathExpression) {
125 translate((PathExpression)xpath);
126 return builder.query();
127 }
128
129 throw new InvalidQueryException(query,
130 "Acceptable XPath queries must lead with a path expression or must be a union, intersect or except");
131 }
132
133 protected void translate( PathExpression pathExpression ) {
134 List<StepExpression> steps = pathExpression.getSteps();
135 assert !steps.isEmpty();
136 if (!pathExpression.isRelative()) {
137
138 Component first = steps.get(0).collapse();
139
140 if (first instanceof DescendantOrSelf) {
141
142 } else if (first instanceof NameTest && steps.size() == 1 && ((NameTest)first).matches("jcr", "root")) {
143
144 steps = steps.subList(1, steps.size());
145 } else if (first instanceof NameTest && steps.size() > 1 && ((NameTest)first).matches("jcr", "root")) {
146
147 steps = steps.subList(1, steps.size());
148 } else {
149 throw new InvalidQueryException(query, "An absolute path expression must start with '//' or '/jcr:root/...'");
150 }
151 }
152
153
154 ConstraintBuilder where = builder.where();
155 List<StepExpression> path = new ArrayList<StepExpression>();
156 String tableName = null;
157 for (StepExpression step : steps) {
158 if (step instanceof AxisStep) {
159 AxisStep axis = (AxisStep)step;
160 NodeTest nodeTest = axis.getNodeTest();
161 if (nodeTest instanceof NameTest) {
162 if (appliesToPathConstraint(axis.getPredicates())) {
163
164 path.add(step);
165 } else {
166
167
168
169 path.add(step);
170
171
172 tableName = translateSource(tableName, path, where);
173 translatePredicates(axis.getPredicates(), tableName, where);
174 path.clear();
175 }
176 } else if (nodeTest instanceof ElementTest) {
177
178 tableName = translateElementTest((ElementTest)nodeTest, path, where);
179 translatePredicates(axis.getPredicates(), tableName, where);
180 path.clear();
181 } else if (nodeTest instanceof AttributeNameTest) {
182 AttributeNameTest attributeName = (AttributeNameTest)nodeTest;
183 builder.select(nameFrom(attributeName.getNameTest()));
184 } else {
185 throw new InvalidQueryException(query, "The '" + step + "' step is not supported");
186 }
187 } else if (step instanceof FilterStep) {
188 FilterStep filter = (FilterStep)step;
189 Component primary = filter.getPrimaryExpression();
190 List<Component> predicates = filter.getPredicates();
191 if (primary instanceof ContextItem) {
192 if (appliesToPathConstraint(predicates)) {
193
194 } else {
195
196 path.add(step);
197 tableName = translateSource(tableName, path, where);
198 translatePredicates(predicates, tableName, where);
199 path.clear();
200 }
201 } else if (primary instanceof Literal) {
202 throw new InvalidQueryException(query,
203 "A literal is not supported in the primary path expression; therefore '"
204 + primary + "' is not valid");
205 } else if (primary instanceof FunctionCall) {
206 throw new InvalidQueryException(query,
207 "A function call is not supported in the primary path expression; therefore '"
208 + primary + "' is not valid");
209 } else if (primary instanceof ParenthesizedExpression) {
210
211 ParenthesizedExpression paren = (ParenthesizedExpression)primary;
212 Component wrapped = paren.getWrapped().collapse();
213 if (wrapped instanceof AttributeNameTest) {
214 AttributeNameTest attributeName = (AttributeNameTest)wrapped;
215 builder.select(nameFrom(attributeName.getNameTest()));
216 } else if (wrapped instanceof BinaryComponent) {
217 for (AttributeNameTest attributeName : extractAttributeNames((BinaryComponent)wrapped)) {
218 builder.select(nameFrom(attributeName.getNameTest()));
219 }
220 path.add(filter);
221 } else {
222 throw new InvalidQueryException(query,
223 "A parenthesized expression of this type is not supported in the primary path expression; therefore '"
224 + primary + "' is not valid");
225 }
226 }
227
228 } else {
229 path.add(step);
230 }
231 }
232 if (steps.isEmpty() || !path.isEmpty()) {
233 translateSource(tableName, path, where);
234 }
235 where.end();
236
237
238 OrderBy orderBy = pathExpression.getOrderBy();
239 if (orderBy != null) {
240 OrderByBuilder orderByBuilder = builder.orderBy();
241 for (OrderBySpec spec : orderBy) {
242 OrderByOperandBuilder operandBuilder = null;
243 switch (spec.getOrder()) {
244 case ASCENDING:
245 operandBuilder = orderByBuilder.ascending();
246 break;
247 case DESCENDING:
248 operandBuilder = orderByBuilder.descending();
249 break;
250 }
251 assert operandBuilder != null;
252 if (spec.getAttributeName() != null) {
253
254 NameTest attribute = spec.getAttributeName();
255 assert !attribute.isWildcard();
256 if (attribute.matches("jcr", "path")) {
257 String pathOf = tableName;
258 if (pathOf == null) pathOf = aliases.iterator().next();
259 operandBuilder.path(pathOf);
260 } else {
261 operandBuilder.propertyValue(tableName, attribute.toString());
262 builder.select(tableName + "." + attribute.toString());
263 }
264 } else {
265
266 FunctionCall scoreFunction = spec.getScoreFunction();
267 assert scoreFunction != null;
268 List<Component> args = scoreFunction.getParameters();
269 String nameOfTableToScore = tableName;
270 if (!args.isEmpty()) {
271 if (args.size() == 1 && args.get(0) instanceof NameTest) {
272
273 NameTest tableNameTest = (NameTest)args.get(0);
274 nameOfTableToScore = tableNameTest.toString();
275 }
276 }
277 operandBuilder.fullTextSearchScore(nameOfTableToScore);
278 }
279 }
280 orderByBuilder.end();
281 }
282
283 Query query = (Query)builder.query();
284 if (query.columns().isEmpty() && query.source() instanceof AllNodes) {
285
286
287
288
289 builder.select("jcr:primaryType");
290 }
291 }
292
293
294
295
296
297
298
299
300 protected List<AttributeNameTest> extractAttributeNames( BinaryComponent binary ) {
301 List<AttributeNameTest> results = new ArrayList<AttributeNameTest>();
302 boolean failed = false;
303 if (binary instanceof Union) {
304 for (int i = 0; i != 2; ++i) {
305 Component comp = i == 0 ? binary.getLeft() : binary.getRight();
306 comp = comp.collapse();
307 if (comp instanceof Union) {
308 results.addAll(extractAttributeNames((BinaryComponent)comp));
309 } else if (comp instanceof AttributeNameTest) {
310 results.add((AttributeNameTest)comp);
311 } else if (comp instanceof NameTest) {
312
313 } else {
314 failed = true;
315 break;
316 }
317 }
318 } else {
319 failed = true;
320 }
321 if (failed) {
322 throw new InvalidQueryException(query,
323 "A parenthesized expression in a path step may only contain ORed and ANDed attribute names or element names; therefore '"
324 + binary + "' is not valid");
325 }
326 return results;
327 }
328
329
330
331
332
333
334
335
336 protected List<NameTest> extractElementNames( BinaryComponent binary ) {
337 List<NameTest> results = new ArrayList<NameTest>();
338 boolean failed = false;
339 if (binary instanceof Union) {
340 for (int i = 0; i != 2; ++i) {
341 Component comp = i == 0 ? binary.getLeft() : binary.getRight();
342 comp = comp.collapse();
343 if (comp instanceof Union) {
344 results.addAll(extractElementNames((BinaryComponent)comp));
345 } else if (comp instanceof AttributeNameTest) {
346
347 } else if (comp instanceof NameTest) {
348 results.add((NameTest)comp);
349 } else {
350 failed = true;
351 break;
352 }
353 }
354 } else {
355 failed = true;
356 }
357 if (failed) {
358 throw new InvalidQueryException(query,
359 "A parenthesized expression in a path step may only contain ORed element names; therefore '"
360 + binary + "' is not valid");
361 }
362 return results;
363 }
364
365 protected String translateSource( String tableName,
366 List<StepExpression> path,
367 ConstraintBuilder where ) {
368 if (path.size() == 0) {
369
370 String alias = newAlias();
371 builder.fromAllNodesAs(alias);
372 where.path(alias).isEqualTo("/");
373 return alias;
374 }
375 String alias = newAlias();
376 if (tableName != null) {
377
378 builder.joinAllNodesAs(alias);
379 } else {
380
381 builder.fromAllNodesAs(alias);
382 }
383 tableName = alias;
384 if (path.size() == 1 && path.get(0).collapse() instanceof NameTest) {
385
386 NameTest nodeName = (NameTest)path.get(0).collapse();
387 where.path(alias).isLike("/" + nameFrom(nodeName) + "[%]");
388 } else if (path.size() == 2 && path.get(0) instanceof DescendantOrSelf && path.get(1).collapse() instanceof NameTest) {
389
390 NameTest nodeName = (NameTest)path.get(1).collapse();
391 if (!nodeName.isWildcard()) {
392 where.nodeName(alias).isEqualTo(nameFrom(nodeName));
393 }
394 } else {
395
396 translatePathExpressionConstraint(new PathExpression(true, path, null), where, alias);
397 }
398 return tableName;
399 }
400
401 protected String translateElementTest( ElementTest elementTest,
402 List<StepExpression> pathConstraint,
403 ConstraintBuilder where ) {
404 String tableName = null;
405 NameTest typeName = elementTest.getTypeName();
406 if (typeName.isWildcard()) {
407 tableName = newAlias();
408 builder.fromAllNodesAs(tableName);
409 } else {
410 if (typeName.getLocalTest() == null) {
411 throw new InvalidQueryException(
412 query,
413 "The '"
414 + elementTest
415 + "' clause uses a partial wildcard in the type name, but only a wildcard on the whole name is supported");
416 }
417 tableName = nameFrom(typeName);
418 builder.from(tableName);
419 }
420 if (elementTest.getElementName() != null) {
421 NameTest nodeName = elementTest.getElementName();
422 if (!nodeName.isWildcard()) {
423 where.nodeName(tableName).isEqualTo(nameFrom(nodeName));
424 }
425 }
426 if (pathConstraint.isEmpty()) {
427 where.depth(tableName).isEqualTo(1);
428 } else {
429 List<StepExpression> path = new ArrayList<StepExpression>(pathConstraint);
430 if (!path.isEmpty() && path.get(path.size() - 1) instanceof AxisStep) {
431
432 path.add(new AxisStep(new NameTest(null, null), Collections.<Component>emptyList()));
433 }
434 translatePathExpressionConstraint(new PathExpression(true, path, null), where, tableName);
435 }
436 return tableName;
437 }
438
439 protected void translatePredicates( List<Component> predicates,
440 String tableName,
441 ConstraintBuilder where ) {
442 assert tableName != null;
443 for (Component predicate : predicates) {
444 translatePredicate(predicate, tableName, where);
445 }
446 }
447
448 protected String translatePredicate( Component predicate,
449 String tableName,
450 ConstraintBuilder where ) {
451 predicate = predicate.collapse();
452 assert tableName != null;
453 if (predicate instanceof ParenthesizedExpression) {
454 ParenthesizedExpression paren = (ParenthesizedExpression)predicate;
455 where = where.openParen();
456 translatePredicate(paren.getWrapped(), tableName, where);
457 where.closeParen();
458 } else if (predicate instanceof And) {
459 And and = (And)predicate;
460 where = where.openParen();
461 translatePredicate(and.getLeft(), tableName, where);
462 where.and();
463 translatePredicate(and.getRight(), tableName, where);
464 where.closeParen();
465 } else if (predicate instanceof Or) {
466 Or or = (Or)predicate;
467 where = where.openParen();
468 translatePredicate(or.getLeft(), tableName, where);
469 where.or();
470 translatePredicate(or.getRight(), tableName, where);
471 where.closeParen();
472 } else if (predicate instanceof Union) {
473 Union union = (Union)predicate;
474 where = where.openParen();
475 translatePredicate(union.getLeft(), tableName, where);
476 where.or();
477 translatePredicate(union.getRight(), tableName, where);
478 where.closeParen();
479 } else if (predicate instanceof Literal) {
480 Literal literal = (Literal)predicate;
481 if (literal.isInteger()) return tableName;
482 } else if (predicate instanceof AttributeNameTest) {
483
484 AttributeNameTest attribute = (AttributeNameTest)predicate;
485 String propertyName = nameFrom(attribute.getNameTest());
486
487
488
489 where.hasProperty(tableName, propertyName);
490 } else if (predicate instanceof NameTest) {
491
492 NameTest childName = (NameTest)predicate;
493 String alias = newAlias();
494 builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
495 if (!childName.isWildcard()) where.nodeName(alias).isEqualTo(nameFrom(childName));
496 tableName = alias;
497 } else if (predicate instanceof Comparison) {
498 Comparison comparison = (Comparison)predicate;
499 Component left = comparison.getLeft();
500 Component right = comparison.getRight();
501 Operator operator = comparison.getOperator();
502 if (left instanceof Literal) {
503 Component temp = left;
504 left = right;
505 right = temp;
506 operator = operator.reverse();
507 }
508 if (left instanceof NodeTest) {
509 NodeTest nodeTest = (NodeTest)left;
510 String propertyName = null;
511 if (nodeTest instanceof AttributeNameTest) {
512 AttributeNameTest attribute = (AttributeNameTest)left;
513 propertyName = nameFrom(attribute.getNameTest());
514 } else if (nodeTest instanceof NameTest) {
515 NameTest nameTest = (NameTest)left;
516 propertyName = nameFrom(nameTest);
517 } else {
518 throw new InvalidQueryException(query,
519 "Left hand side of a comparison must be a name test or attribute name test; therefore '"
520 + comparison + "' is not valid");
521 }
522 if (right instanceof Literal) {
523 String value = ((Literal)right).getValue();
524 where.propertyValue(tableName, propertyName).is(operator, value);
525 } else if (right instanceof FunctionCall) {
526 FunctionCall call = (FunctionCall)right;
527 NameTest functionName = call.getName();
528 List<Component> parameters = call.getParameters();
529
530 String castType = CAST_FUNCTION_NAME_TO_TYPE.get(functionName);
531 if (castType != null) {
532 if (parameters.size() == 1 && parameters.get(0).collapse() instanceof Literal) {
533
534 Literal value = (Literal)parameters.get(0).collapse();
535 where.propertyValue(tableName, propertyName).is(operator).cast(value.getValue()).as(castType);
536 } else {
537 throw new InvalidQueryException(query, "A cast function requires one literal parameter; therefore '"
538 + comparison + "' is not valid");
539 }
540 } else {
541 throw new InvalidQueryException(query,
542 "Only the 'jcr:score' function is allowed in a comparison predicate; therefore '"
543 + comparison + "' is not valid");
544 }
545 }
546 } else if (left instanceof FunctionCall && right instanceof Literal) {
547 FunctionCall call = (FunctionCall)left;
548 NameTest functionName = call.getName();
549 List<Component> parameters = call.getParameters();
550 String value = ((Literal)right).getValue();
551 if (functionName.matches("jcr", "score")) {
552 String scoreTableName = tableName;
553 if (parameters.isEmpty()) {
554 scoreTableName = tableName;
555 } else if (parameters.size() == 1 && parameters.get(0) instanceof NameTest) {
556
557 NameTest name = (NameTest)parameters.get(0);
558 if (!name.isWildcard()) scoreTableName = nameFrom(name);
559 } else {
560 throw new InvalidQueryException(query,
561 "The 'jcr:score' function may have no parameters or the type name as the only parameter.");
562
563 }
564 where.fullTextSearchScore(scoreTableName).is(operator, value);
565 } else {
566 throw new InvalidQueryException(query,
567 "Only the 'jcr:score' function is allowed in a comparison predicate; therefore '"
568 + comparison + "' is not valid");
569 }
570 }
571 } else if (predicate instanceof FunctionCall) {
572 FunctionCall call = (FunctionCall)predicate;
573 NameTest functionName = call.getName();
574 List<Component> parameters = call.getParameters();
575 Component param1 = parameters.size() > 0 ? parameters.get(0) : null;
576 Component param2 = parameters.size() > 1 ? parameters.get(1) : null;
577 if (functionName.matches(null, "not")) {
578 if (parameters.size() != 1) {
579 throw new InvalidQueryException(query, "The 'not' function requires one parameter; therefore '" + predicate
580 + "' is not valid");
581 }
582 where = where.not().openParen();
583 translatePredicate(param1, tableName, where);
584 where.closeParen();
585 } else if (functionName.matches("jcr", "like")) {
586 if (parameters.size() != 2) {
587 throw new InvalidQueryException(query, "The 'jcr:like' function requires two parameters; therefore '"
588 + predicate + "' is not valid");
589 }
590 if (!(param1 instanceof AttributeNameTest)) {
591 throw new InvalidQueryException(query,
592 "The first parameter of 'jcr:like' must be an property reference with the '@' symbol; therefore '"
593 + predicate + "' is not valid");
594 }
595 if (!(param2 instanceof Literal)) {
596 throw new InvalidQueryException(query, "The second parameter of 'jcr:like' must be a literal; therefore '"
597 + predicate + "' is not valid");
598 }
599 NameTest attributeName = ((AttributeNameTest)param1).getNameTest();
600 String value = ((Literal)param2).getValue();
601 where.propertyValue(tableName, nameFrom(attributeName)).isLike(value);
602 } else if (functionName.matches("jcr", "contains")) {
603 if (parameters.size() != 2) {
604 throw new InvalidQueryException(query, "The 'jcr:contains' function requires two parameters; therefore '"
605 + predicate + "' is not valid");
606 }
607 if (!(param2 instanceof Literal)) {
608 throw new InvalidQueryException(query,
609 "The second parameter of 'jcr:contains' must be a literal; therefore '"
610 + predicate + "' is not valid");
611 }
612 String value = ((Literal)param2).getValue();
613 if (param1 instanceof ContextItem) {
614
615 where.search(tableName, value);
616 } else if (param1 instanceof AttributeNameTest) {
617
618 NameTest attributeName = ((AttributeNameTest)param1).getNameTest();
619 where.search(tableName, nameFrom(attributeName), value);
620 } else if (param1 instanceof NameTest) {
621
622 String alias = newAlias();
623 builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
624
625 where.search(alias, value);
626 tableName = alias;
627 } else if (param1 instanceof PathExpression) {
628
629 PathExpression pathExpr = (PathExpression)param1;
630 if (pathExpr.getLastStep().collapse() instanceof AttributeNameTest) {
631 AttributeNameTest attributeName = (AttributeNameTest)pathExpr.getLastStep().collapse();
632 pathExpr = pathExpr.withoutLast();
633 String searchTable = translatePredicate(pathExpr, tableName, where);
634 if (attributeName.getNameTest().isWildcard()) {
635 where.search(searchTable, value);
636 } else {
637 where.search(searchTable, nameFrom(attributeName.getNameTest()), value);
638 }
639 } else {
640 String searchTable = translatePredicate(param1, tableName, where);
641 where.search(searchTable, value);
642 }
643 } else {
644 throw new InvalidQueryException(query,
645 "The first parameter of 'jcr:contains' must be a relative path (e.g., '.', an attribute name, a child name, etc.); therefore '"
646 + predicate + "' is not valid");
647 }
648 } else if (functionName.matches("jcr", "deref")) {
649 throw new InvalidQueryException(query,
650 "The 'jcr:deref' function is not required by JCR and is not currently supported; therefore '"
651 + predicate + "' is not valid");
652 } else {
653 throw new InvalidQueryException(query,
654 "Only the 'jcr:like' and 'jcr:contains' functions are allowed in a predicate; therefore '"
655 + predicate + "' is not valid");
656 }
657 } else if (predicate instanceof PathExpression) {
658
659 PathExpression pathExpr = (PathExpression)predicate;
660 List<StepExpression> steps = pathExpr.getSteps();
661 OrderBy orderBy = pathExpr.getOrderBy();
662 assert steps.size() > 1;
663 Component firstStep = steps.get(0).collapse();
664 if (firstStep instanceof ContextItem) {
665
666 return translatePredicate(new PathExpression(true, steps.subList(1, steps.size()), orderBy), tableName, where);
667 }
668 if (firstStep instanceof NameTest) {
669
670 NameTest childName = (NameTest)firstStep;
671 String alias = newAlias();
672 builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
673 if (!childName.isWildcard()) {
674 where.nodeName(alias).isEqualTo(nameFrom(childName));
675 }
676 return translatePredicate(new PathExpression(true, steps.subList(1, steps.size()), orderBy), alias, where);
677 }
678 if (firstStep instanceof DescendantOrSelf) {
679
680 String alias = newAlias();
681 builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
682 return translatePredicate(new PathExpression(true, steps.subList(1, steps.size()), orderBy), alias, where);
683 }
684
685 String alias = newAlias();
686 builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
687
688 translatePathExpressionConstraint(pathExpr, where, alias);
689 } else {
690 throw new InvalidQueryException(query, "Unsupported criteria '" + predicate + "'");
691 }
692 return tableName;
693 }
694
695
696
697
698
699
700
701
702 protected boolean appliesToPathConstraint( List<Component> predicates ) {
703 if (predicates.isEmpty()) return true;
704 if (predicates.size() > 1) return false;
705 assert predicates.size() == 1;
706 Component predicate = predicates.get(0);
707 if (predicate instanceof Literal && ((Literal)predicate).isInteger()) return true;
708 if (predicate instanceof NameTest && ((NameTest)predicate).isWildcard()) return true;
709 return false;
710 }
711
712 protected boolean translatePathExpressionConstraint( PathExpression pathExrp,
713 ConstraintBuilder where,
714 String tableName ) {
715 RelativePathLikeExpressions expressions = relativePathLikeExpressions(pathExrp);
716 if (expressions.isEmpty()) return false;
717 where = where.openParen();
718 boolean first = true;
719 int number = 0;
720 for (String path : expressions) {
721 if (path == null || path.length() == 0 || path.equals("%/") || path.equals("%/%") || path.equals("%//%")) continue;
722 if (first) first = false;
723 else where.or();
724 if (path.indexOf('%') != -1 || path.indexOf('_') != -1) {
725 where.path(tableName).isLike(path);
726 switch (expressions.depthMode) {
727 case AT_LEAST:
728 where.and().depth(tableName).isGreaterThanOrEqualTo().cast(expressions.depth).asLong();
729 break;
730 case EXACT:
731 where.and().depth(tableName).isEqualTo().cast(expressions.depth).asLong();
732 break;
733 case DEFAULT:
734
735 break;
736 }
737 } else {
738 where.path(tableName).isEqualTo(path);
739 }
740 ++number;
741 }
742 if (number > 0) where.closeParen();
743 return true;
744 }
745
746 protected static enum DepthMode {
747 DEFAULT,
748 EXACT,
749 AT_LEAST;
750 }
751
752 protected static class RelativePathLikeExpressions implements Iterable<String> {
753 protected final List<String> paths;
754 protected final int depth;
755 protected final DepthMode depthMode;
756
757 protected RelativePathLikeExpressions() {
758 this.paths = null;
759 this.depth = 0;
760 this.depthMode = DepthMode.DEFAULT;
761 }
762
763 protected RelativePathLikeExpressions( String[] paths,
764 int depth,
765 DepthMode depthMode ) {
766 this.paths = Arrays.asList(paths);
767 this.depth = depth;
768 this.depthMode = depthMode;
769 }
770
771 protected boolean isEmpty() {
772 return paths == null || paths.isEmpty();
773 }
774
775 public Iterator<String> iterator() {
776 return paths.iterator();
777 }
778 }
779
780 protected RelativePathLikeExpressions relativePathLikeExpressions( PathExpression pathExpression ) {
781 List<StepExpression> steps = pathExpression.getSteps();
782 if (steps.isEmpty()) return new RelativePathLikeExpressions();
783 if (steps.size() == 1 && steps.get(0) instanceof DescendantOrSelf) return new RelativePathLikeExpressions();
784 PathLikeBuilder builder = new SinglePathLikeBuilder();
785 int depth = 0;
786 DepthMode depthMode = DepthMode.EXACT;
787 for (Iterator<StepExpression> iterator = steps.iterator(); iterator.hasNext();) {
788 StepExpression step = iterator.next();
789 if (step instanceof DescendantOrSelf) {
790 ++depth;
791 depthMode = DepthMode.DEFAULT;
792 if (builder.isEmpty()) {
793 builder.append("%/");
794 } else {
795 if (iterator.hasNext()) {
796 builder.append('/');
797 builder = new DualPathLikeBuilder(builder.clone(), builder.append("%"));
798 } else {
799 builder.append('/').append('%');
800 }
801 }
802 } else if (step instanceof AxisStep) {
803 ++depth;
804 AxisStep axis = (AxisStep)step;
805 NodeTest nodeTest = axis.getNodeTest();
806 assert !(nodeTest instanceof ElementTest);
807 if (nodeTest instanceof NameTest) {
808 NameTest nameTest = (NameTest)nodeTest;
809 builder.append('/');
810 boolean addSns = true;
811 if (nameTest.getPrefixTest() != null) {
812 builder.append(nameTest.getPrefixTest()).append(':');
813 }
814 if (nameTest.getLocalTest() != null) {
815 builder.append(nameTest.getLocalTest());
816 } else {
817 builder.append('%');
818 addSns = false;
819 }
820 List<Component> predicates = axis.getPredicates();
821 boolean addedSns = false;
822 if (!predicates.isEmpty()) {
823 assert predicates.size() == 1;
824 Component predicate = predicates.get(0);
825 if (predicate instanceof Literal && ((Literal)predicate).isInteger()) {
826 builder.append('[').append(((Literal)predicate).getValue()).append(']');
827 addedSns = true;
828 }
829 }
830 if (addSns && !addedSns) {
831 builder.append("[%]");
832 }
833 }
834 } else if (step instanceof FilterStep) {
835 FilterStep filter = (FilterStep)step;
836 Component primary = filter.getPrimaryExpression();
837 if (primary instanceof ContextItem) {
838 continue;
839 } else if (primary instanceof ParenthesizedExpression) {
840 ParenthesizedExpression paren = (ParenthesizedExpression)primary;
841 Component wrapped = paren.getWrapped().collapse();
842 if (wrapped instanceof AttributeNameTest) {
843
844 } else if (wrapped instanceof BinaryComponent) {
845 List<NameTest> names = extractElementNames((BinaryComponent)wrapped);
846 if (names.size() >= 1) {
847 PathLikeBuilder orig = builder.clone();
848 builder.append('/').append(nameFrom(names.get(0)));
849 if (names.size() > 1) {
850 for (NameTest name : names.subList(1, names.size())) {
851 builder = new DualPathLikeBuilder(orig.clone().append('/').append(nameFrom(name)), builder);
852 }
853 }
854 }
855 } else {
856 throw new InvalidQueryException(query,
857 "A parenthesized expression of this type is not supported in the primary path expression; therefore '"
858 + primary + "' is not valid");
859 }
860 }
861 }
862 }
863 return new RelativePathLikeExpressions(builder.getPaths(), depth, depthMode);
864 }
865
866 protected static interface PathLikeBuilder {
867 PathLikeBuilder append( String string );
868
869 PathLikeBuilder append( char c );
870
871 boolean isEmpty();
872
873 PathLikeBuilder clone();
874
875 String[] getPaths();
876 }
877
878 protected static class SinglePathLikeBuilder implements PathLikeBuilder {
879 private final StringBuilder builder = new StringBuilder();
880 private char lastChar;
881
882 public SinglePathLikeBuilder append( String string ) {
883 builder.append(string);
884 if (string.length() > 0) lastChar = string.charAt(string.length() - 1);
885 return this;
886 }
887
888 public SinglePathLikeBuilder append( char c ) {
889 if (lastChar != c) {
890 builder.append(c);
891 lastChar = c;
892 }
893 return this;
894 }
895
896 public boolean isEmpty() {
897 return builder.length() == 0;
898 }
899
900 @Override
901 public SinglePathLikeBuilder clone() {
902 return new SinglePathLikeBuilder().append(builder.toString());
903 }
904
905 @Override
906 public String toString() {
907 return builder.toString();
908 }
909
910 public String[] getPaths() {
911 return isEmpty() ? new String[] {} : new String[] {builder.toString()};
912 }
913 }
914
915 protected static class DualPathLikeBuilder implements PathLikeBuilder {
916 private final PathLikeBuilder builder1;
917 private final PathLikeBuilder builder2;
918
919 protected DualPathLikeBuilder( PathLikeBuilder builder1,
920 PathLikeBuilder builder2 ) {
921 this.builder1 = builder1;
922 this.builder2 = builder2;
923 }
924
925 public DualPathLikeBuilder append( String string ) {
926 builder1.append(string);
927 builder2.append(string);
928 return this;
929 }
930
931 public DualPathLikeBuilder append( char c ) {
932 builder1.append(c);
933 builder2.append(c);
934 return this;
935 }
936
937 public boolean isEmpty() {
938 return false;
939 }
940
941 @Override
942 public DualPathLikeBuilder clone() {
943 return new DualPathLikeBuilder(builder1.clone(), builder2.clone());
944 }
945
946 public String[] getPaths() {
947 String[] paths1 = builder1.getPaths();
948 String[] paths2 = builder2.getPaths();
949 String[] result = new String[paths1.length + paths2.length];
950 System.arraycopy(paths1, 0, result, 0, paths1.length);
951 System.arraycopy(paths2, 0, result, paths1.length, paths2.length);
952 return result;
953 }
954 }
955
956 protected String nameFrom( NameTest name ) {
957 String prefix = name.getPrefixTest();
958 String local = name.getLocalTest();
959 assert local != null;
960 return (prefix != null ? prefix + ":" : "") + local;
961 }
962
963 protected String newAlias() {
964 String root = "nodeSet";
965 int num = 1;
966 String alias = root + num;
967 while (aliases.contains(alias)) {
968 num += 1;
969 alias = root + num;
970 }
971 aliases.add(alias);
972 return alias;
973 }
974 }