(no commit message)
[utils] / test / enterprise / src / main / java / org / wamblee / test / persistence / JpaBuilder.java
1 /*
2  * Copyright 2005-2010 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 package org.wamblee.test.persistence;
17
18 import java.util.Map;
19 import java.util.TreeMap;
20 import java.util.logging.Level;
21 import java.util.logging.Logger;
22
23 import javax.persistence.EntityManager;
24 import javax.persistence.EntityManagerFactory;
25 import javax.persistence.EntityTransaction;
26 import javax.persistence.Persistence;
27 import javax.persistence.PersistenceException;
28
29 import org.wamblee.general.ThreadSpecificProxyFactory;
30 import org.wamblee.test.jndi.StubInitialContextFactory;
31 import org.wamblee.test.transactions.TransactionResource;
32 import org.wamblee.test.transactions.TransactionResult;
33
34 /**
35  * Utility for building an appropriately configured EntityManagerFactory. The
36  * idea is that a persistence.xml is used unchanged from the production version.
37  * This utility will then add the additional properties required for execution
38  * in a standalone environment.
39  * 
40  * The other purpose is to to shield dependencies of the test code on a
41  * particular JPA provider.
42  */
43 public class JpaBuilder implements TransactionResource<EntityManager> {
44
45     private static final Logger LOGGER = Logger.getLogger(JpaBuilder.class
46         .getName());
47
48     /**
49      * Callback interface to execute some JPA code within a transaction with the
50      * entitymanager to use provided as input.
51      */
52     public static interface JpaUnitOfWork<T> {
53         /**
54          * Executes the unit of work. A transaction has been started.
55          * 
56          * @param aEm
57          *            Entity manager.
58          * @return Result of the execute method. If you don't want to return
59          *         anything use <code>Void</code> for the return type and return
60          *         null from the implementation.
61          */
62         T execute(EntityManager aEm) throws Exception;
63     }
64
65     private PersistenceUnitDescription persistenceUnit;
66     private String url;
67     private String user;
68     private String password;
69     private EntityManagerFactory factory;
70     private ThreadSpecificProxyFactory<EntityManager> entityManager;
71
72     /**
73      * Constructs the builder.
74      * 
75      * @param aUrl
76      *            JDBC URL
77      * @param aUser
78      *            User name
79      * @param aPassword
80      *            Password.
81      * @param aPersistenceUnit
82      *            Persistence unit.
83      */
84     public JpaBuilder(String aUrl, String aUser, String aPassword,
85         PersistenceUnitDescription aPersistenceUnit) {
86         persistenceUnit = aPersistenceUnit;
87         url = aUrl;
88         user = aUser;
89         password = aPassword;
90         entityManager = new ThreadSpecificProxyFactory<EntityManager>(
91             EntityManager.class);
92     }
93
94     /**
95      * Starts the builder, which in particular, mocks JNDI, binds the datasource
96      * the JNDI where the persistence unit expects it, creates the entity
97      * manager factory, and forces creation of the database schema.
98      */
99     public void start() throws Exception {
100         factory = createFactory();
101         try {
102             execute(new JpaUnitOfWork<Void>() {
103                 public Void execute(EntityManager aEm) {
104                     // Empty, just to trigger database schema creation.
105                     return null;
106                 }
107             });
108         } catch (PersistenceException e) {
109             factory.close();
110             throw e;
111         }
112     }
113
114     /**
115      * Stops the entity manager factory and disables JNDI mocking.
116      */
117     public void stop() {
118         StubInitialContextFactory.unregister();
119         factory.close();
120     }
121
122     /**
123      * Creates a new entity manager factory. Typically not used by test code.
124      * 
125      * @return Entity manager factory.
126      */
127     public EntityManagerFactory createFactory() {
128         Map<String, String> jpaProps = new TreeMap<String, String>();
129
130         jpaProps.put("javax.persistence.jtaDataSource", null);
131         jpaProps.put("javax.persistence.transactionType", "RESOURCE_LOCAL");
132         jpaProps.put("javax.persistence.jdbc.url", url);
133         jpaProps.put("javax.persistence.jdbc.user", user);
134         jpaProps.put("javax.persistence.jdbc.password", password);
135
136         JpaCustomizerBuilder.getCustomizer().customize(persistenceUnit,
137             jpaProps);
138
139         // jpaProps.put("javax.persistence.provider",
140         // HibernatePersistence.class.getName());
141         EntityManagerFactory emf = Persistence.createEntityManagerFactory(
142             persistenceUnit.getUnitName(), jpaProps);
143
144         LOGGER.info("Using " + emf.getClass());
145         return emf;
146     }
147
148     /**
149      * Executes a unit of work. This creates an entitymanager and runs the
150      * {@link JpaUnitOfWork#execute(EntityManager)} within a transaction,
151      * passing it the entity manager. Use of this method saves a lot of typing
152      * for applications.
153      * 
154      * This method requires the transaction to succeed. Otherwise the test will
155      * fail. See {@link #execute(JpaUnitOfWork, TransactionResultCallback)} and
156      * {@link RequireTransactionStatus} for more possibilities.
157      * 
158      * @param aWork
159      *            Work to execute.
160      * 
161      * @return The return value of the execute method of the unit of work.
162      */
163     public <T> T execute(JpaUnitOfWork<T> aWork) throws Exception {
164         return execute(aWork, new RequireTransactionStatus(
165             TransactionResult.COMMIT));
166     }
167
168     /**
169      * Executes a unit of work. This creates an entitymanager and runs the
170      * {@link JpaUnitOfWork#execute(EntityManager)} within a transaction,
171      * passing it the entity manager. Use of this method saves a lot of typing
172      * for applications.
173      * 
174      * @param aWork
175      *            Work to execute.
176      * @param aTransactionResultCallback
177      *            callback to notify of the result of the transaction.
178      * 
179      * @return The return value of the execute method of the unit of work.
180      */
181     public <T> T execute(JpaUnitOfWork<T> aWork,
182         TransactionResultCallback aCallback) throws Exception {
183         EntityManager em = begin();
184         try {
185             T value = aWork.execute(em);
186             TransactionResult result = commit(em);
187             aCallback.status(result);
188             return value;
189         } catch (Exception e) {
190             LOGGER.log(Level.WARNING, "Exception occured", e);
191             TransactionResult result = rollback(em);
192             aCallback.status(result);
193             throw e;
194         }
195     }
196
197     @Override
198     public EntityManager begin() {
199         EntityManager em = factory.createEntityManager();
200         EntityTransaction transaction = em.getTransaction();
201         transaction.begin();
202         entityManager.set(em);
203         return em;
204     }
205
206     @Override
207     public TransactionResult commit(EntityManager aEntityManager) {
208         try {
209             EntityTransaction transaction = aEntityManager.getTransaction();
210             if (transaction.isActive()) {
211                 if (transaction.getRollbackOnly()) {
212                     transaction.rollback();
213                     return TransactionResult.ROLLBACK;
214                 }
215                 transaction.commit();
216                 return TransactionResult.COMMIT;
217             }
218             return TransactionResult.UNKNOWN;
219         } finally {
220             aEntityManager.close();
221             entityManager.set(null);
222         }
223     }
224
225     @Override
226     public TransactionResult rollback(EntityManager aEntityManager) {
227         try {
228             EntityTransaction transaction = aEntityManager.getTransaction();
229             if (transaction.isActive()) {
230                 transaction.rollback();
231                 return TransactionResult.ROLLBACK;
232             }
233             return TransactionResult.UNKNOWN;
234         } finally {
235             if (aEntityManager.isOpen()) {
236                 aEntityManager.close();
237             }
238             entityManager.set(null);
239         }
240     }
241
242     /**
243      * Gets a contextual reference to an entity manager that delegates to the
244      * appropriate (current) one which is active for the current transaction.
245      * 
246      * @return EntityManager.
247      */
248     public EntityManager getContextualEntityManager() {
249         return entityManager.getProxy();
250     }
251 }