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.query;
25
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.NoSuchElementException;
31 import java.util.Set;
32 import javax.jcr.AccessDeniedException;
33 import javax.jcr.ItemNotFoundException;
34 import javax.jcr.Node;
35 import javax.jcr.NodeIterator;
36 import javax.jcr.PropertyType;
37 import javax.jcr.RepositoryException;
38 import javax.jcr.Value;
39 import javax.jcr.query.QueryResult;
40 import javax.jcr.query.Row;
41 import javax.jcr.query.RowIterator;
42 import net.jcip.annotations.NotThreadSafe;
43 import org.modeshape.common.collection.Collections;
44 import org.modeshape.graph.Location;
45 import org.modeshape.graph.property.Name;
46 import org.modeshape.graph.property.Path;
47 import org.modeshape.graph.query.QueryResults;
48 import org.modeshape.graph.query.QueryResults.Columns;
49 import org.modeshape.graph.query.model.Column;
50 import org.modeshape.graph.query.validate.Schemata;
51 import org.modeshape.graph.query.validate.Schemata.Table;
52 import org.modeshape.jcr.JcrI18n;
53
54
55
56
57
58
59
60
61 @NotThreadSafe
62 public class JcrQueryResult implements QueryResult, org.modeshape.jcr.api.query.QueryResult {
63 public static final String JCR_SCORE_COLUMN_NAME = "jcr:score";
64 public static final String JCR_PATH_COLUMN_NAME = "jcr:path";
65 public static final String JCR_NAME_COLUMN_NAME = "jcr:name";
66 public static final String MODE_LOCALNAME_COLUMN_NAME = "mode:localName";
67 public static final String MODE_DEPTH_COLUMN_NAME = "mode:depth";
68 protected static final Set<String> PSEUDO_COLUMNS = Collections.unmodifiableSet(JCR_SCORE_COLUMN_NAME,
69 JCR_PATH_COLUMN_NAME,
70 JCR_NAME_COLUMN_NAME,
71 MODE_LOCALNAME_COLUMN_NAME,
72 MODE_DEPTH_COLUMN_NAME);
73
74 protected final JcrQueryContext context;
75 protected final QueryResults results;
76 protected final Schemata schemata;
77 protected final String queryStatement;
78 private List<String> columnTables;
79
80 protected JcrQueryResult( JcrQueryContext context,
81 String query,
82 QueryResults graphResults,
83 Schemata schemata ) {
84 this.context = context;
85 this.results = graphResults;
86 this.schemata = schemata;
87 this.queryStatement = query;
88 assert this.context != null;
89 assert this.results != null;
90 assert this.schemata != null;
91 assert this.queryStatement != null;
92 }
93
94 protected QueryResults results() {
95 return results;
96 }
97
98 public List<String> getColumnNameList() {
99 return results.getColumns().getColumnNames();
100 }
101
102 public List<String> getColumnTypeList() {
103 return results.getColumns().getColumnTypes();
104 }
105
106
107
108
109
110
111 public String[] getColumnNames()
112 List<String> names = getColumnNameList();
113 return names.toArray(new String[names.size()]);
114 }
115
116
117
118
119
120
121 @Override
122 public String[] getColumnTypes() {
123 List<String> types = getColumnTypeList();
124 return types.toArray(new String[types.size()]);
125 }
126
127
128
129
130
131
132 @Override
133 public String[] getSelectorNames() {
134 if (columnTables == null) {
135
136 Columns columns = results.getColumns();
137 List<String> tables = new ArrayList<String>(columns.getColumnCount());
138 for (Column column : columns) {
139 String tableName = "";
140 Table table = schemata.getTable(column.selectorName());
141 if (table != null) tableName = table.getName().name();
142 tables.add(tableName);
143 }
144 columnTables = tables;
145 }
146 return columnTables.toArray(new String[columnTables.size()]);
147 }
148
149
150
151
152
153
154 public NodeIterator getNodes() throws RepositoryException {
155
156
157 final int numRows = results.getRowCount();
158 if (numRows == 0) return context.emptyNodeIterator();
159
160 final List<Node> nodes = new ArrayList<Node>(numRows);
161 final String selectorName = results.getColumns().getSelectorNames().get(0);
162 final int locationIndex = results.getColumns().getLocationIndex(selectorName);
163 for (Object[] tuple : results.getTuples()) {
164 Location location = (Location)tuple[locationIndex];
165 Node node = context.getNode(location);
166 if (node != null) {
167 nodes.add(node);
168 }
169 }
170 return new QueryResultNodeIterator(nodes);
171 }
172
173
174
175
176
177
178 public RowIterator getRows()
179
180 final int numRows = results.getRowCount();
181 final List<Object[]> tuples = results.getTuples();
182 if (results.getColumns().getLocationCount() == 1) {
183 return new SingleSelectorQueryResultRowIterator(context, queryStatement, results, tuples.iterator(), numRows);
184 }
185 return new QueryResultRowIterator(context, queryStatement, results, tuples.iterator(), numRows);
186 }
187
188
189
190
191
192
193 public String getPlan() {
194 return results.getPlan();
195 }
196
197
198
199
200
201
202 @Override
203 public String toString() {
204 return results.toString();
205 }
206
207
208
209
210
211
212 @NotThreadSafe
213 protected static class QueryResultNodeIterator implements NodeIterator {
214 private final Iterator<? extends Node> nodes;
215 private final int size;
216 private long position = 0L;
217
218 protected QueryResultNodeIterator( List<? extends Node> nodes ) {
219 this.nodes = nodes.iterator();
220 this.size = nodes.size();
221 }
222
223
224
225
226
227
228 public Node nextNode() {
229 Node node = nodes.next();
230 ++position;
231 return node;
232 }
233
234
235
236
237
238
239 public long getPosition() {
240 return position;
241 }
242
243
244
245
246
247
248 public long getSize() {
249 return size;
250 }
251
252
253
254
255
256
257 public void skip( long skipNum ) {
258 for (long i = 0L; i != skipNum; ++i)
259 nextNode();
260 }
261
262
263
264
265
266
267 public boolean hasNext() {
268 return nodes.hasNext();
269 }
270
271
272
273
274
275
276 public Object next() {
277 return nextNode();
278 }
279
280
281
282
283
284
285 public void remove() {
286 throw new UnsupportedOperationException();
287 }
288 }
289
290
291
292
293
294
295 @NotThreadSafe
296 protected static class QueryResultRowIterator implements RowIterator {
297 protected final List<String> columnNames;
298 private final Iterator<Object[]> tuples;
299 private final Set<String> selectorNames;
300 protected final JcrQueryContext context;
301 protected final Columns columns;
302 protected final String query;
303 private int[] locationIndexes;
304 private long position = 0L;
305 private long numRows;
306 private Row nextRow;
307
308 protected QueryResultRowIterator( JcrQueryContext context,
309 String query,
310 QueryResults results,
311 Iterator<Object[]> tuples,
312 long numRows ) {
313 this.tuples = tuples;
314 this.query = query;
315 this.columns = results.getColumns();
316 this.columnNames = this.columns.getColumnNames();
317 this.context = context;
318 this.numRows = numRows;
319 this.selectorNames = new HashSet<String>(columns.getSelectorNames());
320 int i = 0;
321 locationIndexes = new int[selectorNames.size()];
322 for (String selectorName : selectorNames) {
323 locationIndexes[i++] = columns.getLocationIndex(selectorName);
324 }
325 }
326
327 public boolean hasSelector( String selectorName ) {
328 return this.selectorNames.contains(selectorName);
329 }
330
331
332
333
334
335
336 public Row nextRow() {
337 if (nextRow == null) {
338
339 if (!hasNext()) {
340 throw new NoSuchElementException();
341 }
342 }
343 assert nextRow != null;
344 Row result = nextRow;
345 nextRow = null;
346 return result;
347 }
348
349
350
351
352
353
354 public long getPosition() {
355 return position;
356 }
357
358
359
360
361
362
363 public long getSize() {
364 return numRows;
365 }
366
367
368
369
370
371
372 public void skip( long skipNum ) {
373 for (long i = 0L; i != skipNum; ++i) {
374 tuples.next();
375 }
376 position += skipNum;
377 }
378
379
380
381
382
383
384 public boolean hasNext() {
385 if (nextRow != null) {
386 return true;
387 }
388 while (tuples.hasNext()) {
389 final Object[] tuple = tuples.next();
390 ++position;
391 try {
392
393 nextRow = getNextRow(tuple);
394 if (nextRow != null) return true;
395 } catch (RepositoryException e) {
396
397 }
398 --numRows;
399 }
400 return false;
401 }
402
403 protected Row getNextRow( Object[] tuple ) throws RepositoryException {
404
405 Node[] nodes = new Node[locationIndexes.length];
406 int index = 0;
407 for (int locationIndex : locationIndexes) {
408 Location location = (Location)tuple[locationIndex];
409 try {
410 Node node = context.getNode(location);
411 if (node == null) {
412
413 return null;
414 }
415 nodes[index++] = node;
416 } catch (AccessDeniedException e) {
417
418 return null;
419 }
420 }
421 return new MultiSelectorQueryResultRow(this, nodes, locationIndexes, tuple);
422 }
423
424
425
426
427
428
429 public Object next() {
430 return nextRow();
431 }
432
433
434
435
436
437
438 public void remove() {
439 throw new UnsupportedOperationException();
440 }
441
442 protected Value jcrPath( Path path ) {
443 return context.createValue(PropertyType.PATH, path);
444 }
445
446 protected Value jcrName( Name name ) {
447 return context.createValue(PropertyType.NAME, name);
448 }
449
450 protected Value jcrName() {
451 return context.createValue(PropertyType.NAME, "");
452 }
453
454 protected Value jcrString( String name ) {
455 return context.createValue(PropertyType.STRING, name);
456 }
457
458 protected Value jcrLong( Long value ) {
459 return context.createValue(PropertyType.LONG, value);
460 }
461
462 protected Value jcrDouble( Float score ) {
463 return context.createValue(PropertyType.DOUBLE, score);
464 }
465 }
466
467
468
469
470
471
472 @NotThreadSafe
473 protected static class SingleSelectorQueryResultRowIterator extends QueryResultRowIterator {
474 protected final int locationIndex;
475 protected final int scoreIndex;
476
477 protected SingleSelectorQueryResultRowIterator( JcrQueryContext context,
478 String query,
479 QueryResults results,
480 Iterator<Object[]> tuples,
481 long numRows ) {
482 super(context, query, results, tuples, numRows);
483 String selectorName = columns.getSelectorNames().get(0);
484 locationIndex = columns.getLocationIndex(selectorName);
485 scoreIndex = columns.getFullTextSearchScoreIndexFor(selectorName);
486 }
487
488
489
490
491
492
493 @Override
494 protected Row getNextRow( Object[] tuple ) throws RepositoryException {
495 Location location = (Location)tuple[locationIndex];
496 Node node = context.getNode(location);
497 return node != null ? createRow(node, tuple) : null;
498 }
499
500 protected Row createRow( Node node,
501 Object[] tuple ) {
502 return new SingleSelectorQueryResultRow(this, node, tuple);
503 }
504 }
505
506 protected static class SingleSelectorQueryResultRow implements Row, org.modeshape.jcr.api.query.Row {
507 protected final SingleSelectorQueryResultRowIterator iterator;
508 protected final Node node;
509 protected final Object[] tuple;
510 private Value[] values = null;
511
512 protected SingleSelectorQueryResultRow( SingleSelectorQueryResultRowIterator iterator,
513 Node node,
514 Object[] tuple ) {
515 this.iterator = iterator;
516 this.node = node;
517 this.tuple = tuple;
518 assert this.iterator != null;
519 assert this.node != null;
520 assert this.tuple != null;
521 }
522
523
524
525
526
527
528 @Override
529 public Node getNode( String selectorName ) throws RepositoryException {
530 if (!iterator.hasSelector(selectorName)) {
531 throw new RepositoryException(JcrI18n.selectorNotUsedInQuery.text(selectorName, iterator.query));
532 }
533 return node;
534 }
535
536
537
538
539
540
541 public Value getValue( String columnName ) throws ItemNotFoundException, RepositoryException {
542 if (PSEUDO_COLUMNS.contains(columnName)) {
543 if (JCR_PATH_COLUMN_NAME.equals(columnName)) {
544 Location location = (Location)tuple[iterator.locationIndex];
545 return iterator.jcrPath(location.getPath());
546 }
547 if (JCR_NAME_COLUMN_NAME.equals(columnName)) {
548 Location location = (Location)tuple[iterator.locationIndex];
549 Path path = location.getPath();
550 if (path.isRoot()) return iterator.jcrName();
551 return iterator.jcrName(path.getLastSegment().getName());
552 }
553 if (MODE_LOCALNAME_COLUMN_NAME.equals(columnName)) {
554 Location location = (Location)tuple[iterator.locationIndex];
555 Path path = location.getPath();
556 if (path.isRoot()) return iterator.jcrString("");
557 return iterator.jcrString(path.getLastSegment().getName().getLocalName());
558 }
559 if (MODE_DEPTH_COLUMN_NAME.equals(columnName)) {
560 Location location = (Location)tuple[iterator.locationIndex];
561 Path path = location.getPath();
562 Long depth = new Long(path.size());
563 return iterator.jcrLong(depth);
564 }
565 if (JCR_SCORE_COLUMN_NAME.equals(columnName)) {
566 Float score = iterator.scoreIndex == -1 ? 0.0f : (Float)tuple[iterator.scoreIndex];
567 return iterator.jcrDouble(score);
568 }
569 }
570 return node.getProperty(columnName).getValue();
571 }
572
573
574
575
576
577
578 public Value[] getValues() throws RepositoryException {
579 if (values == null) {
580 int i = 0;
581 values = new Value[iterator.columnNames.size()];
582 for (String columnName : iterator.columnNames) {
583 values[i++] = getValue(columnName);
584 }
585 }
586 return values;
587 }
588
589 @Override
590 public Node getNode() {
591 return node;
592 }
593
594 @Override
595 public String getPath() throws RepositoryException {
596 return node.getPath();
597 }
598
599 @Override
600 public String getPath( String selectorName ) throws RepositoryException {
601 if (!iterator.hasSelector(selectorName)) {
602 throw new RepositoryException(JcrI18n.selectorNotUsedInQuery.text(selectorName, iterator.query));
603 }
604 return node.getPath();
605 }
606
607 @Override
608 public double getScore() throws RepositoryException {
609 int index = iterator.scoreIndex;
610 if (index == -1) {
611 throw new RepositoryException(JcrI18n.queryResultsDoNotIncludeScore.text(iterator.query));
612 }
613 Object score = tuple[index];
614 return score instanceof Float ? ((Float)score).doubleValue() : (Double)score;
615 }
616
617 @Override
618 public double getScore( String selectorName ) throws RepositoryException {
619 if (!iterator.hasSelector(selectorName)) {
620 throw new RepositoryException(JcrI18n.selectorNotUsedInQuery.text(selectorName, iterator.query));
621 }
622 return getScore();
623 }
624 }
625
626 protected static class MultiSelectorQueryResultRow implements Row, org.modeshape.jcr.api.query.Row {
627 protected final QueryResultRowIterator iterator;
628 protected final Object[] tuple;
629 private Value[] values = null;
630 private Node[] nodes;
631
632 protected MultiSelectorQueryResultRow( QueryResultRowIterator iterator,
633 Node[] nodes,
634 int[] locationIndexes,
635 Object[] tuple ) {
636 this.iterator = iterator;
637 this.tuple = tuple;
638 this.nodes = nodes;
639 assert this.iterator != null;
640 assert this.tuple != null;
641 }
642
643
644
645
646
647
648 @Override
649 public Node getNode( String selectorName ) throws RepositoryException {
650 int locationIndex = iterator.columns.getLocationIndex(selectorName);
651 if (locationIndex == -1) {
652 throw new RepositoryException(JcrI18n.selectorNotUsedInQuery.text(selectorName, iterator.query));
653 }
654 return nodes[locationIndex];
655 }
656
657
658
659
660
661
662 public Value getValue( String columnName ) throws ItemNotFoundException, RepositoryException {
663 int locationIndex = iterator.columns.getLocationIndexForColumn(columnName);
664 if (locationIndex == -1) {
665 throw new RepositoryException(JcrI18n.queryResultsDoNotIncludeColumn.text(columnName, iterator.query));
666 }
667 Node node = nodes[locationIndex];
668 if (node == null) return null;
669 if (PSEUDO_COLUMNS.contains(columnName)) {
670 if (JCR_PATH_COLUMN_NAME.equals(columnName)) {
671 Location location = (Location)tuple[locationIndex];
672 return iterator.jcrPath(location.getPath());
673 }
674 if (JCR_NAME_COLUMN_NAME.equals(columnName)) {
675 Location location = (Location)tuple[locationIndex];
676 Path path = location.getPath();
677 if (path.isRoot()) return iterator.jcrName();
678 return iterator.jcrName(path.getLastSegment().getName());
679 }
680 if (MODE_LOCALNAME_COLUMN_NAME.equals(columnName)) {
681 Location location = (Location)tuple[locationIndex];
682 Path path = location.getPath();
683 if (path.isRoot()) return iterator.jcrString("");
684 return iterator.jcrString(path.getLastSegment().getName().getLocalName());
685 }
686 if (MODE_DEPTH_COLUMN_NAME.equals(columnName)) {
687 Location location = (Location)tuple[locationIndex];
688 Path path = location.getPath();
689 Long depth = new Long(path.size());
690 return iterator.jcrLong(depth);
691 }
692 if (JCR_SCORE_COLUMN_NAME.equals(columnName)) {
693 int scoreIndex = iterator.columns.getFullTextSearchScoreIndexFor(columnName);
694 Float score = scoreIndex == -1 ? 0.0f : (Float)tuple[scoreIndex];
695 return iterator.jcrDouble(score);
696 }
697 }
698 return node.getProperty(columnName).getValue();
699 }
700
701
702
703
704
705
706 public Value[] getValues() throws RepositoryException {
707 if (values == null) {
708 int i = 0;
709 values = new Value[iterator.columnNames.size()];
710 for (String columnName : iterator.columnNames) {
711 values[i++] = getValue(columnName);
712 }
713 }
714 return values;
715 }
716
717 @Override
718 public Node getNode() throws RepositoryException {
719 throw new RepositoryException(
720 JcrI18n.multipleSelectorsAppearInQueryRequireSpecifyingSelectorName.text(iterator.query));
721 }
722
723 @Override
724 public String getPath() throws RepositoryException {
725 throw new RepositoryException(
726 JcrI18n.multipleSelectorsAppearInQueryRequireSpecifyingSelectorName.text(iterator.query));
727 }
728
729 @Override
730 public double getScore() throws RepositoryException {
731 throw new RepositoryException(
732 JcrI18n.multipleSelectorsAppearInQueryRequireSpecifyingSelectorName.text(iterator.query));
733 }
734
735 @Override
736 public String getPath( String selectorName ) throws RepositoryException {
737 return getNode(selectorName).getPath();
738 }
739
740 @Override
741 public double getScore( String selectorName ) throws RepositoryException {
742 if (!iterator.hasSelector(selectorName)) {
743 throw new RepositoryException(JcrI18n.selectorNotUsedInQuery.text(selectorName, iterator.query));
744 }
745 int scoreIndex = iterator.columns.getFullTextSearchScoreIndexFor(selectorName);
746 if (scoreIndex == -1) {
747 throw new RepositoryException(JcrI18n.queryResultsDoNotIncludeScore.text(iterator.query));
748 }
749 Object score = tuple[scoreIndex];
750 return score instanceof Float ? ((Float)score).doubleValue() : (Double)score;
751 }
752
753 }
754 }