1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.kernel;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.apache.commons.collections.map.LinkedMap;
31 import org.apache.openjpa.conf.OpenJPAConfiguration;
32 import org.apache.openjpa.kernel.exps.AbstractExpressionVisitor;
33 import org.apache.openjpa.kernel.exps.AggregateListener;
34 import org.apache.openjpa.kernel.exps.Constant;
35 import org.apache.openjpa.kernel.exps.ExpressionFactory;
36 import org.apache.openjpa.kernel.exps.ExpressionParser;
37 import org.apache.openjpa.kernel.exps.FilterListener;
38 import org.apache.openjpa.kernel.exps.InMemoryExpressionFactory;
39 import org.apache.openjpa.kernel.exps.Path;
40 import org.apache.openjpa.kernel.exps.QueryExpressions;
41 import org.apache.openjpa.kernel.exps.Resolver;
42 import org.apache.openjpa.kernel.exps.StringContains;
43 import org.apache.openjpa.kernel.exps.Val;
44 import org.apache.openjpa.kernel.exps.Value;
45 import org.apache.openjpa.kernel.exps.WildcardMatch;
46 import org.apache.openjpa.lib.rop.ListResultObjectProvider;
47 import org.apache.openjpa.lib.rop.RangeResultObjectProvider;
48 import org.apache.openjpa.lib.rop.ResultObjectProvider;
49 import org.apache.openjpa.lib.util.Localizer;
50 import org.apache.openjpa.meta.ClassMetaData;
51 import org.apache.openjpa.meta.FieldMetaData;
52 import org.apache.openjpa.meta.JavaTypes;
53 import org.apache.openjpa.util.ImplHelper;
54 import org.apache.openjpa.util.InvalidStateException;
55 import org.apache.openjpa.util.UnsupportedException;
56 import org.apache.openjpa.util.UserException;
57
58 /**
59 * Implementation of an expression-based query, which can handle
60 * String-based query expressions such as JPQL and JDOQL.
61 * This implementation is suitable for in-memory operation.
62 * Override the following methods to also support datastore operation:
63 * <ul>
64 * <li>Override {@link #supportsDataStoreExecution} to return
65 * <code>true</code>.</li>
66 * <li>Override {@link #executeQuery}, {@link #executeDelete}, and
67 * {@link #executeUpdate} to execute the query against the data store.
68 * Keep in mind that the parameters passed to this method might be in use
69 * by several threads in different query instances. Thus components like
70 * the expression factory must either be thread safe, or this method must
71 * synchronize on them.</li>
72 * <li>Override {@link #getDataStoreActions} to return a representation of
73 * the actions that will be taken on the data store. For use in visual
74 * tools.</li>
75 * <li>Override {@link #getExpressionFactory} to return a factory for creating
76 * expressions in the datastore's language. The factory must be cachable.</li>
77 * </ul>
78 *
79 * @author Abe White
80 */
81 public class ExpressionStoreQuery
82 extends AbstractStoreQuery {
83
84 private static final Localizer _loc = Localizer.forPackage
85 (ExpressionStoreQuery.class);
86
87 // maintain support for a couple of deprecated extensions
88 private static final FilterListener[] _listeners = new FilterListener[]{
89 new StringContains(), new WildcardMatch(),
90 };
91
92 private final ExpressionParser _parser;
93 private transient Object _parsed;
94
95 /**
96 * Construct a query with a parser for the language.
97 */
98 public ExpressionStoreQuery(ExpressionParser parser) {
99 _parser = parser;
100 }
101
102 /**
103 * Resolver used in parsing.
104 */
105 public Resolver getResolver() {
106 return new Resolver() {
107 public Class classForName(String name, String[] imports) {
108 return ctx.classForName(name, imports);
109 }
110
111 public FilterListener getFilterListener(String tag) {
112 return ctx.getFilterListener(tag);
113 }
114
115 public AggregateListener getAggregateListener(String tag) {
116 return ctx.getAggregateListener(tag);
117 }
118
119 public OpenJPAConfiguration getConfiguration() {
120 return ctx.getStoreContext().getConfiguration();
121 }
122
123 public QueryContext getQueryContext() {
124 return ctx;
125 }
126 };
127 }
128
129 /**
130 * Allow direct setting of parsed state for facades that do parsing.
131 * The facade should call this method twice: once with the query string,
132 * and again with the parsed state.
133 */
134 public boolean setQuery(Object query) {
135 _parsed = query;
136 return true;
137 }
138
139 public FilterListener getFilterListener(String tag) {
140 for (int i = 0; i < _listeners.length; i++)
141 if (_listeners[i].getTag().equals(tag))
142 return _listeners[i];
143 return null;
144 }
145
146 public Object newCompilation() {
147 if (_parsed != null)
148 return _parsed;
149 return _parser.parse(ctx.getQueryString(), this);
150 }
151
152 public void populateFromCompilation(Object comp) {
153 _parser.populate(comp, this);
154 }
155
156 public void invalidateCompilation() {
157 _parsed = null;
158 }
159
160 public boolean supportsInMemoryExecution() {
161 return true;
162 }
163
164 public Executor newInMemoryExecutor(ClassMetaData meta, boolean subs) {
165 return new InMemoryExecutor(this, meta, subs, _parser,
166 ctx.getCompilation());
167 }
168
169 public Executor newDataStoreExecutor(ClassMetaData meta, boolean subs) {
170 return new DataStoreExecutor(this, meta, subs, _parser,
171 ctx.getCompilation());
172 }
173
174 ////////////////////////
175 // Methods for Override
176 ////////////////////////
177
178 /**
179 * Execute the given expression against the given candidate extent.
180 *
181 * @param ex current executor
182 * @param base the base type the query should match
183 * @param types the independent candidate types
184 * @param subclasses true if subclasses should be included in the results
185 * @param facts the expression factory used to build the query for
186 * each base type
187 * @param parsed the parsed query values
188 * @param params parameter values, or empty array
189 * @param range result range
190 * @return a provider for matching objects
191 */
192 protected ResultObjectProvider executeQuery(Executor ex,
193 ClassMetaData base, ClassMetaData[] types, boolean subclasses,
194 ExpressionFactory[] facts, QueryExpressions[] parsed, Object[] params,
195 Range range) {
196 throw new UnsupportedException();
197 }
198
199 /**
200 * Execute the given expression against the given candidate extent
201 * and delete the instances.
202 *
203 * @param ex current executor
204 * @param base the base type the query should match
205 * @param types the independent candidate types
206 * @param subclasses true if subclasses should be included in the results
207 * @param facts the expression factory used to build the query for
208 * each base type
209 * @param parsed the parsed query values
210 * @param params parameter values, or empty array
211 * @return a number indicating the number of instances deleted,
212 * or null to execute the delete in memory
213 */
214 protected Number executeDelete(Executor ex, ClassMetaData base,
215 ClassMetaData[] types, boolean subclasses, ExpressionFactory[] facts,
216 QueryExpressions[] parsed, Object[] params) {
217 return null;
218 }
219
220 /**
221 * Execute the given expression against the given candidate extent
222 * and updates the instances.
223 *
224 * @param ex current executor
225 * @param base the base type the query should match
226 * @param types the independent candidate types
227 * @param subclasses true if subclasses should be included in the results
228 * @param facts the expression factory used to build the query for
229 * each base type
230 * @param parsed the parsed query values
231 * @param params parameter values, or empty array
232 * @return a number indicating the number of instances updated,
233 * or null to execute the update in memory.
234 */
235 protected Number executeUpdate(Executor ex, ClassMetaData base,
236 ClassMetaData[] types, boolean subclasses, ExpressionFactory[] facts,
237 QueryExpressions[] parsed, Object[] params) {
238 return null;
239 }
240
241 /**
242 * Return the commands that will be sent to the datastore in order
243 * to execute the query, typically in the database's native language.
244 *
245 * @param base the base type the query should match
246 * @param types the independent candidate types
247 * @param subclasses true if subclasses should be included in the results
248 * @param facts the expression factory used to build the query for
249 * each base type
250 * @param parsed the parsed query values
251 * @param params parameter values, or empty array
252 * @param range result range
253 * @return a textual description of the query to execute
254 */
255 protected String[] getDataStoreActions(ClassMetaData base,
256 ClassMetaData[] types, boolean subclasses, ExpressionFactory[] facts,
257 QueryExpressions[] parsed, Object[] params, Range range) {
258 return StoreQuery.EMPTY_STRINGS;
259 }
260
261 /**
262 * Return the assignable types for the given metadata whose expression
263 * trees must be compiled independently.
264 */
265 protected ClassMetaData[] getIndependentExpressionCandidates
266 (ClassMetaData type, boolean subclasses) {
267 return new ClassMetaData[]{ type };
268 }
269
270 /**
271 * Return an {@link ExpressionFactory} to use to create an expression to
272 * be executed against an extent. Each factory will be used to compile
273 * one filter only. The factory must be cachable.
274 */
275 protected ExpressionFactory getExpressionFactory(ClassMetaData type) {
276 throw new UnsupportedException();
277 }
278
279 /**
280 * Provides support for queries that hold query information
281 * in a {@link QueryExpressions} instance.
282 *
283 * @author Marc Prud'hommeaux
284 */
285 public static abstract class AbstractExpressionExecutor
286 extends AbstractExecutor
287 implements Executor {
288
289 /**
290 * Return the parsed query expressions for our candidate types.
291 */
292 protected abstract QueryExpressions[] getQueryExpressions();
293
294 /**
295 * Return the query expressions for one candidate type, or die if none.
296 */
297 private QueryExpressions assertQueryExpression() {
298 QueryExpressions[] exp = getQueryExpressions();
299 if (exp == null || exp.length < 1)
300 throw new InvalidStateException(_loc.get("no-expressions"));
301
302 return exp[0];
303 }
304
305 /**
306 * Throw proper exception if given value is a collection/map/array.
307 */
308 protected void assertNotContainer(Value val, StoreQuery q) {
309 // variables represent container elements, not the container itself
310 if (val.isVariable())
311 return;
312
313 Class type;
314 if (val instanceof Path) {
315 FieldMetaData fmd = ((Path) val).last();
316 type = (fmd == null) ? val.getType() : fmd.getDeclaredType();
317 } else
318 type = val.getType();
319
320 switch (JavaTypes.getTypeCode(type)) {
321 case JavaTypes.ARRAY:
322 case JavaTypes.COLLECTION:
323 case JavaTypes.MAP:
324 throw new UserException(_loc.get("container-projection",
325 q.getContext().getQueryString()));
326 }
327 }
328
329 public final void validate(StoreQuery q) {
330 QueryExpressions exps = assertQueryExpression();
331 ValidateGroupingExpressionVisitor.validate(q.getContext(), exps);
332 }
333
334 public void getRange(StoreQuery q, Object[] params, Range range) {
335 QueryExpressions exps = assertQueryExpression();
336 if (exps.range.length == 0)
337 return;
338
339 if (exps.range.length == 2
340 && exps.range[0] instanceof Constant
341 && exps.range[1] instanceof Constant) {
342 try {
343 range.start = ((Number) ((Constant) exps.range[0]).
344 getValue(params)).longValue();
345 range.end = ((Number) ((Constant) exps.range[1]).
346 getValue(params)).longValue();
347 return;
348 } catch (ClassCastException cce) {
349 // fall through to exception below
350 } catch (NullPointerException npe) {
351 // fall through to exception below
352 }
353 }
354 throw new UserException(_loc.get("only-range-constants",
355 q.getContext().getQueryString()));
356 }
357
358 public final Class getResultClass(StoreQuery q) {
359 return assertQueryExpression().resultClass;
360 }
361
362 public final boolean[] getAscending(StoreQuery q) {
363 return assertQueryExpression().ascending;
364 }
365
366 public final String getAlias(StoreQuery q) {
367 return assertQueryExpression().alias;
368 }
369
370 public final String[] getProjectionAliases(StoreQuery q) {
371 return assertQueryExpression().projectionAliases;
372 }
373
374 public final int getOperation(StoreQuery q) {
375 return assertQueryExpression().operation;
376 }
377
378 public final boolean isAggregate(StoreQuery q) {
379 return assertQueryExpression().isAggregate();
380 }
381
382 public final boolean hasGrouping(StoreQuery q) {
383 return assertQueryExpression().grouping.length > 0;
384 }
385
386 public final LinkedMap getParameterTypes(StoreQuery q) {
387 return assertQueryExpression().parameterTypes;
388 }
389
390 public final Map getUpdates(StoreQuery q) {
391 return assertQueryExpression().updates;
392 }
393
394 public final ClassMetaData[] getAccessPathMetaDatas(StoreQuery q) {
395 QueryExpressions[] exps = getQueryExpressions();
396 if (exps.length == 1)
397 return exps[0].accessPath;
398
399 List metas = null;
400 for (int i = 0; i < exps.length; i++)
401 metas = Filters.addAccessPathMetaDatas(metas,
402 exps[i].accessPath);
403 if (metas == null)
404 return StoreQuery.EMPTY_METAS;
405 return (ClassMetaData[]) metas.toArray
406 (new ClassMetaData[metas.size()]);
407 }
408
409 public boolean isPacking(StoreQuery q) {
410 return false;
411 }
412
413 /**
414 * Throws an exception if select or having clauses contain
415 * non-aggregate, non-grouped paths.
416 */
417 private static class ValidateGroupingExpressionVisitor
418 extends AbstractExpressionVisitor {
419
420 private final QueryContext _ctx;
421 private boolean _grouping = false;
422 private Set _grouped = null;
423 private Value _agg = null;
424
425 /**
426 * Throw proper exception if query does not meet validation.
427 */
428 public static void validate(QueryContext ctx,
429 QueryExpressions exps) {
430 if (exps.grouping.length == 0)
431 return;
432
433 ValidateGroupingExpressionVisitor visitor =
434 new ValidateGroupingExpressionVisitor(ctx);
435 visitor._grouping = true;
436 for (int i = 0; i < exps.grouping.length; i++)
437 exps.grouping[i].acceptVisit(visitor);
438 visitor._grouping = false;
439 if (exps.having != null)
440 exps.having.acceptVisit(visitor);
441 for (int i = 0; i < exps.projections.length; i++)
442 exps.projections[i].acceptVisit(visitor);
443 }
444
445 public ValidateGroupingExpressionVisitor(QueryContext ctx) {
446 _ctx = ctx;
447 }
448
449 public void enter(Value val) {
450 if (_grouping) {
451 if (val instanceof Path) {
452 if (_grouped == null)
453 _grouped = new HashSet();
454 _grouped.add(val);
455 }
456 } else if (_agg == null) {
457 if (val.isAggregate())
458 _agg = val;
459 else if (val instanceof Path
460 && (_grouped == null || !_grouped.contains(val))) {
461 throw new UserException(_loc.get("bad-grouping",
462 _ctx.getCandidateType(), _ctx.getQueryString()));
463 }
464 }
465 }
466
467 public void exit(Value val) {
468 if (val == _agg)
469 _agg = null;
470 }
471 }
472 }
473
474 /**
475 * Runs the expression query in memory.
476 */
477 private static class InMemoryExecutor
478 extends AbstractExpressionExecutor
479 implements Executor, Serializable {
480
481 private final ClassMetaData _meta;
482 private final boolean _subs;
483 private final InMemoryExpressionFactory _factory;
484 private final QueryExpressions[] _exps;
485 private final Class[] _projTypes;
486
487 public InMemoryExecutor(ExpressionStoreQuery q,
488 ClassMetaData candidate, boolean subclasses,
489 ExpressionParser parser, Object parsed) {
490 _meta = candidate;
491 _subs = subclasses;
492 _factory = new InMemoryExpressionFactory();
493
494 _exps = new QueryExpressions[] {
495 parser.eval(parsed, q, _factory, _meta)
496 };
497 if (_exps[0].projections.length == 0)
498 _projTypes = StoreQuery.EMPTY_CLASSES;
499 else {
500 AssertNoVariablesExpressionVisitor novars = new
501 AssertNoVariablesExpressionVisitor(q.getContext());
502 _projTypes = new Class[_exps[0].projections.length];
503 for (int i = 0; i < _exps[0].projections.length; i++) {
504 _projTypes[i] = _exps[0].projections[i].getType();
505 assertNotContainer(_exps[0].projections[i], q);
506 _exps[0].projections[i].acceptVisit(novars);
507 }
508 for (int i = 0; i < _exps[0].grouping.length; i++)
509 _exps[0].grouping[i].acceptVisit(novars);
510 }
511 }
512
513 protected QueryExpressions[] getQueryExpressions() {
514 return _exps;
515 }
516
517 public ResultObjectProvider executeQuery(StoreQuery q,
518 Object[] params, Range range) {
519 // execute in memory for candidate collection;
520 // also execute in memory for transactional extents
521 Collection coll = q.getContext().getCandidateCollection();
522 Iterator itr;
523 if (coll != null)
524 itr = coll.iterator();
525 else
526 itr = q.getContext().getStoreContext().
527 extentIterator(_meta.getDescribedType(), _subs,
528 q.getContext().getFetchConfiguration(),
529 q.getContext().getIgnoreChanges());
530
531 // find matching objects
532 List results = new ArrayList();
533 StoreContext ctx = q.getContext().getStoreContext();
534 try {
535 Object obj;
536 while (itr.hasNext()) {
537 obj = itr.next();
538 if (_factory.matches(_exps[0], _meta, _subs, obj, ctx,
539 params))
540 results.add(obj);
541 }
542 }
543 finally {
544 ImplHelper.close(itr);
545 }
546
547 // group results
548 results = _factory.group(_exps[0], results, ctx, params);
549
550 // apply having to filter groups
551 if (_exps[0].having != null) {
552 List matches = new ArrayList(results.size());
553 Collection c;
554 itr = results.iterator();
555 while (itr.hasNext()) {
556 c = (Collection) itr.next();
557 if (_factory.matches(_exps[0], c, ctx, params))
558 matches.add(c);
559 }
560 results = matches;
561 }
562
563 // apply projections, order results, and filter duplicates
564 results = _factory.project(_exps[0], results, ctx, params);
565 results = _factory.order(_exps[0], results, ctx, params);
566 results = _factory.distinct(_exps[0], coll == null, results);
567
568 ResultObjectProvider rop = new ListResultObjectProvider(results);
569 if (range.start != 0 || range.end != Long.MAX_VALUE)
570 rop = new RangeResultObjectProvider(rop, range.start,range.end);
571 return rop;
572 }
573
574 public String[] getDataStoreActions(StoreQuery q, Object[] params,
575 Range range) {
576 // in memory queries have no datastore actions to perform
577 return StoreQuery.EMPTY_STRINGS;
578 }
579
580 public Object getOrderingValue(StoreQuery q, Object[] params,
581 Object resultObject, int orderIndex) {
582 // if this is a projection, then we have to order on something
583 // we selected
584 if (_exps[0].projections.length > 0) {
585 String ordering = _exps[0].orderingClauses[orderIndex];
586 for (int i = 0; i < _exps[0].projectionClauses.length; i++)
587 if (ordering.equals(_exps[0].projectionClauses[i]))
588 return ((Object[]) resultObject)[i];
589
590 throw new InvalidStateException(_loc.get
591 ("merged-order-with-result", q.getContext().getLanguage(),
592 q.getContext().getQueryString(), ordering));
593 }
594
595 // use the parsed ordering expression to extract the ordering value
596 Val val = (Val) _exps[0].ordering[orderIndex];
597 return val.evaluate(resultObject, resultObject, q.getContext().
598 getStoreContext(), params);
599 }
600
601 public Class[] getProjectionTypes(StoreQuery q) {
602 return _projTypes;
603 }
604
605 /**
606 * Throws an exception if a variable is found.
607 */
608 private static class AssertNoVariablesExpressionVisitor
609 extends AbstractExpressionVisitor {
610
611 private final QueryContext _ctx;
612
613 public AssertNoVariablesExpressionVisitor(QueryContext ctx) {
614 _ctx = ctx;
615 }
616
617 public void enter(Value val) {
618 if (!val.isVariable())
619 return;
620 throw new UnsupportedException(_loc.get("inmem-agg-proj-var",
621 _ctx.getCandidateType(), _ctx.getQueryString()));
622 }
623 }
624 }
625
626 /**
627 * The DataStoreExecutor executes the query against the
628 * implementation's overridden {@link #executeQuery} method.
629 *
630 * @author Marc Prud'hommeaux
631 */
632 public static class DataStoreExecutor
633 extends AbstractExpressionExecutor
634 implements Executor, Serializable {
635
636 private ClassMetaData _meta;
637 private ClassMetaData[] _metas;
638 private boolean _subs;
639 private ExpressionParser _parser;
640 private ExpressionFactory[] _facts;
641 private QueryExpressions[] _exps;
642 private Class[] _projTypes;
643 private Value[] _inMemOrdering;
644
645 public DataStoreExecutor(ExpressionStoreQuery q,
646 ClassMetaData meta, boolean subclasses,
647 ExpressionParser parser, Object parsed) {
648 _metas = q.getIndependentExpressionCandidates(meta, subclasses);
649 if (_metas.length == 0)
650 throw new UserException(_loc.get("query-unmapped", meta));
651 _meta = meta;
652 _subs = subclasses;
653 _parser = parser;
654
655 _facts = new ExpressionFactory[_metas.length];
656 for (int i = 0; i < _facts.length; i++)
657 _facts[i] = q.getExpressionFactory(_metas[i]);
658
659 _exps = new QueryExpressions[_metas.length];
660 for (int i = 0; i < _exps.length; i++)
661 _exps[i] = parser.eval(parsed, q, _facts[i], _metas[i]);
662
663 if (_exps[0].projections.length == 0)
664 _projTypes = StoreQuery.EMPTY_CLASSES;
665 else {
666 _projTypes = new Class[_exps[0].projections.length];
667 for (int i = 0; i < _exps[0].projections.length; i++) {
668 assertNotContainer(_exps[0].projections[i], q);
669 _projTypes[i] = _exps[0].projections[i].getType();
670 }
671 }
672 }
673
674 protected QueryExpressions[] getQueryExpressions() {
675 return _exps;
676 }
677
678 public ResultObjectProvider executeQuery(StoreQuery q,
679 Object[] params, Range range) {
680 range.lrs &= !isAggregate(q) && !hasGrouping(q);
681 return ((ExpressionStoreQuery) q).executeQuery(this, _meta, _metas,
682 _subs, _facts, _exps, params, range);
683 }
684
685 public Number executeDelete(StoreQuery q, Object[] params) {
686 Number num = ((ExpressionStoreQuery) q).executeDelete(this, _meta,
687 _metas, _subs, _facts, _exps, params);
688 if (num == null)
689 return q.getContext().deleteInMemory(q, this, params);
690 return num;
691 }
692
693 public Number executeUpdate(StoreQuery q, Object[] params) {
694 Number num = ((ExpressionStoreQuery) q).executeUpdate(this, _meta,
695 _metas, _subs, _facts, _exps, params);
696 if (num == null)
697 return q.getContext().updateInMemory(q, this, params);
698 return num;
699 }
700
701 public String[] getDataStoreActions(StoreQuery q, Object[] params,
702 Range range) {
703 return ((ExpressionStoreQuery) q).getDataStoreActions(_meta,
704 _metas, _subs, _facts, _exps, params, range);
705 }
706
707 public Object getOrderingValue(StoreQuery q, Object[] params,
708 Object resultObject, int orderIndex) {
709 // if this is a projection, then we have to order on something
710 // we selected
711 if (_exps[0].projections.length > 0) {
712 String ordering = _exps[0].orderingClauses[orderIndex];
713 for (int i = 0; i < _exps[0].projectionClauses.length; i++)
714 if (ordering.equals(_exps[0].projectionClauses[i]))
715 return ((Object[]) resultObject)[i];
716
717 throw new InvalidStateException(_loc.get
718 ("merged-order-with-result", q.getContext().getLanguage(),
719 q.getContext().getQueryString(), ordering));
720 }
721
722 // need to parse orderings?
723 synchronized (this) {
724 if (_inMemOrdering == null) {
725 ExpressionFactory factory = new InMemoryExpressionFactory();
726 _inMemOrdering = _parser.eval(_exps[0].orderingClauses,
727 (ExpressionStoreQuery) q, factory, _meta);
728 }
729 if (_inMemOrdering == null)
730 _inMemOrdering = _exps[0].ordering;
731 }
732
733 // use the parsed ordering expression to extract the ordering value
734 Val val = (Val) _inMemOrdering[orderIndex];
735 return val.evaluate(resultObject, resultObject,
736 q.getContext().getStoreContext(), params);
737 }
738
739 public Class[] getProjectionTypes(StoreQuery q) {
740 return _projTypes;
741 }
742 }
743 }