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 operandBuilder.propertyValue(tableName, attribute.toString());
257 builder.select(tableName + "." + attribute.toString());
258 } else {
259
260 FunctionCall scoreFunction = spec.getScoreFunction();
261 assert scoreFunction != null;
262 List<Component> args = scoreFunction.getParameters();
263 String nameOfTableToScore = tableName;
264 if (!args.isEmpty()) {
265 if (args.size() == 1 && args.get(0) instanceof NameTest) {
266
267 NameTest tableNameTest = (NameTest)args.get(0);
268 nameOfTableToScore = tableNameTest.toString();
269 }
270 }
271 operandBuilder.fullTextSearchScore(nameOfTableToScore);
272 }
273 }
274 orderByBuilder.end();
275 }
276
277 Query query = (Query)builder.query();
278 if (query.columns().isEmpty() && query.source() instanceof AllNodes) {
279
280
281
282
283 builder.select("jcr:primaryType");
284 }
285 }
286
287
288
289
290
291
292
293
294 protected List<AttributeNameTest> extractAttributeNames( BinaryComponent binary ) {
295 List<AttributeNameTest> results = new ArrayList<AttributeNameTest>();
296 boolean failed = false;
297 if (binary instanceof Union) {
298 for (int i = 0; i != 2; ++i) {
299 Component comp = i == 0 ? binary.getLeft() : binary.getRight();
300 comp = comp.collapse();
301 if (comp instanceof Union) {
302 results.addAll(extractAttributeNames((BinaryComponent)comp));
303 } else if (comp instanceof AttributeNameTest) {
304 results.add((AttributeNameTest)comp);
305 } else if (comp instanceof NameTest) {
306
307 } else {
308 failed = true;
309 break;
310 }
311 }
312 } else {
313 failed = true;
314 }
315 if (failed) {
316 throw new InvalidQueryException(query,
317 "A parenthesized expression in a path step may only contain ORed and ANDed attribute names or element names; therefore '"
318 + binary + "' is not valid");
319 }
320 return results;
321 }
322
323
324
325
326
327
328
329
330 protected List<NameTest> extractElementNames( BinaryComponent binary ) {
331 List<NameTest> results = new ArrayList<NameTest>();
332 boolean failed = false;
333 if (binary instanceof Union) {
334 for (int i = 0; i != 2; ++i) {
335 Component comp = i == 0 ? binary.getLeft() : binary.getRight();
336 comp = comp.collapse();
337 if (comp instanceof Union) {
338 results.addAll(extractElementNames((BinaryComponent)comp));
339 } else if (comp instanceof AttributeNameTest) {
340
341 } else if (comp instanceof NameTest) {
342 results.add((NameTest)comp);
343 } else {
344 failed = true;
345 break;
346 }
347 }
348 } else {
349 failed = true;
350 }
351 if (failed) {
352 throw new InvalidQueryException(query,
353 "A parenthesized expression in a path step may only contain ORed element names; therefore '"
354 + binary + "' is not valid");
355 }
356 return results;
357 }
358
359 protected String translateSource( String tableName,
360 List<StepExpression> path,
361 ConstraintBuilder where ) {
362 if (path.size() == 0) {
363
364 String alias = newAlias();
365 builder.fromAllNodesAs(alias);
366 where.path(alias).isEqualTo("/");
367 return alias;
368 }
369 String alias = newAlias();
370 if (tableName != null) {
371
372 builder.joinAllNodesAs(alias);
373 } else {
374
375 builder.fromAllNodesAs(alias);
376 }
377 tableName = alias;
378 if (path.size() == 1 && path.get(0).collapse() instanceof NameTest) {
379
380 NameTest nodeName = (NameTest)path.get(0).collapse();
381 where.path(alias).isEqualTo("/" + nameFrom(nodeName));
382 } else if (path.size() == 2 && path.get(0) instanceof DescendantOrSelf && path.get(1).collapse() instanceof NameTest) {
383
384 NameTest nodeName = (NameTest)path.get(1).collapse();
385 if (!nodeName.isWildcard()) {
386 where.nodeName(alias).isEqualTo(nameFrom(nodeName));
387 }
388 } else {
389
390 translatePathExpressionConstraint(new PathExpression(true, path, null), where, alias);
391 }
392 return tableName;
393 }
394
395 protected String translateElementTest( ElementTest elementTest,
396 List<StepExpression> pathConstraint,
397 ConstraintBuilder where ) {
398 String tableName = null;
399 NameTest typeName = elementTest.getTypeName();
400 if (typeName.isWildcard()) {
401 tableName = newAlias();
402 builder.fromAllNodesAs(tableName);
403 } else {
404 if (typeName.getLocalTest() == null) {
405 throw new InvalidQueryException(
406 query,
407 "The '"
408 + elementTest
409 + "' clause uses a partial wildcard in the type name, but only a wildcard on the whole name is supported");
410 }
411 tableName = nameFrom(typeName);
412 builder.from(tableName);
413 }
414 if (elementTest.getElementName() != null) {
415 NameTest nodeName = elementTest.getElementName();
416 if (!nodeName.isWildcard()) {
417 where.nodeName(tableName).isEqualTo(nameFrom(nodeName));
418 }
419 }
420 if (pathConstraint.isEmpty()) {
421 where.depth(tableName).isEqualTo(1);
422 } else {
423 List<StepExpression> path = new ArrayList<StepExpression>(pathConstraint);
424 if (!path.isEmpty() && path.get(path.size() - 1) instanceof AxisStep) {
425
426 path.add(new AxisStep(new NameTest(null, null), Collections.<Component>emptyList()));
427 }
428 translatePathExpressionConstraint(new PathExpression(true, path, null), where, tableName);
429 }
430 return tableName;
431 }
432
433 protected void translatePredicates( List<Component> predicates,
434 String tableName,
435 ConstraintBuilder where ) {
436 assert tableName != null;
437 for (Component predicate : predicates) {
438 translatePredicate(predicate, tableName, where);
439 }
440 }
441
442 protected String translatePredicate( Component predicate,
443 String tableName,
444 ConstraintBuilder where ) {
445 predicate = predicate.collapse();
446 assert tableName != null;
447 if (predicate instanceof ParenthesizedExpression) {
448 ParenthesizedExpression paren = (ParenthesizedExpression)predicate;
449 where = where.openParen();
450 translatePredicate(paren.getWrapped(), tableName, where);
451 where.closeParen();
452 } else if (predicate instanceof And) {
453 And and = (And)predicate;
454 where = where.openParen();
455 translatePredicate(and.getLeft(), tableName, where);
456 where.and();
457 translatePredicate(and.getRight(), tableName, where);
458 where.closeParen();
459 } else if (predicate instanceof Or) {
460 Or or = (Or)predicate;
461 where = where.openParen();
462 translatePredicate(or.getLeft(), tableName, where);
463 where.or();
464 translatePredicate(or.getRight(), tableName, where);
465 where.closeParen();
466 } else if (predicate instanceof Union) {
467 Union union = (Union)predicate;
468 where = where.openParen();
469 translatePredicate(union.getLeft(), tableName, where);
470 where.or();
471 translatePredicate(union.getRight(), tableName, where);
472 where.closeParen();
473 } else if (predicate instanceof Literal) {
474 Literal literal = (Literal)predicate;
475 if (literal.isInteger()) return tableName;
476 } else if (predicate instanceof AttributeNameTest) {
477
478 AttributeNameTest attribute = (AttributeNameTest)predicate;
479 String propertyName = nameFrom(attribute.getNameTest());
480
481
482
483 where.hasProperty(tableName, propertyName);
484 } else if (predicate instanceof NameTest) {
485
486 NameTest childName = (NameTest)predicate;
487 String alias = newAlias();
488 builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
489 if (!childName.isWildcard()) where.nodeName(alias).isEqualTo(nameFrom(childName));
490 tableName = alias;
491 } else if (predicate instanceof Comparison) {
492 Comparison comparison = (Comparison)predicate;
493 Component left = comparison.getLeft();
494 Component right = comparison.getRight();
495 Operator operator = comparison.getOperator();
496 if (left instanceof Literal) {
497 Component temp = left;
498 left = right;
499 right = temp;
500 operator = operator.reverse();
501 }
502 if (left instanceof NodeTest) {
503 NodeTest nodeTest = (NodeTest)left;
504 String propertyName = null;
505 if (nodeTest instanceof AttributeNameTest) {
506 AttributeNameTest attribute = (AttributeNameTest)left;
507 propertyName = nameFrom(attribute.getNameTest());
508 } else if (nodeTest instanceof NameTest) {
509 NameTest nameTest = (NameTest)left;
510 propertyName = nameFrom(nameTest);
511 } else {
512 throw new InvalidQueryException(query,
513 "Left hand side of a comparison must be a name test or attribute name test; therefore '"
514 + comparison + "' is not valid");
515 }
516 if (right instanceof Literal) {
517 String value = ((Literal)right).getValue();
518 where.propertyValue(tableName, propertyName).is(operator, value);
519 } else if (right instanceof FunctionCall) {
520 FunctionCall call = (FunctionCall)right;
521 NameTest functionName = call.getName();
522 List<Component> parameters = call.getParameters();
523
524 String castType = CAST_FUNCTION_NAME_TO_TYPE.get(functionName);
525 if (castType != null) {
526 if (parameters.size() == 1 && parameters.get(0).collapse() instanceof Literal) {
527
528 Literal value = (Literal)parameters.get(0).collapse();
529 where.propertyValue(tableName, propertyName).is(operator).cast(value.getValue()).as(castType);
530 } else {
531 throw new InvalidQueryException(query, "A cast function requires one literal parameter; therefore '"
532 + comparison + "' is not valid");
533 }
534 } else {
535 throw new InvalidQueryException(query,
536 "Only the 'jcr:score' function is allowed in a comparison predicate; therefore '"
537 + comparison + "' is not valid");
538 }
539 }
540 } else if (left instanceof FunctionCall && right instanceof Literal) {
541 FunctionCall call = (FunctionCall)left;
542 NameTest functionName = call.getName();
543 List<Component> parameters = call.getParameters();
544 String value = ((Literal)right).getValue();
545 if (functionName.matches("jcr", "score")) {
546 String scoreTableName = tableName;
547 if (parameters.isEmpty()) {
548 scoreTableName = tableName;
549 } else if (parameters.size() == 1 && parameters.get(0) instanceof NameTest) {
550
551 NameTest name = (NameTest)parameters.get(0);
552 if (!name.isWildcard()) scoreTableName = nameFrom(name);
553 } else {
554 throw new InvalidQueryException(query,
555 "The 'jcr:score' function may have no parameters or the type name as the only parameter.");
556
557 }
558 where.fullTextSearchScore(scoreTableName).is(operator, value);
559 } else {
560 throw new InvalidQueryException(query,
561 "Only the 'jcr:score' function is allowed in a comparison predicate; therefore '"
562 + comparison + "' is not valid");
563 }
564 }
565 } else if (predicate instanceof FunctionCall) {
566 FunctionCall call = (FunctionCall)predicate;
567 NameTest functionName = call.getName();
568 List<Component> parameters = call.getParameters();
569 Component param1 = parameters.size() > 0 ? parameters.get(0) : null;
570 Component param2 = parameters.size() > 1 ? parameters.get(1) : null;
571 if (functionName.matches(null, "not")) {
572 if (parameters.size() != 1) {
573 throw new InvalidQueryException(query, "The 'not' function requires one parameter; therefore '" + predicate
574 + "' is not valid");
575 }
576 where = where.not().openParen();
577 translatePredicate(param1, tableName, where);
578 where.closeParen();
579 } else if (functionName.matches("jcr", "like")) {
580 if (parameters.size() != 2) {
581 throw new InvalidQueryException(query, "The 'jcr:like' function requires two parameters; therefore '"
582 + predicate + "' is not valid");
583 }
584 if (!(param1 instanceof AttributeNameTest)) {
585 throw new InvalidQueryException(query,
586 "The first parameter of 'jcr:like' must be an property reference with the '@' symbol; therefore '"
587 + predicate + "' is not valid");
588 }
589 if (!(param2 instanceof Literal)) {
590 throw new InvalidQueryException(query, "The second parameter of 'jcr:like' must be a literal; therefore '"
591 + predicate + "' is not valid");
592 }
593 NameTest attributeName = ((AttributeNameTest)param1).getNameTest();
594 String value = ((Literal)param2).getValue();
595 where.propertyValue(tableName, nameFrom(attributeName)).isLike(value);
596 } else if (functionName.matches("jcr", "contains")) {
597 if (parameters.size() != 2) {
598 throw new InvalidQueryException(query, "The 'jcr:contains' function requires two parameters; therefore '"
599 + predicate + "' is not valid");
600 }
601 if (!(param2 instanceof Literal)) {
602 throw new InvalidQueryException(query,
603 "The second parameter of 'jcr:contains' must be a literal; therefore '"
604 + predicate + "' is not valid");
605 }
606 String value = ((Literal)param2).getValue();
607 if (param1 instanceof ContextItem) {
608
609 where.search(tableName, value);
610 } else if (param1 instanceof AttributeNameTest) {
611
612 NameTest attributeName = ((AttributeNameTest)param1).getNameTest();
613 where.search(tableName, nameFrom(attributeName), value);
614 } else if (param1 instanceof NameTest) {
615
616 String alias = newAlias();
617 builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
618
619 where.search(alias, value);
620 tableName = alias;
621 } else if (param1 instanceof PathExpression) {
622
623 PathExpression pathExpr = (PathExpression)param1;
624 if (pathExpr.getLastStep().collapse() instanceof AttributeNameTest) {
625 AttributeNameTest attributeName = (AttributeNameTest)pathExpr.getLastStep().collapse();
626 pathExpr = pathExpr.withoutLast();
627 String searchTable = translatePredicate(pathExpr, tableName, where);
628 if (attributeName.getNameTest().isWildcard()) {
629 where.search(searchTable, value);
630 } else {
631 where.search(searchTable, nameFrom(attributeName.getNameTest()), value);
632 }
633 } else {
634 String searchTable = translatePredicate(param1, tableName, where);
635 where.search(searchTable, value);
636 }
637 } else {
638 throw new InvalidQueryException(query,
639 "The first parameter of 'jcr:contains' must be a relative path (e.g., '.', an attribute name, a child name, etc.); therefore '"
640 + predicate + "' is not valid");
641 }
642 } else if (functionName.matches("jcr", "deref")) {
643 throw new InvalidQueryException(query,
644 "The 'jcr:deref' function is not required by JCR and is not currently supported; therefore '"
645 + predicate + "' is not valid");
646 } else {
647 throw new InvalidQueryException(query,
648 "Only the 'jcr:like' and 'jcr:contains' functions are allowed in a predicate; therefore '"
649 + predicate + "' is not valid");
650 }
651 } else if (predicate instanceof PathExpression) {
652
653 PathExpression pathExpr = (PathExpression)predicate;
654 List<StepExpression> steps = pathExpr.getSteps();
655 OrderBy orderBy = pathExpr.getOrderBy();
656 assert steps.size() > 1;
657 Component firstStep = steps.get(0).collapse();
658 if (firstStep instanceof ContextItem) {
659
660 return translatePredicate(new PathExpression(true, steps.subList(1, steps.size()), orderBy), tableName, where);
661 }
662 if (firstStep instanceof NameTest) {
663
664 NameTest childName = (NameTest)firstStep;
665 String alias = newAlias();
666 builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
667 if (!childName.isWildcard()) {
668 where.nodeName(alias).isEqualTo(nameFrom(childName));
669 }
670 return translatePredicate(new PathExpression(true, steps.subList(1, steps.size()), orderBy), alias, where);
671 }
672 if (firstStep instanceof DescendantOrSelf) {
673
674 String alias = newAlias();
675 builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
676 return translatePredicate(new PathExpression(true, steps.subList(1, steps.size()), orderBy), alias, where);
677 }
678
679 String alias = newAlias();
680 builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
681
682 translatePathExpressionConstraint(pathExpr, where, alias);
683 } else {
684 throw new InvalidQueryException(query, "Unsupported criteria '" + predicate + "'");
685 }
686 return tableName;
687 }
688
689
690
691
692
693
694
695
696 protected boolean appliesToPathConstraint( List<Component> predicates ) {
697 if (predicates.isEmpty()) return true;
698 if (predicates.size() > 1) return false;
699 assert predicates.size() == 1;
700 Component predicate = predicates.get(0);
701 if (predicate instanceof Literal && ((Literal)predicate).isInteger()) return true;
702 if (predicate instanceof NameTest && ((NameTest)predicate).isWildcard()) return true;
703 return false;
704 }
705
706 protected boolean translatePathExpressionConstraint( PathExpression pathExrp,
707 ConstraintBuilder where,
708 String tableName ) {
709 RelativePathLikeExpressions expressions = relativePathLikeExpressions(pathExrp);
710 if (expressions.isEmpty()) return false;
711 where = where.openParen();
712 boolean first = true;
713 int number = 0;
714 for (String path : expressions) {
715 if (path == null || path.length() == 0 || path.equals("%/") || path.equals("%/%") || path.equals("%//%")) continue;
716 if (first) first = false;
717 else where.or();
718 if (path.indexOf('%') != -1) {
719 where.path(tableName).isLike(path);
720 switch (expressions.depthMode) {
721 case AT_LEAST:
722 where.and().depth(tableName).isGreaterThanOrEqualTo().cast(expressions.depth).asLong();
723 break;
724 case EXACT:
725 where.and().depth(tableName).isEqualTo().cast(expressions.depth).asLong();
726 break;
727 case DEFAULT:
728
729 break;
730 }
731 } else {
732 where.path(tableName).isEqualTo(path);
733 }
734 ++number;
735 }
736 if (number > 0) where.closeParen();
737 return true;
738 }
739
740 protected static enum DepthMode {
741 DEFAULT,
742 EXACT,
743 AT_LEAST;
744 }
745
746 protected static class RelativePathLikeExpressions implements Iterable<String> {
747 protected final List<String> paths;
748 protected final int depth;
749 protected final DepthMode depthMode;
750
751 protected RelativePathLikeExpressions() {
752 this.paths = null;
753 this.depth = 0;
754 this.depthMode = DepthMode.DEFAULT;
755 }
756
757 protected RelativePathLikeExpressions( String[] paths,
758 int depth,
759 DepthMode depthMode ) {
760 this.paths = Arrays.asList(paths);
761 this.depth = depth;
762 this.depthMode = depthMode;
763 }
764
765 protected boolean isEmpty() {
766 return paths == null || paths.isEmpty();
767 }
768
769 public Iterator<String> iterator() {
770 return paths.iterator();
771 }
772 }
773
774 protected RelativePathLikeExpressions relativePathLikeExpressions( PathExpression pathExpression ) {
775 List<StepExpression> steps = pathExpression.getSteps();
776 if (steps.isEmpty()) return new RelativePathLikeExpressions();
777 if (steps.size() == 1 && steps.get(0) instanceof DescendantOrSelf) return new RelativePathLikeExpressions();
778 PathLikeBuilder builder = new SinglePathLikeBuilder();
779 int depth = 0;
780 DepthMode depthMode = DepthMode.EXACT;
781 for (Iterator<StepExpression> iterator = steps.iterator(); iterator.hasNext();) {
782 StepExpression step = iterator.next();
783 if (step instanceof DescendantOrSelf) {
784 ++depth;
785 depthMode = DepthMode.DEFAULT;
786 if (builder.isEmpty()) {
787 builder.append("%/");
788 } else {
789 if (iterator.hasNext()) {
790 builder.append('/');
791 builder = new DualPathLikeBuilder(builder.clone(), builder.append("%"));
792 } else {
793 builder.append('/').append('%');
794 }
795 }
796 } else if (step instanceof AxisStep) {
797 ++depth;
798 AxisStep axis = (AxisStep)step;
799 NodeTest nodeTest = axis.getNodeTest();
800 assert !(nodeTest instanceof ElementTest);
801 if (nodeTest instanceof NameTest) {
802 NameTest nameTest = (NameTest)nodeTest;
803 builder.append('/');
804 if (nameTest.getPrefixTest() != null) {
805 builder.append(nameTest.getPrefixTest()).append(':');
806 }
807 if (nameTest.getLocalTest() != null) {
808 builder.append(nameTest.getLocalTest());
809 } else {
810 builder.append('%');
811 }
812 List<Component> predicates = axis.getPredicates();
813 if (!predicates.isEmpty()) {
814 assert predicates.size() == 1;
815 Component predicate = predicates.get(0);
816 if (predicate instanceof Literal && ((Literal)predicate).isInteger()) {
817 builder.append('[').append(((Literal)predicate).getValue()).append(']');
818 }
819 }
820 }
821 } else if (step instanceof FilterStep) {
822 FilterStep filter = (FilterStep)step;
823 Component primary = filter.getPrimaryExpression();
824 if (primary instanceof ContextItem) {
825 continue;
826 } else if (primary instanceof ParenthesizedExpression) {
827 ParenthesizedExpression paren = (ParenthesizedExpression)primary;
828 Component wrapped = paren.getWrapped().collapse();
829 if (wrapped instanceof AttributeNameTest) {
830
831 } else if (wrapped instanceof BinaryComponent) {
832 List<NameTest> names = extractElementNames((BinaryComponent)wrapped);
833 if (names.size() >= 1) {
834 PathLikeBuilder orig = builder.clone();
835 builder.append('/').append(nameFrom(names.get(0)));
836 if (names.size() > 1) {
837 for (NameTest name : names.subList(1, names.size())) {
838 builder = new DualPathLikeBuilder(orig.clone().append('/').append(nameFrom(name)), builder);
839 }
840 }
841 }
842 } else {
843 throw new InvalidQueryException(query,
844 "A parenthesized expression of this type is not supported in the primary path expression; therefore '"
845 + primary + "' is not valid");
846 }
847 }
848 }
849 }
850 return new RelativePathLikeExpressions(builder.getPaths(), depth, depthMode);
851 }
852
853 protected static interface PathLikeBuilder {
854 PathLikeBuilder append( String string );
855
856 PathLikeBuilder append( char c );
857
858 boolean isEmpty();
859
860 PathLikeBuilder clone();
861
862 String[] getPaths();
863 }
864
865 protected static class SinglePathLikeBuilder implements PathLikeBuilder {
866 private final StringBuilder builder = new StringBuilder();
867 private char lastChar;
868
869 public SinglePathLikeBuilder append( String string ) {
870 builder.append(string);
871 if (string.length() > 0) lastChar = string.charAt(string.length() - 1);
872 return this;
873 }
874
875 public SinglePathLikeBuilder append( char c ) {
876 if (lastChar != c) {
877 builder.append(c);
878 lastChar = c;
879 }
880 return this;
881 }
882
883 public boolean isEmpty() {
884 return builder.length() == 0;
885 }
886
887 @Override
888 public SinglePathLikeBuilder clone() {
889 return new SinglePathLikeBuilder().append(builder.toString());
890 }
891
892 @Override
893 public String toString() {
894 return builder.toString();
895 }
896
897 public String[] getPaths() {
898 return isEmpty() ? new String[] {} : new String[] {builder.toString()};
899 }
900 }
901
902 protected static class DualPathLikeBuilder implements PathLikeBuilder {
903 private final PathLikeBuilder builder1;
904 private final PathLikeBuilder builder2;
905
906 protected DualPathLikeBuilder( PathLikeBuilder builder1,
907 PathLikeBuilder builder2 ) {
908 this.builder1 = builder1;
909 this.builder2 = builder2;
910 }
911
912 public DualPathLikeBuilder append( String string ) {
913 builder1.append(string);
914 builder2.append(string);
915 return this;
916 }
917
918 public DualPathLikeBuilder append( char c ) {
919 builder1.append(c);
920 builder2.append(c);
921 return this;
922 }
923
924 public boolean isEmpty() {
925 return false;
926 }
927
928 @Override
929 public DualPathLikeBuilder clone() {
930 return new DualPathLikeBuilder(builder1.clone(), builder2.clone());
931 }
932
933 public String[] getPaths() {
934 String[] paths1 = builder1.getPaths();
935 String[] paths2 = builder2.getPaths();
936 String[] result = new String[paths1.length + paths2.length];
937 System.arraycopy(paths1, 0, result, 0, paths1.length);
938 System.arraycopy(paths2, 0, result, paths1.length, paths2.length);
939 return result;
940 }
941 }
942
943 protected String nameFrom( NameTest name ) {
944 String prefix = name.getPrefixTest();
945 String local = name.getLocalTest();
946 assert local != null;
947 return (prefix != null ? prefix + ":" : "") + local;
948 }
949
950 protected String newAlias() {
951 String root = "nodeSet";
952 int num = 1;
953 String alias = root + num;
954 while (aliases.contains(alias)) {
955 num += 1;
956 alias = root + num;
957 }
958 aliases.add(alias);
959 return alias;
960 }
961 }