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.persistence;
20
21 import java.io.Serializable;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Calendar;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import javax.persistence.FlushModeType;
34 import javax.persistence.Query;
35 import javax.persistence.TemporalType;
36
37 import org.apache.commons.collections.map.LinkedMap;
38 import org.apache.openjpa.enhance.Reflection;
39 import org.apache.openjpa.kernel.DelegatingQuery;
40 import org.apache.openjpa.kernel.DelegatingResultList;
41 import org.apache.openjpa.kernel.Filters;
42 import org.apache.openjpa.kernel.QueryOperations;
43 import org.apache.openjpa.kernel.exps.AggregateListener;
44 import org.apache.openjpa.kernel.exps.FilterListener;
45 import org.apache.openjpa.lib.rop.ResultList;
46 import org.apache.openjpa.lib.util.Localizer;
47 import org.apache.openjpa.util.ImplHelper;
48 import org.apache.openjpa.util.RuntimeExceptionTranslator;
49
50 /**
51 * Implementation of {@link Query} interface.
52 *
53 * @author Marc Prud'hommeaux
54 * @author Abe White
55 * @nojavadoc
56 */
57 public class QueryImpl
58 implements OpenJPAQuerySPI, Serializable {
59
60 private static final Object[] EMPTY_ARRAY = new Object[0];
61
62 private static final Localizer _loc = Localizer.forPackage
63 (QueryImpl.class);
64
65 private final DelegatingQuery _query;
66 private transient EntityManagerImpl _em;
67 private transient FetchPlan _fetch;
68
69 private Map _named;
70 private List _positional;
71
72 /**
73 * Constructor; supply factory and delegate.
74 */
75 public QueryImpl(EntityManagerImpl em, RuntimeExceptionTranslator ret,
76 org.apache.openjpa.kernel.Query query) {
77 _em = em;
78 _query = new DelegatingQuery(query, ret);
79 }
80
81 /**
82 * Delegate.
83 */
84 public org.apache.openjpa.kernel.Query getDelegate() {
85 return _query.getDelegate();
86 }
87
88 public OpenJPAEntityManager getEntityManager() {
89 return _em;
90 }
91
92 public String getLanguage() {
93 return _query.getLanguage();
94 }
95
96 public QueryOperationType getOperation() {
97 return QueryOperationType.fromKernelConstant(_query.getOperation());
98 }
99
100 public FetchPlan getFetchPlan() {
101 _em.assertNotCloseInvoked();
102 _query.assertNotSerialized();
103 _query.lock();
104 try {
105 if (_fetch == null)
106 _fetch = ((EntityManagerFactoryImpl) _em.
107 getEntityManagerFactory()).toFetchPlan(_query.getBroker(),
108 _query.getFetchConfiguration());
109 return _fetch;
110 } finally {
111 _query.unlock();
112 }
113 }
114
115 public String getQueryString() {
116 return _query.getQueryString();
117 }
118
119 public boolean getIgnoreChanges() {
120 return _query.getIgnoreChanges();
121 }
122
123 public OpenJPAQuery setIgnoreChanges(boolean ignore) {
124 _em.assertNotCloseInvoked();
125 _query.setIgnoreChanges(ignore);
126 return this;
127 }
128
129 public OpenJPAQuery addFilterListener(FilterListener listener) {
130 _em.assertNotCloseInvoked();
131 _query.addFilterListener(listener);
132 return this;
133 }
134
135 public OpenJPAQuery removeFilterListener(FilterListener listener) {
136 _em.assertNotCloseInvoked();
137 _query.removeFilterListener(listener);
138 return this;
139 }
140
141 public OpenJPAQuery addAggregateListener(AggregateListener listener) {
142 _em.assertNotCloseInvoked();
143 _query.addAggregateListener(listener);
144 return this;
145 }
146
147 public OpenJPAQuery removeAggregateListener(AggregateListener listener) {
148 _em.assertNotCloseInvoked();
149 _query.removeAggregateListener(listener);
150 return this;
151 }
152
153 public Collection getCandidateCollection() {
154 return _query.getCandidateCollection();
155 }
156
157 public OpenJPAQuery setCandidateCollection(Collection coll) {
158 _em.assertNotCloseInvoked();
159 _query.setCandidateCollection(coll);
160 return this;
161 }
162
163 public Class getResultClass() {
164 Class res = _query.getResultType();
165 if (res != null)
166 return res;
167 return _query.getCandidateType();
168 }
169
170 public OpenJPAQuery setResultClass(Class cls) {
171 _em.assertNotCloseInvoked();
172 if (ImplHelper.isManagedType(_em.getConfiguration(), cls))
173 _query.setCandidateType(cls, true);
174 else
175 _query.setResultType(cls);
176 return this;
177 }
178
179 public boolean hasSubclasses() {
180 return _query.hasSubclasses();
181 }
182
183 public OpenJPAQuery setSubclasses(boolean subs) {
184 _em.assertNotCloseInvoked();
185 Class cls = _query.getCandidateType();
186 _query.setCandidateExtent(_query.getBroker().newExtent(cls, subs));
187 return this;
188 }
189
190 public int getFirstResult() {
191 return asInt(_query.getStartRange());
192 }
193
194 public OpenJPAQuery setFirstResult(int startPosition) {
195 _em.assertNotCloseInvoked();
196 long end;
197 if (_query.getEndRange() == Long.MAX_VALUE)
198 end = Long.MAX_VALUE;
199 else
200 end = startPosition +
201 (_query.getEndRange() - _query.getStartRange());
202 _query.setRange(startPosition, end);
203 return this;
204 }
205
206 public int getMaxResults() {
207 return asInt(_query.getEndRange() - _query.getStartRange());
208 }
209
210 public OpenJPAQuery setMaxResults(int max) {
211 _em.assertNotCloseInvoked();
212 long start = _query.getStartRange();
213 if (max == Integer.MAX_VALUE)
214 _query.setRange(start, Long.MAX_VALUE);
215 else
216 _query.setRange(start, start + max);
217 return this;
218 }
219
220 public OpenJPAQuery compile() {
221 _em.assertNotCloseInvoked();
222 _query.compile();
223 return this;
224 }
225
226 private Object execute() {
227 if (_query.getOperation() != QueryOperations.OP_SELECT)
228 throw new InvalidStateException(_loc.get("not-select-query",
229 _query.getQueryString()), null, null, false);
230
231 validateParameters();
232
233 // handle which types of parameters we are using, if any
234 if (_positional != null)
235 return _query.execute(_positional.toArray());
236 if (_named != null)
237 return _query.execute(_named);
238 return _query.execute();
239 }
240
241 /**
242 * Validate that the types of the parameters are correct.
243 */
244 private void validateParameters() {
245 if (_positional != null) {
246 LinkedMap types = _query.getParameterTypes();
247 for (int i = 0,
248 size = Math.min(_positional.size(), types.size());
249 i < size; i++)
250 validateParameter(String.valueOf(i),
251 (Class) types.getValue(i), _positional.get(i));
252 } else if (_named != null) {
253 Map types = _query.getParameterTypes();
254 for (Iterator i = _named.entrySet().iterator(); i.hasNext();) {
255 Map.Entry entry = (Map.Entry) i.next();
256 String name = (String) entry.getKey();
257 validateParameter(name, (Class) types.get(name),
258 entry.getValue());
259 }
260 }
261 }
262
263 private void validateParameter(String paramDesc, Class type, Object param) {
264 // null parameters are allowed, so are not validated
265 if (param == null || type == null)
266 return;
267
268 // check the parameter against the wrapped type
269 if (!Filters.wrap(type).isInstance(param))
270 throw new ArgumentException(_loc.get("bad-param-type",
271 paramDesc, param.getClass().getName(), type.getName()),
272 null, null, false);
273 }
274
275 public List getResultList() {
276 _em.assertNotCloseInvoked();
277 Object ob = execute();
278 if (ob instanceof List) {
279 List ret = (List) ob;
280 if (ret instanceof ResultList)
281 return new DelegatingResultList((ResultList) ret,
282 PersistenceExceptions.getRollbackTranslator(_em));
283 else
284 return ret;
285 }
286
287 return Collections.singletonList(ob);
288 }
289
290 /**
291 * Execute a query that returns a single result.
292 */
293 public Object getSingleResult() {
294 _em.assertNotCloseInvoked();
295 // temporarily set query to unique so that a single result is validated
296 // and returned; unset again in case the user executes query again
297 // via getResultList
298 _query.setUnique(true);
299 try {
300 return execute();
301 } finally {
302 _query.setUnique(false);
303 }
304 }
305
306 public int executeUpdate() {
307 _em.assertNotCloseInvoked();
308 if (_query.getOperation() == QueryOperations.OP_DELETE) {
309 // handle which types of parameters we are using, if any
310 if (_positional != null)
311 return asInt(_query.deleteAll(_positional.toArray()));
312 if (_named != null)
313 return asInt(_query.deleteAll(_named));
314 return asInt(_query.deleteAll());
315 }
316 if (_query.getOperation() == QueryOperations.OP_UPDATE) {
317 // handle which types of parameters we are using, if any
318 if (_positional != null)
319 return asInt(_query.updateAll(_positional.toArray()));
320 if (_named != null)
321 return asInt(_query.updateAll(_named));
322 return asInt(_query.updateAll());
323 }
324 throw new InvalidStateException(_loc.get("not-update-delete-query",
325 _query.getQueryString()), null, null, false);
326 }
327
328 /**
329 * Cast the specified long down to an int, first checking for overflow.
330 */
331 private static int asInt(long l) {
332 if (l > Integer.MAX_VALUE)
333 return Integer.MAX_VALUE;
334 if (l < Integer.MIN_VALUE) // unlikely, but we might as well check
335 return Integer.MIN_VALUE;
336 return (int) l;
337 }
338
339 public FlushModeType getFlushMode() {
340 return EntityManagerImpl.fromFlushBeforeQueries(_query.
341 getFetchConfiguration().getFlushBeforeQueries());
342 }
343
344 public OpenJPAQuery setFlushMode(FlushModeType flushMode) {
345 _em.assertNotCloseInvoked();
346 _query.getFetchConfiguration().setFlushBeforeQueries
347 (EntityManagerImpl.toFlushBeforeQueries(flushMode));
348 return this;
349 }
350
351 public OpenJPAQuery setHint(String key, Object value) {
352 _em.assertNotCloseInvoked();
353 if (key == null || !key.startsWith("openjpa."))
354 return this;
355 String k = key.substring("openjpa.".length());
356
357 try {
358 if ("Subclasses".equals(k)) {
359 if (value instanceof String)
360 value = Boolean.valueOf((String) value);
361 setSubclasses(((Boolean) value).booleanValue());
362 } else if ("FilterListener".equals(k))
363 addFilterListener(Filters.hintToFilterListener(value,
364 _query.getBroker().getClassLoader()));
365 else if ("FilterListeners".equals(k)) {
366 FilterListener[] arr = Filters.hintToFilterListeners(value,
367 _query.getBroker().getClassLoader());
368 for (int i = 0; i < arr.length; i++)
369 addFilterListener(arr[i]);
370 } else if ("AggregateListener".equals(k))
371 addAggregateListener(Filters.hintToAggregateListener(value,
372 _query.getBroker().getClassLoader()));
373 else if ("FilterListeners".equals(k)) {
374 AggregateListener[] arr = Filters.hintToAggregateListeners
375 (value, _query.getBroker().getClassLoader());
376 for (int i = 0; i < arr.length; i++)
377 addAggregateListener(arr[i]);
378 } else if (k.startsWith("FetchPlan.")) {
379 k = k.substring("FetchPlan.".length());
380 hintToSetter(getFetchPlan(), k, value);
381 } else if (k.startsWith("hint.")) {
382 if ("hint.OptimizeResultCount".equals(k)) {
383 if (value instanceof String) {
384 try {
385 value = new Integer((String) value);
386 } catch (NumberFormatException nfe) {
387 }
388 }
389 if (!(value instanceof Number)
390 || ((Number) value).intValue() < 0)
391 throw new ArgumentException(_loc.get
392 ("bad-query-hint-value", key, value), null, null,
393 false);
394 }
395 _query.getFetchConfiguration().setHint(key, value);
396 }
397 else
398 throw new ArgumentException(_loc.get("bad-query-hint", key),
399 null, null, false);
400 return this;
401 } catch (Exception e) {
402 throw PersistenceExceptions.toPersistenceException(e);
403 }
404 }
405
406 private void hintToSetter(FetchPlan fetchPlan, String k, Object value) {
407 if (fetchPlan == null || k == null)
408 return;
409
410 Method setter = Reflection.findSetter(fetchPlan.getClass(), k, true);
411 Class paramType = setter.getParameterTypes()[0];
412 if (Enum.class.isAssignableFrom(paramType) && value instanceof String)
413 value = Enum.valueOf(paramType, (String) value);
414
415 Filters.hintToSetter(fetchPlan, k, value);
416 }
417
418 public OpenJPAQuery setParameter(int position, Calendar value,
419 TemporalType t) {
420 return setParameter(position, value);
421 }
422
423 public OpenJPAQuery setParameter(int position, Date value,
424 TemporalType type) {
425 return setParameter(position, value);
426 }
427
428 public OpenJPAQuery setParameter(int position, Object value) {
429 _query.assertOpen();
430 _em.assertNotCloseInvoked();
431 _query.lock();
432 try {
433 // not allowed to mix positional and named parameters (EDR2 3.6.4)
434 if (_named != null)
435 throw new InvalidStateException(_loc.get
436 ("no-pos-named-params-mix", _query.getQueryString()),
437 null, null, false);
438
439 if (position < 1)
440 throw new InvalidStateException(_loc.get
441 ("illegal-index", position), null, null, false);
442
443 if (_positional == null)
444 _positional = new ArrayList();
445
446 // make sure it is at least the requested size
447 while (_positional.size() < position)
448 _positional.add(null);
449
450 // note that we add it to position - 1, since setPosition
451 // starts at 1, while List starts at 0
452 _positional.set(position - 1, value);
453 return this;
454 } finally {
455 _query.unlock();
456 }
457 }
458
459 public OpenJPAQuery setParameter(String name, Calendar value,
460 TemporalType t) {
461 return setParameter(name, value);
462 }
463
464 public OpenJPAQuery setParameter(String name, Date value,
465 TemporalType type) {
466 return setParameter(name, value);
467 }
468
469 public OpenJPAQuery setParameter(String name, Object value) {
470 _query.assertOpen();
471 _em.assertNotCloseInvoked();
472 _query.lock();
473 try {
474 // not allowed to mix positional and named parameters (EDR2 3.6.4)
475 if (_positional != null)
476 throw new InvalidStateException(_loc.get
477 ("no-pos-named-params-mix", _query.getQueryString()),
478 null, null, false);
479
480 if (_named == null)
481 _named = new HashMap();
482 _named.put(name, value);
483 return this;
484 } finally {
485 _query.unlock();
486 }
487 }
488
489 public boolean hasPositionalParameters() {
490 return _positional != null;
491 }
492
493 public Object[] getPositionalParameters() {
494 _query.lock();
495 try {
496 return (_positional == null) ? EMPTY_ARRAY : _positional.toArray();
497 } finally {
498 _query.unlock();
499 }
500 }
501
502 public OpenJPAQuery setParameters(Object... params) {
503 _query.assertOpen();
504 _em.assertNotCloseInvoked();
505 _query.lock();
506 try {
507 _positional = null;
508 _named = null;
509 if (params != null)
510 for (int i = 0; i < params.length; i++)
511 setParameter(i + 1, params[i]);
512 return this;
513 } finally {
514 _query.unlock();
515 }
516 }
517
518 public Map getNamedParameters() {
519 _query.lock();
520 try {
521 return (_named == null) ? Collections.EMPTY_MAP
522 : Collections.unmodifiableMap(_named);
523 } finally {
524 _query.unlock();
525 }
526 }
527
528 public OpenJPAQuery setParameters(Map params) {
529 _query.assertOpen();
530 _em.assertNotCloseInvoked();
531 _query.lock();
532 try {
533 _positional = null;
534 _named = null;
535 if (params != null)
536 for (Map.Entry e : (Set<Map.Entry>) params.entrySet())
537 setParameter((String) e.getKey(), e.getValue());
538 return this;
539 } finally {
540 _query.unlock();
541 }
542 }
543
544 public OpenJPAQuery closeAll() {
545 _query.closeAll();
546 return this;
547 }
548
549 public String[] getDataStoreActions(Map params) {
550 return _query.getDataStoreActions(params);
551 }
552
553 public int hashCode() {
554 return _query.hashCode();
555 }
556
557 public boolean equals(Object other) {
558 if (other == this)
559 return true;
560 if (!(other instanceof QueryImpl))
561 return false;
562 return _query.equals(((QueryImpl) other)._query);
563 }
564 }