001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.orm.jdo;
018:
019: import java.lang.reflect.InvocationHandler;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023: import java.util.Collection;
024: import java.util.Map;
025:
026: import javax.jdo.JDOException;
027: import javax.jdo.PersistenceManager;
028: import javax.jdo.PersistenceManagerFactory;
029: import javax.jdo.Query;
030:
031: import org.springframework.dao.DataAccessException;
032: import org.springframework.dao.InvalidDataAccessApiUsageException;
033: import org.springframework.transaction.support.TransactionSynchronizationManager;
034: import org.springframework.util.Assert;
035: import org.springframework.util.ClassUtils;
036:
037: /**
038: * Helper class that simplifies JDO data access code, and converts
039: * JDOExceptions into Spring DataAccessExceptions, following the
040: * <code>org.springframework.dao</code> exception hierarchy.
041: *
042: * <p>The central method is <code>execute</code>, supporting JDO access code
043: * implementing the {@link JdoCallback} interface. It provides JDO PersistenceManager
044: * handling such that neither the JdoCallback implementation nor the calling
045: * code needs to explicitly care about retrieving/closing PersistenceManagers,
046: * or handling JDO lifecycle exceptions.
047: *
048: * <p>Typically used to implement data access or business logic services that
049: * use JDO within their implementation but are JDO-agnostic in their interface.
050: * The latter or code calling the latter only have to deal with business
051: * objects, query objects, and <code>org.springframework.dao</code> exceptions.
052: *
053: * <p>Can be used within a service implementation via direct instantiation
054: * with a PersistenceManagerFactory reference, or get prepared in an
055: * application context and given to services as bean reference.
056: * Note: The PersistenceManagerFactory should always be configured as bean in
057: * the application context, in the first case given to the service directly,
058: * in the second case to the prepared template.
059: *
060: * <p>This class can be considered as direct alternative to working with the
061: * raw JDO PersistenceManager API (through
062: * <code>PersistenceManagerFactoryUtils.getPersistenceManager()</code>).
063: * The major advantage is its automatic conversion to DataAccessExceptions, the
064: * major disadvantage that no checked application exceptions can get thrown from
065: * within data access code. Corresponding checks and the actual throwing of such
066: * exceptions can often be deferred to after callback execution, though.
067: *
068: * <p>{@link LocalPersistenceManagerFactoryBean} is the preferred way of obtaining
069: * a reference to a specific PersistenceManagerFactory, at least in a non-EJB
070: * environment. The Spring application context will manage its lifecycle,
071: * initializing and shutting down the factory as part of the application.
072: *
073: * <p>Note that lazy loading will just work with an open JDO PersistenceManager,
074: * either within a Spring-driven transaction (with JdoTransactionManager or
075: * JtaTransactionManager) or within OpenPersistenceManagerInViewFilter/Interceptor.
076: * Furthermore, some operations just make sense within transactions,
077: * for example: <code>evict</code>, <code>evictAll</code>, <code>flush</code>.
078: *
079: * <p><b>NOTE: This class requires JDO 2.0 or higher, as of Spring 2.5.</b>
080: *
081: * @author Juergen Hoeller
082: * @since 03.06.2003
083: * @see #setPersistenceManagerFactory
084: * @see JdoCallback
085: * @see javax.jdo.PersistenceManager
086: * @see LocalPersistenceManagerFactoryBean
087: * @see JdoTransactionManager
088: * @see org.springframework.transaction.jta.JtaTransactionManager
089: * @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewFilter
090: * @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewInterceptor
091: */
092: public class JdoTemplate extends JdoAccessor implements JdoOperations {
093:
094: private boolean allowCreate = true;
095:
096: private boolean exposeNativePersistenceManager = false;
097:
098: /**
099: * Create a new JdoTemplate instance.
100: */
101: public JdoTemplate() {
102: }
103:
104: /**
105: * Create a new JdoTemplate instance.
106: * @param pmf PersistenceManagerFactory to create PersistenceManagers
107: */
108: public JdoTemplate(PersistenceManagerFactory pmf) {
109: setPersistenceManagerFactory(pmf);
110: afterPropertiesSet();
111: }
112:
113: /**
114: * Create a new JdoTemplate instance.
115: * @param pmf PersistenceManagerFactory to create PersistenceManagers
116: * @param allowCreate if a non-transactional PersistenceManager should be created
117: * when no transactional PersistenceManager can be found for the current thread
118: */
119: public JdoTemplate(PersistenceManagerFactory pmf,
120: boolean allowCreate) {
121: setPersistenceManagerFactory(pmf);
122: setAllowCreate(allowCreate);
123: afterPropertiesSet();
124: }
125:
126: /**
127: * Set if a new PersistenceManager should be created when no transactional
128: * PersistenceManager can be found for the current thread.
129: * <p>JdoTemplate is aware of a corresponding PersistenceManager bound to the
130: * current thread, for example when using JdoTransactionManager.
131: * If allowCreate is true, a new non-transactional PersistenceManager will be
132: * created if none found, which needs to be closed at the end of the operation.
133: * If false, an IllegalStateException will get thrown in this case.
134: * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
135: */
136: public void setAllowCreate(boolean allowCreate) {
137: this .allowCreate = allowCreate;
138: }
139:
140: /**
141: * Return if a new PersistenceManager should be created if no thread-bound found.
142: */
143: public boolean isAllowCreate() {
144: return this .allowCreate;
145: }
146:
147: /**
148: * Set whether to expose the native JDO PersistenceManager to JdoCallback
149: * code. Default is "false": a PersistenceManager proxy will be returned,
150: * suppressing <code>close</code> calls and automatically applying transaction
151: * timeouts (if any).
152: * <p>As there is often a need to cast to a provider-specific PersistenceManager
153: * class in DAOs that use provider-specific functionality, the exposed proxy
154: * implements all interfaces implemented by the original PersistenceManager.
155: * If this is not sufficient, turn this flag to "true".
156: * @see JdoCallback
157: * @see javax.jdo.PersistenceManager
158: * @see #prepareQuery
159: */
160: public void setExposeNativePersistenceManager(
161: boolean exposeNativePersistenceManager) {
162: this .exposeNativePersistenceManager = exposeNativePersistenceManager;
163: }
164:
165: /**
166: * Return whether to expose the native JDO PersistenceManager to JdoCallback
167: * code, or rather a PersistenceManager proxy.
168: */
169: public boolean isExposeNativePersistenceManager() {
170: return this .exposeNativePersistenceManager;
171: }
172:
173: public Object execute(JdoCallback action)
174: throws DataAccessException {
175: return execute(action, isExposeNativePersistenceManager());
176: }
177:
178: public Collection executeFind(JdoCallback action)
179: throws DataAccessException {
180: Object result = execute(action,
181: isExposeNativePersistenceManager());
182: if (result != null && !(result instanceof Collection)) {
183: throw new InvalidDataAccessApiUsageException(
184: "Result object returned from JdoCallback isn't a Collection: ["
185: + result + "]");
186: }
187: return (Collection) result;
188: }
189:
190: /**
191: * Execute the action specified by the given action object within a
192: * PersistenceManager.
193: * @param action callback object that specifies the JDO action
194: * @param exposeNativePersistenceManager whether to expose the native
195: * JDO persistence manager to callback code
196: * @return a result object returned by the action, or <code>null</code>
197: * @throws org.springframework.dao.DataAccessException in case of JDO errors
198: */
199: public Object execute(JdoCallback action,
200: boolean exposeNativePersistenceManager)
201: throws DataAccessException {
202: Assert.notNull(action, "Callback object must not be null");
203:
204: PersistenceManager pm = PersistenceManagerFactoryUtils
205: .getPersistenceManager(getPersistenceManagerFactory(),
206: isAllowCreate());
207: boolean existingTransaction = TransactionSynchronizationManager
208: .hasResource(getPersistenceManagerFactory());
209: try {
210: PersistenceManager pmToExpose = (exposeNativePersistenceManager ? pm
211: : createPersistenceManagerProxy(pm));
212: Object result = action.doInJdo(pmToExpose);
213: flushIfNecessary(pm, existingTransaction);
214: return postProcessResult(result, pm, existingTransaction);
215: } catch (JDOException ex) {
216: throw convertJdoAccessException(ex);
217: } catch (RuntimeException ex) {
218: // callback code threw application exception
219: throw ex;
220: } finally {
221: PersistenceManagerFactoryUtils.releasePersistenceManager(
222: pm, getPersistenceManagerFactory());
223: }
224: }
225:
226: /**
227: * Create a close-suppressing proxy for the given JDO PersistenceManager.
228: * Called by the <code>execute</code> method.
229: * <p>The proxy also prepares returned JDO Query objects.
230: * @param pm the JDO PersistenceManager to create a proxy for
231: * @return the PersistenceManager proxy, implementing all interfaces
232: * implemented by the passed-in PersistenceManager object (that is,
233: * also implementing all provider-specific extension interfaces)
234: * @see javax.jdo.PersistenceManager#close()
235: * @see #execute(JdoCallback, boolean)
236: * @see #prepareQuery
237: */
238: protected PersistenceManager createPersistenceManagerProxy(
239: PersistenceManager pm) {
240: Class[] ifcs = ClassUtils.getAllInterfaces(pm);
241: return (PersistenceManager) Proxy.newProxyInstance(getClass()
242: .getClassLoader(), ifcs,
243: new CloseSuppressingInvocationHandler(pm));
244: }
245:
246: /**
247: * Post-process the given result object, which might be a Collection.
248: * Called by the <code>execute</code> method.
249: * <p>Default implementation always returns the passed-in Object as-is.
250: * Subclasses might override this to automatically detach result
251: * collections or even single result objects.
252: * @param pm the current JDO PersistenceManager
253: * @param result the result object (might be a Collection)
254: * @param existingTransaction if executing within an existing transaction
255: * (within an existing JDO PersistenceManager that won't be closed immediately)
256: * @return the post-processed result object (can be simply be the passed-in object)
257: * @see #execute(JdoCallback, boolean)
258: */
259: protected Object postProcessResult(Object result,
260: PersistenceManager pm, boolean existingTransaction) {
261: return result;
262: }
263:
264: //-------------------------------------------------------------------------
265: // Convenience methods for load, save, delete
266: //-------------------------------------------------------------------------
267:
268: public Object getObjectById(final Object objectId)
269: throws DataAccessException {
270: return execute(new JdoCallback() {
271: public Object doInJdo(PersistenceManager pm)
272: throws JDOException {
273: return pm.getObjectById(objectId, true);
274: }
275: }, true);
276: }
277:
278: public Object getObjectById(final Class entityClass,
279: final Object idValue) throws DataAccessException {
280: return execute(new JdoCallback() {
281: public Object doInJdo(PersistenceManager pm)
282: throws JDOException {
283: return pm.getObjectById(entityClass, idValue);
284: }
285: }, true);
286: }
287:
288: public void evict(final Object entity) throws DataAccessException {
289: execute(new JdoCallback() {
290: public Object doInJdo(PersistenceManager pm)
291: throws JDOException {
292: pm.evict(entity);
293: return null;
294: }
295: }, true);
296: }
297:
298: public void evictAll(final Collection entities)
299: throws DataAccessException {
300: execute(new JdoCallback() {
301: public Object doInJdo(PersistenceManager pm)
302: throws JDOException {
303: pm.evictAll(entities);
304: return null;
305: }
306: }, true);
307: }
308:
309: public void evictAll() throws DataAccessException {
310: execute(new JdoCallback() {
311: public Object doInJdo(PersistenceManager pm)
312: throws JDOException {
313: pm.evictAll();
314: return null;
315: }
316: }, true);
317: }
318:
319: public void refresh(final Object entity) throws DataAccessException {
320: execute(new JdoCallback() {
321: public Object doInJdo(PersistenceManager pm)
322: throws JDOException {
323: pm.refresh(entity);
324: return null;
325: }
326: }, true);
327: }
328:
329: public void refreshAll(final Collection entities)
330: throws DataAccessException {
331: execute(new JdoCallback() {
332: public Object doInJdo(PersistenceManager pm)
333: throws JDOException {
334: pm.refreshAll(entities);
335: return null;
336: }
337: }, true);
338: }
339:
340: public void refreshAll() throws DataAccessException {
341: execute(new JdoCallback() {
342: public Object doInJdo(PersistenceManager pm)
343: throws JDOException {
344: pm.refreshAll();
345: return null;
346: }
347: }, true);
348: }
349:
350: public Object makePersistent(final Object entity)
351: throws DataAccessException {
352: return execute(new JdoCallback() {
353: public Object doInJdo(PersistenceManager pm)
354: throws JDOException {
355: return pm.makePersistent(entity);
356: }
357: }, true);
358: }
359:
360: public Collection makePersistentAll(final Collection entities)
361: throws DataAccessException {
362: return (Collection) execute(new JdoCallback() {
363: public Object doInJdo(PersistenceManager pm)
364: throws JDOException {
365: return pm.makePersistentAll(entities);
366: }
367: }, true);
368: }
369:
370: public void deletePersistent(final Object entity)
371: throws DataAccessException {
372: execute(new JdoCallback() {
373: public Object doInJdo(PersistenceManager pm)
374: throws JDOException {
375: pm.deletePersistent(entity);
376: return null;
377: }
378: }, true);
379: }
380:
381: public void deletePersistentAll(final Collection entities)
382: throws DataAccessException {
383: execute(new JdoCallback() {
384: public Object doInJdo(PersistenceManager pm)
385: throws JDOException {
386: pm.deletePersistentAll(entities);
387: return null;
388: }
389: }, true);
390: }
391:
392: public Object detachCopy(final Object entity) {
393: return execute(new JdoCallback() {
394: public Object doInJdo(PersistenceManager pm)
395: throws JDOException {
396: return pm.detachCopy(entity);
397: }
398: }, true);
399: }
400:
401: public Collection detachCopyAll(final Collection entities) {
402: return (Collection) execute(new JdoCallback() {
403: public Object doInJdo(PersistenceManager pm)
404: throws JDOException {
405: return pm.detachCopyAll(entities);
406: }
407: }, true);
408: }
409:
410: /**
411: * @deprecated in favor of {@link #makePersistent(Object)}.
412: * To be removed in Spring 3.0.
413: */
414: public Object attachCopy(Object detachedEntity) {
415: return makePersistent(detachedEntity);
416: }
417:
418: /**
419: * @deprecated in favor of {@link #makePersistentAll(java.util.Collection)}.
420: * To be removed in Spring 3.0.
421: */
422: public Collection attachCopyAll(Collection detachedEntities) {
423: return makePersistentAll(detachedEntities);
424: }
425:
426: public void flush() throws DataAccessException {
427: execute(new JdoCallback() {
428: public Object doInJdo(PersistenceManager pm)
429: throws JDOException {
430: getJdoDialect().flush(pm);
431: return null;
432: }
433: }, true);
434: }
435:
436: //-------------------------------------------------------------------------
437: // Convenience finder methods
438: //-------------------------------------------------------------------------
439:
440: public Collection find(Class entityClass)
441: throws DataAccessException {
442: return find(entityClass, null, null);
443: }
444:
445: public Collection find(Class entityClass, String filter)
446: throws DataAccessException {
447: return find(entityClass, filter, null);
448: }
449:
450: public Collection find(final Class entityClass,
451: final String filter, final String ordering)
452: throws DataAccessException {
453:
454: return (Collection) execute(new JdoCallback() {
455: public Object doInJdo(PersistenceManager pm)
456: throws JDOException {
457: Query query = (filter != null ? pm.newQuery(
458: entityClass, filter) : pm.newQuery(entityClass));
459: prepareQuery(query);
460: if (ordering != null) {
461: query.setOrdering(ordering);
462: }
463: return query.execute();
464: }
465: }, true);
466: }
467:
468: public Collection find(Class entityClass, String filter,
469: String parameters, Object[] values)
470: throws DataAccessException {
471:
472: return find(entityClass, filter, parameters, values, null);
473: }
474:
475: public Collection find(final Class entityClass,
476: final String filter, final String parameters,
477: final Object[] values, final String ordering)
478: throws DataAccessException {
479:
480: return (Collection) execute(new JdoCallback() {
481: public Object doInJdo(PersistenceManager pm)
482: throws JDOException {
483: Query query = pm.newQuery(entityClass, filter);
484: prepareQuery(query);
485: query.declareParameters(parameters);
486: if (ordering != null) {
487: query.setOrdering(ordering);
488: }
489: return query.executeWithArray(values);
490: }
491: }, true);
492: }
493:
494: public Collection find(Class entityClass, String filter,
495: String parameters, Map values) throws DataAccessException {
496:
497: return find(entityClass, filter, parameters, values, null);
498: }
499:
500: public Collection find(final Class entityClass,
501: final String filter, final String parameters,
502: final Map values, final String ordering)
503: throws DataAccessException {
504:
505: return (Collection) execute(new JdoCallback() {
506: public Object doInJdo(PersistenceManager pm)
507: throws JDOException {
508: Query query = pm.newQuery(entityClass, filter);
509: prepareQuery(query);
510: query.declareParameters(parameters);
511: if (ordering != null) {
512: query.setOrdering(ordering);
513: }
514: return query.executeWithMap(values);
515: }
516: }, true);
517: }
518:
519: public Collection find(final String language,
520: final Object queryObject) throws DataAccessException {
521: return (Collection) execute(new JdoCallback() {
522: public Object doInJdo(PersistenceManager pm)
523: throws JDOException {
524: Query query = pm.newQuery(language, queryObject);
525: prepareQuery(query);
526: return query.execute();
527: }
528: }, true);
529: }
530:
531: public Collection find(final String queryString)
532: throws DataAccessException {
533: return (Collection) execute(new JdoCallback() {
534: public Object doInJdo(PersistenceManager pm)
535: throws JDOException {
536: Query query = pm.newQuery(queryString);
537: prepareQuery(query);
538: return query.execute();
539: }
540: }, true);
541: }
542:
543: public Collection find(final String queryString,
544: final Object[] values) throws DataAccessException {
545: return (Collection) execute(new JdoCallback() {
546: public Object doInJdo(PersistenceManager pm)
547: throws JDOException {
548: Query query = pm.newQuery(queryString);
549: prepareQuery(query);
550: return query.executeWithArray(values);
551: }
552: }, true);
553: }
554:
555: public Collection find(final String queryString, final Map values)
556: throws DataAccessException {
557: return (Collection) execute(new JdoCallback() {
558: public Object doInJdo(PersistenceManager pm)
559: throws JDOException {
560: Query query = pm.newQuery(queryString);
561: prepareQuery(query);
562: return query.executeWithMap(values);
563: }
564: }, true);
565: }
566:
567: public Collection findByNamedQuery(final Class entityClass,
568: final String queryName) throws DataAccessException {
569: return (Collection) execute(new JdoCallback() {
570: public Object doInJdo(PersistenceManager pm)
571: throws JDOException {
572: Query query = pm.newNamedQuery(entityClass, queryName);
573: prepareQuery(query);
574: return query.execute();
575: }
576: }, true);
577: }
578:
579: public Collection findByNamedQuery(final Class entityClass,
580: final String queryName, final Object[] values)
581: throws DataAccessException {
582:
583: return (Collection) execute(new JdoCallback() {
584: public Object doInJdo(PersistenceManager pm)
585: throws JDOException {
586: Query query = pm.newNamedQuery(entityClass, queryName);
587: prepareQuery(query);
588: return query.executeWithArray(values);
589: }
590: }, true);
591: }
592:
593: public Collection findByNamedQuery(final Class entityClass,
594: final String queryName, final Map values)
595: throws DataAccessException {
596:
597: return (Collection) execute(new JdoCallback() {
598: public Object doInJdo(PersistenceManager pm)
599: throws JDOException {
600: Query query = pm.newNamedQuery(entityClass, queryName);
601: prepareQuery(query);
602: return query.executeWithMap(values);
603: }
604: }, true);
605: }
606:
607: /**
608: * Prepare the given JDO query object. To be used within a JdoCallback.
609: * Applies a transaction timeout, if any. If you don't use such timeouts,
610: * the call is a no-op.
611: * <p>In general, prefer a proxied PersistenceManager instead, which will
612: * automatically apply the transaction timeout (through the use of a special
613: * PersistenceManager proxy). You need to set the "exposeNativePersistenceManager"
614: * property to "false" to activate this. Note that you won't be able to cast
615: * to a provider-specific JDO PersistenceManager class anymore then.
616: * @param query the JDO query object
617: * @throws JDOException if the query could not be properly prepared
618: * @see JdoCallback#doInJdo
619: * @see PersistenceManagerFactoryUtils#applyTransactionTimeout
620: * @see #setExposeNativePersistenceManager
621: */
622: public void prepareQuery(Query query) throws JDOException {
623: PersistenceManagerFactoryUtils.applyTransactionTimeout(query,
624: getPersistenceManagerFactory(), getJdoDialect());
625: }
626:
627: /**
628: * Invocation handler that suppresses close calls on JDO PersistenceManagers.
629: * Also prepares returned Query objects.
630: * @see javax.jdo.PersistenceManager#close()
631: */
632: private class CloseSuppressingInvocationHandler implements
633: InvocationHandler {
634:
635: private final PersistenceManager target;
636:
637: public CloseSuppressingInvocationHandler(
638: PersistenceManager target) {
639: this .target = target;
640: }
641:
642: public Object invoke(Object proxy, Method method, Object[] args)
643: throws Throwable {
644: // Invocation on PersistenceManager interface (or provider-specific extension) coming in...
645:
646: if (method.getName().equals("equals")) {
647: // Only consider equal when proxies are identical.
648: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
649: } else if (method.getName().equals("hashCode")) {
650: // Use hashCode of PersistenceManager proxy.
651: return new Integer(hashCode());
652: } else if (method.getName().equals("close")) {
653: // Handle close method: suppress, not valid.
654: return null;
655: }
656:
657: // Invoke method on target PersistenceManager.
658: try {
659: Object retVal = method.invoke(this .target, args);
660:
661: // If return value is a JDO Query object, apply transaction timeout.
662: if (retVal instanceof Query) {
663: prepareQuery(((Query) retVal));
664: }
665:
666: return retVal;
667: } catch (InvocationTargetException ex) {
668: throw ex.getTargetException();
669: }
670: }
671: }
672:
673: }
|