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