KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > orm > jpa > ExtendedEntityManagerCreator


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.orm.jpa;
18
19 import java.lang.reflect.InvocationHandler JavaDoc;
20 import java.lang.reflect.InvocationTargetException JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.lang.reflect.Proxy JavaDoc;
23 import java.util.Map JavaDoc;
24
25 import javax.persistence.EntityManager;
26 import javax.persistence.EntityManagerFactory;
27 import javax.persistence.EntityTransaction;
28 import javax.persistence.TransactionRequiredException;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32
33 import org.springframework.dao.DataAccessException;
34 import org.springframework.dao.support.PersistenceExceptionTranslator;
35 import org.springframework.transaction.support.TransactionSynchronizationAdapter;
36 import org.springframework.transaction.support.TransactionSynchronizationManager;
37 import org.springframework.util.Assert;
38 import org.springframework.util.ClassUtils;
39 import org.springframework.util.CollectionUtils;
40 import org.springframework.util.ObjectUtils;
41
42 /**
43  * Factory for dynamic EntityManager proxies that follow the JPA spec's
44  * semantics for "extended" EntityManagers.
45  *
46  * <p>Supports explicit joining of a transaction through the
47  * <code>joinTransaction()</code> method ("application-managed extended
48  * EntityManager") as well as automatic joining on each operation
49  * ("container-managed extended EntityManager").
50  *
51  * @author Rod Johnson
52  * @author Juergen Hoeller
53  * @since 2.0
54  */

55 public abstract class ExtendedEntityManagerCreator {
56
57     /**
58      * Create an EntityManager that can join transactions with the
59      * <code>joinTransaction()</code> method, but is not automatically
60      * managed by the container.
61      * @param rawEntityManager raw EntityManager
62      * @param plusOperations an implementation of the EntityManagerPlusOperations
63      * interface, if those operations should be exposed (may be <code>null</code>)
64      * @return an application-managed EntityManager that can join transactions
65      * but does not participate in them automatically
66      */

67     public static EntityManager createApplicationManagedEntityManager(
68             EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations) {
69
70         return createProxy(rawEntityManager, plusOperations, null, false);
71     }
72
73     /**
74      * Create an EntityManager that can join transactions with the
75      * <code>joinTransaction()</code> method, but is not automatically
76      * managed by the container.
77      * @param rawEntityManager raw EntityManager
78      * @param plusOperations an implementation of the EntityManagerPlusOperations
79      * interface, if those operations should be exposed (may be <code>null</code>)
80      * @param exceptionTranslator the exception translator to use for translating
81      * JPA commit/rollback exceptions during transaction synchronization
82      * (may be <code>null</code>)
83      * @return an application-managed EntityManager that can join transactions
84      * but does not participate in them automatically
85      */

86     public static EntityManager createApplicationManagedEntityManager(
87             EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations,
88             PersistenceExceptionTranslator exceptionTranslator) {
89
90         return createProxy(rawEntityManager, plusOperations, exceptionTranslator, false);
91     }
92
93     /**
94      * Create an EntityManager that automatically joins transactions on each
95      * operation in a transaction.
96      * @param rawEntityManager raw EntityManager
97      * @param plusOperations an implementation of the EntityManagerPlusOperations
98      * interface, if those operations should be exposed (may be <code>null</code>)
99      * @return a container-managed EntityManager that will automatically participate
100      * in any managed transaction
101      */

102     public static EntityManager createContainerManagedEntityManager(
103             EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations) {
104
105         return createProxy(rawEntityManager, plusOperations, null, true);
106     }
107
108     /**
109      * Create an EntityManager that automatically joins transactions on each
110      * operation in a transaction.
111      * @param rawEntityManager raw EntityManager
112      * @param plusOperations an implementation of the EntityManagerPlusOperations
113      * interface, if those operations should be exposed (may be <code>null</code>)
114      * @param exceptionTranslator the exception translator to use for translating
115      * JPA commit/rollback exceptions during transaction synchronization
116      * (may be <code>null</code>)
117      * @return a container-managed EntityManager that will automatically participate
118      * in any managed transaction
119      */

120     public static EntityManager createContainerManagedEntityManager(
121             EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations,
122             PersistenceExceptionTranslator exceptionTranslator) {
123
124         return createProxy(rawEntityManager, plusOperations, exceptionTranslator, true);
125     }
126
127     /**
128      * Create an EntityManager that automatically joins transactions on each
129      * operation in a transaction.
130      * @param emf the EntityManagerFactory to create the EntityManager with.
131      * If this implements the EntityManagerFactoryInfo interface, appropriate handling
132      * of the native EntityManagerFactory and available EntityManagerPlusOperations
133      * will automatically apply.
134      * @return a container-managed EntityManager that will automatically participate
135      * in any managed transaction
136      * @see javax.persistence.EntityManagerFactory#createEntityManager()
137      */

138     public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf) {
139         return createContainerManagedEntityManager(emf, null);
140     }
141
142     /**
143      * Create an EntityManager that automatically joins transactions on each
144      * operation in a transaction.
145      * @param emf the EntityManagerFactory to create the EntityManager with.
146      * If this implements the EntityManagerFactoryInfo interface, appropriate handling
147      * of the native EntityManagerFactory and available EntityManagerPlusOperations
148      * will automatically apply.
149      * @param properties the properties to be passed into the <code>createEntityManager</code>
150      * call (may be <code>null</code>)
151      * @return a container-managed EntityManager that will automatically participate
152      * in any managed transaction
153      * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
154      */

155     public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf, Map JavaDoc properties) {
156         Assert.notNull(emf, "EntityManagerFactory must not be null");
157         if (emf instanceof EntityManagerFactoryInfo) {
158             EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
159             EntityManagerFactory nativeEmf = emfInfo.getNativeEntityManagerFactory();
160             EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
161                     nativeEmf.createEntityManager(properties) : nativeEmf.createEntityManager());
162             JpaDialect jpaDialect = emfInfo.getJpaDialect();
163             EntityManagerPlusOperations plusOperations = null;
164             if (jpaDialect != null && jpaDialect.supportsEntityManagerPlusOperations()) {
165                 plusOperations = jpaDialect.getEntityManagerPlusOperations(rawEntityManager);
166             }
167             return createProxy(rawEntityManager, plusOperations, emfInfo.getJpaDialect(), true);
168         }
169         else {
170             EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ?
171                     emf.createEntityManager(properties) : emf.createEntityManager());
172             return createProxy(rawEntityManager, null, null, true);
173         }
174     }
175
176     /**
177      * Actually create the EntityManager proxy.
178      * @param rawEntityManager raw EntityManager
179      * @param plusOperations an implementation of the EntityManagerPlusOperations
180      * interface, if those operations should be exposed (may be <code>null</code>)
181      * @param containerManaged whether to follow container-managed EntityManager
182      * or application-managed EntityManager semantics
183      * @return the EntityManager proxy
184      */

185     private static EntityManager createProxy(
186             EntityManager rawEntityManager, EntityManagerPlusOperations plusOperations,
187             PersistenceExceptionTranslator exceptionTranslator, boolean containerManaged) {
188
189         Assert.notNull(rawEntityManager, "EntityManager must not be null");
190         Class JavaDoc[] ifcs = ClassUtils.getAllInterfaces(rawEntityManager);
191         if (plusOperations != null) {
192             ifcs = (Class JavaDoc[]) ObjectUtils.addObjectToArray(ifcs, EntityManagerPlusOperations.class);
193         }
194         return (EntityManager) Proxy.newProxyInstance(
195                 ExtendedEntityManagerCreator.class.getClassLoader(), ifcs,
196                 new ExtendedEntityManagerInvocationHandler(
197                         rawEntityManager, plusOperations, exceptionTranslator, containerManaged));
198     }
199
200
201     /**
202      * InvocationHandler for extended EntityManagers as defined in the JPA spec.
203      */

204     private static class ExtendedEntityManagerInvocationHandler implements InvocationHandler JavaDoc {
205         
206         private static final Log logger = LogFactory.getLog(ExtendedEntityManagerInvocationHandler.class);
207
208         private final EntityManager target;
209
210         private final EntityManagerPlusOperations plusOperations;
211
212         private final PersistenceExceptionTranslator exceptionTranslator;
213
214         private final boolean containerManaged;
215
216         private boolean jta;
217
218         private ExtendedEntityManagerInvocationHandler(
219                 EntityManager target, EntityManagerPlusOperations plusOperations,
220                 PersistenceExceptionTranslator exceptionTranslator, boolean containerManaged) {
221
222             this.target = target;
223             this.plusOperations = plusOperations;
224             this.exceptionTranslator = exceptionTranslator;
225             this.containerManaged = containerManaged;
226             this.jta = isJtaEntityManager();
227         }
228
229         private boolean isJtaEntityManager() {
230             try {
231                 this.target.getTransaction();
232                 return false;
233             }
234             catch (IllegalStateException JavaDoc ex) {
235                 logger.debug("Cannot access EntityTransaction handle - assuming we're in a JTA environment");
236                 return true;
237             }
238         }
239
240         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
241             // Invocation on EntityManager interface coming in...
242

243             if (method.getDeclaringClass().equals(EntityManagerPlusOperations.class)) {
244                 return method.invoke(this.plusOperations, args);
245             }
246
247             if (method.getName().equals("equals")) {
248                 // Only consider equal when proxies are identical.
249
return (proxy == args[0]);
250             }
251             else if (method.getName().equals("hashCode")) {
252                 // Use hashCode of EntityManager proxy.
253
return hashCode();
254             }
255             else if (method.getName().equals("joinTransaction")) {
256                 doJoinTransaction(true);
257                 return null;
258             }
259             else if (method.getName().equals("getTransaction")) {
260                 if (this.containerManaged) {
261                     throw new IllegalStateException JavaDoc("Cannot execute getTransaction() on " +
262                         "a container-managed EntityManager");
263                 }
264             }
265             else if (method.getName().equals("close")) {
266                 if (this.containerManaged) {
267                     throw new IllegalStateException JavaDoc("Invalid usage: Cannot close a container-managed EntityManager");
268                 }
269             }
270             else if (method.getName().equals("isOpen")) {
271                 if (this.containerManaged) {
272                     return true;
273                 }
274             }
275
276             // Do automatic joining if required.
277
if (this.containerManaged) {
278                 doJoinTransaction(false);
279             }
280
281             // Invoke method on current EntityManager.
282
try {
283                 return method.invoke(this.target, args);
284             }
285             catch (InvocationTargetException JavaDoc ex) {
286                 throw ex.getTargetException();
287             }
288         }
289
290         /**
291          * Join an existing transaction, if not already joined.
292          */

293         private void doJoinTransaction(boolean enforce) {
294             if (this.jta) {
295                 // Let's try whether we're in a JTA transaction.
296
try {
297                     this.target.joinTransaction();
298                     logger.debug("Joined JTA transaction");
299                 }
300                 catch (TransactionRequiredException ex) {
301                     if (!enforce) {
302                         logger.debug("No JTA transaction to join: " + ex);
303                     }
304                     else {
305                         throw ex;
306                     }
307                 }
308             }
309             else {
310                 if (TransactionSynchronizationManager.isSynchronizationActive()) {
311                     if (!TransactionSynchronizationManager.hasResource(this.target)) {
312                         enlistInCurrentTransaction();
313                     }
314                     logger.debug("Joined local transaction");
315                 }
316                 else {
317                     if (!enforce) {
318                         logger.debug("No local transaction to join");
319                     }
320                     else {
321                         throw new TransactionRequiredException("No local transaction to join");
322                     }
323                 }
324             }
325         }
326
327         /**
328          * Enlist this application-managed EntityManager in the current transaction.
329          */

330         private void enlistInCurrentTransaction() {
331             // Resource local transaction, need to acquire the EntityTransaction,
332
// start a transaction now and enlist a synchronization for
333
// commit or rollback later.
334
EntityTransaction et = this.target.getTransaction();
335             et.begin();
336             if (logger.isDebugEnabled()) {
337                 logger.debug("Starting resource local transaction on application-managed " +
338                         "EntityManager [" + this.target + "]");
339             }
340             EntityManagerHolder emh = new EntityManagerHolder(this.target);
341             ContainerManagedExtendedEntityManagerSynchronization applicationManagedEntityManagerSynchronization =
342                     new ContainerManagedExtendedEntityManagerSynchronization(emh, this.exceptionTranslator);
343             TransactionSynchronizationManager.bindResource(this.target,
344                     applicationManagedEntityManagerSynchronization);
345             TransactionSynchronizationManager.registerSynchronization(applicationManagedEntityManagerSynchronization);
346         }
347     }
348
349
350     /**
351      * TransactionSynchronization enlisting a container-managed extended
352      * EntityManager with a current transaction.
353      */

354     private static class ContainerManagedExtendedEntityManagerSynchronization extends
355             TransactionSynchronizationAdapter {
356
357         private final EntityManagerHolder entityManagerHolder;
358
359         private final PersistenceExceptionTranslator exceptionTranslator;
360
361         private boolean holderActive = true;
362
363         public ContainerManagedExtendedEntityManagerSynchronization(
364                 EntityManagerHolder emHolder, PersistenceExceptionTranslator exceptionTranslator) {
365
366             this.entityManagerHolder = emHolder;
367             this.exceptionTranslator = exceptionTranslator;
368         }
369
370         public int getOrder() {
371             return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1;
372         }
373
374         public void suspend() {
375             if (this.holderActive) {
376                 TransactionSynchronizationManager.unbindResource(this.entityManagerHolder.getEntityManager());
377             }
378         }
379
380         public void resume() {
381             if (this.holderActive) {
382                 TransactionSynchronizationManager.bindResource(
383                         this.entityManagerHolder.getEntityManager(), this.entityManagerHolder);
384             }
385         }
386
387         public void beforeCompletion() {
388             TransactionSynchronizationManager.unbindResource(this.entityManagerHolder.getEntityManager());
389             this.holderActive = false;
390         }
391
392         public void afterCommit() {
393             // Trigger commit here to let exceptions propagate to the caller.
394
try {
395                 this.entityManagerHolder.getEntityManager().getTransaction().commit();
396             }
397             catch (RuntimeException JavaDoc ex) {
398                 throw convertCompletionException(ex);
399             }
400         }
401
402         public void afterCompletion(int status) {
403             this.entityManagerHolder.setSynchronizedWithTransaction(false);
404             if (status != STATUS_COMMITTED) {
405                 // Haven't had an afterCommit call: trigger a rollback.
406
try {
407                     this.entityManagerHolder.getEntityManager().getTransaction().rollback();
408                 }
409                 catch (RuntimeException JavaDoc ex) {
410                     throw convertCompletionException(ex);
411                 }
412             }
413             // Don't close the EntityManager... That's up to the user.
414
}
415
416         private RuntimeException JavaDoc convertCompletionException(RuntimeException JavaDoc ex) {
417             DataAccessException daex = (this.exceptionTranslator != null) ?
418                     this.exceptionTranslator.translateExceptionIfPossible(ex) :
419                     EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
420             return (daex != null ? daex : ex);
421         }
422     }
423
424 }
425
Popular Tags