View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
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   * The results of a query. This is not thread-safe because it relies upon JcrSession, which is not thread-safe. Also, although the
56   * results of a query never change, the objects returned by the iterators may vary if the session information changes.
57   * 
58   * @see XPathQueryResult
59   * @see JcrSqlQueryResult
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      * {@inheritDoc}
108      * 
109      * @see javax.jcr.query.QueryResult#getColumnNames()
110      */
111     public String[] getColumnNames() /*throws RepositoryException*/{
112         List<String> names = getColumnNameList();
113         return names.toArray(new String[names.size()]); // make a defensive copy ...
114     }
115 
116     /**
117      * {@inheritDoc}
118      * 
119      * @see org.modeshape.jcr.api.query.QueryResult#getColumnTypes()
120      */
121     @Override
122     public String[] getColumnTypes() {
123         List<String> types = getColumnTypeList();
124         return types.toArray(new String[types.size()]); // make a defensive copy ...
125     }
126 
127     /**
128      * {@inheritDoc}
129      * 
130      * @see org.modeshape.jcr.api.query.QueryResult#getSelectorNames()
131      */
132     @Override
133     public String[] getSelectorNames() {
134         if (columnTables == null) {
135             // Discover the types ...
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      * {@inheritDoc}
151      * 
152      * @see javax.jcr.query.QueryResult#getNodes()
153      */
154     public NodeIterator getNodes() throws RepositoryException {
155         // Find all of the nodes in the results. We have to do this pre-emptively, since this
156         // is the only method to throw RepositoryException ...
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      * {@inheritDoc}
175      * 
176      * @see javax.jcr.query.QueryResult#getRows()
177      */
178     public RowIterator getRows() /*throws RepositoryException*/{
179         // We can actually delay the loading of the nodes until the rows are accessed ...
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      * Get a description of the query plan, if requested.
190      * 
191      * @return the query plan, or null if the plan was not requested
192      */
193     public String getPlan() {
194         return results.getPlan();
195     }
196 
197     /**
198      * {@inheritDoc}
199      * 
200      * @see java.lang.Object#toString()
201      */
202     @Override
203     public String toString() {
204         return results.toString();
205     }
206 
207     /**
208      * The {@link NodeIterator} implementation returned by the {@link JcrQueryResult}.
209      * 
210      * @see JcrQueryResult#getNodes()
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          * {@inheritDoc}
225          * 
226          * @see javax.jcr.NodeIterator#nextNode()
227          */
228         public Node nextNode() {
229             Node node = nodes.next();
230             ++position;
231             return node;
232         }
233 
234         /**
235          * {@inheritDoc}
236          * 
237          * @see javax.jcr.RangeIterator#getPosition()
238          */
239         public long getPosition() {
240             return position;
241         }
242 
243         /**
244          * {@inheritDoc}
245          * 
246          * @see javax.jcr.RangeIterator#getSize()
247          */
248         public long getSize() {
249             return size;
250         }
251 
252         /**
253          * {@inheritDoc}
254          * 
255          * @see javax.jcr.RangeIterator#skip(long)
256          */
257         public void skip( long skipNum ) {
258             for (long i = 0L; i != skipNum; ++i)
259                 nextNode();
260         }
261 
262         /**
263          * {@inheritDoc}
264          * 
265          * @see java.util.Iterator#hasNext()
266          */
267         public boolean hasNext() {
268             return nodes.hasNext();
269         }
270 
271         /**
272          * {@inheritDoc}
273          * 
274          * @see java.util.Iterator#next()
275          */
276         public Object next() {
277             return nextNode();
278         }
279 
280         /**
281          * {@inheritDoc}
282          * 
283          * @see java.util.Iterator#remove()
284          */
285         public void remove() {
286             throw new UnsupportedOperationException();
287         }
288     }
289 
290     /**
291      * The {@link RowIterator} implementation returned by the {@link JcrQueryResult}.
292      * 
293      * @see JcrQueryResult#getRows()
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          * {@inheritDoc}
333          * 
334          * @see javax.jcr.query.RowIterator#nextRow()
335          */
336         public Row nextRow() {
337             if (nextRow == null) {
338                 // Didn't call 'hasNext()' ...
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          * {@inheritDoc}
351          * 
352          * @see javax.jcr.RangeIterator#getPosition()
353          */
354         public long getPosition() {
355             return position;
356         }
357 
358         /**
359          * {@inheritDoc}
360          * 
361          * @see javax.jcr.RangeIterator#getSize()
362          */
363         public long getSize() {
364             return numRows;
365         }
366 
367         /**
368          * {@inheritDoc}
369          * 
370          * @see javax.jcr.RangeIterator#skip(long)
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          * {@inheritDoc}
381          * 
382          * @see java.util.Iterator#hasNext()
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                     // Get the next row ...
393                     nextRow = getNextRow(tuple);
394                     if (nextRow != null) return true;
395                 } catch (RepositoryException e) {
396                     // The node could not be found in this session, so skip it ...
397                 }
398                 --numRows;
399             }
400             return false;
401         }
402 
403         protected Row getNextRow( Object[] tuple ) throws RepositoryException {
404             // Make sure that each node referenced by the tuple exists and is accessible ...
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                         // Skip this record because one of the nodes no longer exists ...
413                         return null;
414                     }
415                     nodes[index++] = node;
416                 } catch (AccessDeniedException e) {
417                     // No access to this node, so skip the record ...
418                     return null;
419                 }
420             }
421             return new MultiSelectorQueryResultRow(this, nodes, locationIndexes, tuple);
422         }
423 
424         /**
425          * {@inheritDoc}
426          * 
427          * @see java.util.Iterator#next()
428          */
429         public Object next() {
430             return nextRow();
431         }
432 
433         /**
434          * {@inheritDoc}
435          * 
436          * @see java.util.Iterator#remove()
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      * The {@link RowIterator} implementation returned by the {@link JcrQueryResult}.
469      * 
470      * @see JcrQueryResult#getRows()
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          * {@inheritDoc}
490          * 
491          * @see QueryResultRowIterator#getNextRow(java.lang.Object[])
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          * {@inheritDoc}
525          * 
526          * @see org.modeshape.jcr.api.query.Row#getNode(java.lang.String)
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          * {@inheritDoc}
538          * 
539          * @see javax.jcr.query.Row#getValue(java.lang.String)
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          * {@inheritDoc}
575          * 
576          * @see javax.jcr.query.Row#getValues()
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          * {@inheritDoc}
645          * 
646          * @see org.modeshape.jcr.api.query.Row#getNode(java.lang.String)
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          * {@inheritDoc}
659          * 
660          * @see javax.jcr.query.Row#getValue(java.lang.String)
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          * {@inheritDoc}
703          * 
704          * @see javax.jcr.query.Row#getValues()
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 }