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.jpa;
018:
019: import java.io.Serializable;
020: import java.lang.reflect.InvocationHandler;
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Proxy;
024: import java.util.Map;
025: import java.util.Set;
026:
027: import javax.persistence.EntityManager;
028: import javax.persistence.EntityManagerFactory;
029: import javax.persistence.EntityTransaction;
030: import javax.persistence.TransactionRequiredException;
031: import javax.persistence.spi.PersistenceUnitInfo;
032: import javax.persistence.spi.PersistenceUnitTransactionType;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036:
037: import org.springframework.dao.DataAccessException;
038: import org.springframework.dao.support.PersistenceExceptionTranslator;
039: import org.springframework.transaction.support.TransactionSynchronizationAdapter;
040: import org.springframework.transaction.support.TransactionSynchronizationManager;
041: import org.springframework.util.Assert;
042: import org.springframework.util.ClassUtils;
043: import org.springframework.util.CollectionUtils;
044:
045: /**
046: * Factory for dynamic EntityManager proxies that follow the JPA spec's
047: * semantics for "extended" EntityManagers.
048: *
049: * <p>Supports explicit joining of a transaction through the
050: * <code>joinTransaction()</code> method ("application-managed extended
051: * EntityManager") as well as automatic joining on each operation
052: * ("container-managed extended EntityManager").
053: *
054: * @author Rod Johnson
055: * @author Juergen Hoeller
056: * @since 2.0
057: */
058: public abstract class ExtendedEntityManagerCreator {
059:
060: /**
061: * Create an EntityManager that can join transactions with the
062: * <code>joinTransaction()</code> method, but is not automatically
063: * managed by the container.
064: * @param rawEntityManager raw EntityManager
065: * @param plusOperations an implementation of the EntityManagerPlusOperations
066: * interface, if those operations should be exposed (may be <code>null</code>)
067: * @return an application-managed EntityManager that can join transactions
068: * but does not participate in them automatically
069: */
070: public static EntityManager createApplicationManagedEntityManager(
071: EntityManager rawEntityManager,
072: EntityManagerPlusOperations plusOperations) {
073:
074: return createProxy(rawEntityManager, plusOperations, null,
075: null, false);
076: }
077:
078: /**
079: * Create an EntityManager that can join transactions with the
080: * <code>joinTransaction()</code> method, but is not automatically
081: * managed by the container.
082: * @param rawEntityManager raw EntityManager
083: * @param plusOperations an implementation of the EntityManagerPlusOperations
084: * interface, if those operations should be exposed (may be <code>null</code>)
085: * @param exceptionTranslator the exception translator to use for translating
086: * JPA commit/rollback exceptions during transaction synchronization
087: * (may be <code>null</code>)
088: * @return an application-managed EntityManager that can join transactions
089: * but does not participate in them automatically
090: */
091: public static EntityManager createApplicationManagedEntityManager(
092: EntityManager rawEntityManager,
093: EntityManagerPlusOperations plusOperations,
094: PersistenceExceptionTranslator exceptionTranslator) {
095:
096: return createProxy(rawEntityManager, plusOperations,
097: exceptionTranslator, null, false);
098: }
099:
100: /**
101: * Create an EntityManager that can join transactions with the
102: * <code>joinTransaction()</code> method, but is not automatically
103: * managed by the container.
104: * @param rawEntityManager raw EntityManager
105: * @param emfInfo the EntityManagerFactoryInfo to obtain the
106: * EntityManagerPlusOperations and PersistenceUnitInfo from
107: * @return an application-managed EntityManager that can join transactions
108: * but does not participate in them automatically
109: */
110: public static EntityManager createApplicationManagedEntityManager(
111: EntityManager rawEntityManager,
112: EntityManagerFactoryInfo emfInfo) {
113:
114: return createProxy(rawEntityManager, emfInfo, false);
115: }
116:
117: /**
118: * Create an EntityManager that automatically joins transactions on each
119: * operation in a transaction.
120: * @param rawEntityManager raw EntityManager
121: * @param plusOperations an implementation of the EntityManagerPlusOperations
122: * interface, if those operations should be exposed (may be <code>null</code>)
123: * @return a container-managed EntityManager that will automatically participate
124: * in any managed transaction
125: */
126: public static EntityManager createContainerManagedEntityManager(
127: EntityManager rawEntityManager,
128: EntityManagerPlusOperations plusOperations) {
129:
130: return createProxy(rawEntityManager, plusOperations, null,
131: null, true);
132: }
133:
134: /**
135: * Create an EntityManager that automatically joins transactions on each
136: * operation in a transaction.
137: * @param rawEntityManager raw EntityManager
138: * @param plusOperations an implementation of the EntityManagerPlusOperations
139: * interface, if those operations should be exposed (may be <code>null</code>)
140: * @param exceptionTranslator the exception translator to use for translating
141: * JPA commit/rollback exceptions during transaction synchronization
142: * (may be <code>null</code>)
143: * @return a container-managed EntityManager that will automatically participate
144: * in any managed transaction
145: */
146: public static EntityManager createContainerManagedEntityManager(
147: EntityManager rawEntityManager,
148: EntityManagerPlusOperations plusOperations,
149: PersistenceExceptionTranslator exceptionTranslator) {
150:
151: return createProxy(rawEntityManager, plusOperations,
152: exceptionTranslator, null, true);
153: }
154:
155: /**
156: * Create an EntityManager that automatically joins transactions on each
157: * operation in a transaction.
158: * @param rawEntityManager raw EntityManager
159: * @param emfInfo the EntityManagerFactoryInfo to obtain the
160: * EntityManagerPlusOperations and PersistenceUnitInfo from
161: * @return a container-managed EntityManager that will automatically participate
162: * in any managed transaction
163: */
164: public static EntityManager createContainerManagedEntityManager(
165: EntityManager rawEntityManager,
166: EntityManagerFactoryInfo emfInfo) {
167:
168: return createProxy(rawEntityManager, emfInfo, true);
169: }
170:
171: /**
172: * Create an EntityManager that automatically joins transactions on each
173: * operation in a transaction.
174: * @param emf the EntityManagerFactory to create the EntityManager with.
175: * If this implements the EntityManagerFactoryInfo interface, appropriate handling
176: * of the native EntityManagerFactory and available EntityManagerPlusOperations
177: * will automatically apply.
178: * @return a container-managed EntityManager that will automatically participate
179: * in any managed transaction
180: * @see javax.persistence.EntityManagerFactory#createEntityManager()
181: */
182: public static EntityManager createContainerManagedEntityManager(
183: EntityManagerFactory emf) {
184: return createContainerManagedEntityManager(emf, null);
185: }
186:
187: /**
188: * Create an EntityManager that automatically joins transactions on each
189: * operation in a transaction.
190: * @param emf the EntityManagerFactory to create the EntityManager with.
191: * If this implements the EntityManagerFactoryInfo interface, appropriate handling
192: * of the native EntityManagerFactory and available EntityManagerPlusOperations
193: * will automatically apply.
194: * @param properties the properties to be passed into the <code>createEntityManager</code>
195: * call (may be <code>null</code>)
196: * @return a container-managed EntityManager that will automatically participate
197: * in any managed transaction
198: * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
199: */
200: public static EntityManager createContainerManagedEntityManager(
201: EntityManagerFactory emf, Map properties) {
202: Assert.notNull(emf, "EntityManagerFactory must not be null");
203: if (emf instanceof EntityManagerFactoryInfo) {
204: EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
205: EntityManagerFactory nativeEmf = emfInfo
206: .getNativeEntityManagerFactory();
207: EntityManager rawEntityManager = (!CollectionUtils
208: .isEmpty(properties) ? nativeEmf
209: .createEntityManager(properties) : nativeEmf
210: .createEntityManager());
211: return createProxy(rawEntityManager, emfInfo, true);
212: } else {
213: EntityManager rawEntityManager = (!CollectionUtils
214: .isEmpty(properties) ? emf
215: .createEntityManager(properties) : emf
216: .createEntityManager());
217: return createProxy(rawEntityManager, null, null, null, true);
218: }
219: }
220:
221: /**
222: * Actually create the EntityManager proxy.
223: * @param rawEntityManager raw EntityManager
224: * @param emfInfo the EntityManagerFactoryInfo to obtain the
225: * EntityManagerPlusOperations and PersistenceUnitInfo from
226: * @param containerManaged whether to follow container-managed EntityManager
227: * or application-managed EntityManager semantics
228: * @return the EntityManager proxy
229: */
230: private static EntityManager createProxy(
231: EntityManager rawEntityManager,
232: EntityManagerFactoryInfo emfInfo, boolean containerManaged) {
233:
234: Assert.notNull(emfInfo,
235: "EntityManagerFactoryInfo must not be null");
236: JpaDialect jpaDialect = emfInfo.getJpaDialect();
237: EntityManagerPlusOperations plusOperations = null;
238: if (jpaDialect != null
239: && jpaDialect.supportsEntityManagerPlusOperations()) {
240: plusOperations = jpaDialect
241: .getEntityManagerPlusOperations(rawEntityManager);
242: }
243: PersistenceUnitInfo pui = emfInfo.getPersistenceUnitInfo();
244: Boolean jta = (pui != null ? pui.getTransactionType() == PersistenceUnitTransactionType.JTA
245: : null);
246: return createProxy(rawEntityManager, plusOperations,
247: jpaDialect, jta, containerManaged);
248: }
249:
250: /**
251: * Actually create the EntityManager proxy.
252: * @param rawEntityManager raw EntityManager
253: * @param plusOperations an implementation of the EntityManagerPlusOperations
254: * interface, if those operations should be exposed (may be <code>null</code>)
255: * @param exceptionTranslator the PersistenceException translator to use
256: * @param jta whether to create a JTA-aware EntityManager
257: * (or <code>null</code> if not known in advance)
258: * @param containerManaged whether to follow container-managed EntityManager
259: * or application-managed EntityManager semantics
260: * @return the EntityManager proxy
261: */
262: private static EntityManager createProxy(
263: EntityManager rawEntityManager,
264: EntityManagerPlusOperations plusOperations,
265: PersistenceExceptionTranslator exceptionTranslator,
266: Boolean jta, boolean containerManaged) {
267:
268: Assert.notNull(rawEntityManager,
269: "EntityManager must not be null");
270: Set ifcs = ClassUtils.getAllInterfacesAsSet(rawEntityManager);
271: ifcs.add(EntityManagerProxy.class);
272: if (plusOperations != null) {
273: ifcs.add(EntityManagerPlusOperations.class);
274: }
275: return (EntityManager) Proxy.newProxyInstance(
276: ExtendedEntityManagerCreator.class.getClassLoader(),
277: (Class[]) ifcs.toArray(new Class[ifcs.size()]),
278: new ExtendedEntityManagerInvocationHandler(
279: rawEntityManager, plusOperations,
280: exceptionTranslator, jta, containerManaged));
281: }
282:
283: /**
284: * InvocationHandler for extended EntityManagers as defined in the JPA spec.
285: */
286: private static class ExtendedEntityManagerInvocationHandler
287: implements InvocationHandler, Serializable {
288:
289: private static final Log logger = LogFactory
290: .getLog(ExtendedEntityManagerInvocationHandler.class);
291:
292: private final EntityManager target;
293:
294: private final EntityManagerPlusOperations plusOperations;
295:
296: private final PersistenceExceptionTranslator exceptionTranslator;
297:
298: private final boolean containerManaged;
299:
300: private boolean jta;
301:
302: private ExtendedEntityManagerInvocationHandler(
303: EntityManager target,
304: EntityManagerPlusOperations plusOperations,
305: PersistenceExceptionTranslator exceptionTranslator,
306: Boolean jta, boolean containerManaged) {
307:
308: this .target = target;
309: this .plusOperations = plusOperations;
310: this .exceptionTranslator = exceptionTranslator;
311: this .jta = (jta != null ? jta.booleanValue()
312: : isJtaEntityManager());
313: this .containerManaged = containerManaged;
314: }
315:
316: private boolean isJtaEntityManager() {
317: try {
318: this .target.getTransaction();
319: return false;
320: } catch (IllegalStateException ex) {
321: logger
322: .debug("Cannot access EntityTransaction handle - assuming we're in a JTA environment");
323: return true;
324: }
325: }
326:
327: public Object invoke(Object proxy, Method method, Object[] args)
328: throws Throwable {
329: // Invocation on EntityManager interface coming in...
330:
331: if (method.getName().equals("equals")) {
332: // Only consider equal when proxies are identical.
333: return (proxy == args[0]);
334: } else if (method.getName().equals("hashCode")) {
335: // Use hashCode of EntityManager proxy.
336: return hashCode();
337: } else if (method.getName()
338: .equals("getTargetEntityManager")) {
339: return this .target;
340: } else if (method.getName().equals("isOpen")) {
341: if (this .containerManaged) {
342: return true;
343: }
344: } else if (method.getName().equals("close")) {
345: if (this .containerManaged) {
346: throw new IllegalStateException(
347: "Invalid usage: Cannot close a container-managed EntityManager");
348: }
349: } else if (method.getName().equals("getTransaction")) {
350: if (this .containerManaged) {
351: throw new IllegalStateException(
352: "Cannot execute getTransaction() on a container-managed EntityManager");
353: }
354: } else if (method.getName().equals("joinTransaction")) {
355: doJoinTransaction(true);
356: return null;
357: }
358:
359: // Do automatic joining if required.
360: if (this .containerManaged
361: && method.getDeclaringClass().isInterface()) {
362: doJoinTransaction(false);
363: }
364:
365: // Invoke method on current EntityManager.
366: try {
367: if (method.getDeclaringClass().equals(
368: EntityManagerPlusOperations.class)) {
369: return method.invoke(this .plusOperations, args);
370: } else {
371: return method.invoke(this .target, args);
372: }
373: } catch (InvocationTargetException ex) {
374: throw ex.getTargetException();
375: }
376: }
377:
378: /**
379: * Join an existing transaction, if not already joined.
380: * @param enforce whether to enforce the transaction
381: * (i.e. whether failure to join is considered fatal)
382: */
383: private void doJoinTransaction(boolean enforce) {
384: if (this .jta) {
385: // Let's try whether we're in a JTA transaction.
386: try {
387: this .target.joinTransaction();
388: logger.debug("Joined JTA transaction");
389: } catch (TransactionRequiredException ex) {
390: if (!enforce) {
391: logger.debug("No JTA transaction to join: "
392: + ex);
393: } else {
394: throw ex;
395: }
396: }
397: } else {
398: if (TransactionSynchronizationManager
399: .isSynchronizationActive()) {
400: if (!TransactionSynchronizationManager
401: .hasResource(this .target)
402: && !this .target.getTransaction().isActive()) {
403: enlistInCurrentTransaction();
404: }
405: logger.debug("Joined local transaction");
406: } else {
407: if (!enforce) {
408: logger.debug("No local transaction to join");
409: } else {
410: throw new TransactionRequiredException(
411: "No local transaction to join");
412: }
413: }
414: }
415: }
416:
417: /**
418: * Enlist this application-managed EntityManager in the current transaction.
419: */
420: private void enlistInCurrentTransaction() {
421: // Resource local transaction, need to acquire the EntityTransaction,
422: // start a transaction now and enlist a synchronization for
423: // commit or rollback later.
424: EntityTransaction et = this .target.getTransaction();
425: et.begin();
426: if (logger.isDebugEnabled()) {
427: logger
428: .debug("Starting resource local transaction on application-managed "
429: + "EntityManager [" + this .target + "]");
430: }
431: EntityManagerHolder emh = new EntityManagerHolder(
432: this .target);
433: ExtendedEntityManagerSynchronization extendedEntityManagerSynchronization = new ExtendedEntityManagerSynchronization(
434: emh, this .exceptionTranslator);
435: TransactionSynchronizationManager.bindResource(this .target,
436: extendedEntityManagerSynchronization);
437: TransactionSynchronizationManager
438: .registerSynchronization(extendedEntityManagerSynchronization);
439: }
440: }
441:
442: /**
443: * TransactionSynchronization enlisting an extended EntityManager
444: * with a current Spring transaction.
445: */
446: private static class ExtendedEntityManagerSynchronization extends
447: TransactionSynchronizationAdapter {
448:
449: private final EntityManagerHolder entityManagerHolder;
450:
451: private final PersistenceExceptionTranslator exceptionTranslator;
452:
453: private boolean holderActive = true;
454:
455: public ExtendedEntityManagerSynchronization(
456: EntityManagerHolder emHolder,
457: PersistenceExceptionTranslator exceptionTranslator) {
458:
459: this .entityManagerHolder = emHolder;
460: this .exceptionTranslator = exceptionTranslator;
461: }
462:
463: public int getOrder() {
464: return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1;
465: }
466:
467: public void suspend() {
468: if (this .holderActive) {
469: TransactionSynchronizationManager
470: .unbindResource(this .entityManagerHolder
471: .getEntityManager());
472: }
473: }
474:
475: public void resume() {
476: if (this .holderActive) {
477: TransactionSynchronizationManager.bindResource(
478: this .entityManagerHolder.getEntityManager(),
479: this .entityManagerHolder);
480: }
481: }
482:
483: public void beforeCompletion() {
484: TransactionSynchronizationManager
485: .unbindResource(this .entityManagerHolder
486: .getEntityManager());
487: this .holderActive = false;
488: }
489:
490: public void afterCommit() {
491: // Trigger commit here to let exceptions propagate to the caller.
492: try {
493: this .entityManagerHolder.getEntityManager()
494: .getTransaction().commit();
495: } catch (RuntimeException ex) {
496: throw convertCompletionException(ex);
497: }
498: }
499:
500: public void afterCompletion(int status) {
501: this .entityManagerHolder
502: .setSynchronizedWithTransaction(false);
503: if (status != STATUS_COMMITTED) {
504: // Haven't had an afterCommit call: trigger a rollback.
505: try {
506: this .entityManagerHolder.getEntityManager()
507: .getTransaction().rollback();
508: } catch (RuntimeException ex) {
509: throw convertCompletionException(ex);
510: }
511: }
512: // Don't close the EntityManager... That's up to the user.
513: }
514:
515: private RuntimeException convertCompletionException(
516: RuntimeException ex) {
517: DataAccessException daex = (this.exceptionTranslator != null) ? this.exceptionTranslator
518: .translateExceptionIfPossible(ex)
519: : EntityManagerFactoryUtils
520: .convertJpaAccessExceptionIfPossible(ex);
521: return (daex != null ? daex : ex);
522: }
523: }
524:
525: }
|