1 /**
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one or more
4 * contributor license agreements. See the NOTICE file distributed with
5 * this work for additional information regarding copyright ownership.
6 * The ASF licenses this file to You under the Apache License, Version 2.0
7 * (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.openejb.persistence;
19
20
21 import java.util.Map;
22 import java.util.HashMap;
23 import javax.persistence.EntityManager;
24 import javax.persistence.EntityManagerFactory;
25 import javax.persistence.TransactionRequiredException;
26 import javax.transaction.Status;
27 import javax.transaction.Synchronization;
28 import javax.transaction.TransactionSynchronizationRegistry;
29
30 /**
31 * The JtaEntityManagerRegistry tracks JTA entity managers for transation and extended scoped
32 * entity managers. A signle instance of this object should be created and shared by all
33 * JtaEntityManagers in the server instance. Failure to do this will result in multiple entity
34 * managers being created for a single persistence until, and that will result in cache
35 * incoherence.
36 */
37 public class JtaEntityManagerRegistry {
38 /**
39 * Registry of transaction associated entity managers.
40 */
41 private final TransactionSynchronizationRegistry transactionRegistry;
42
43 /**
44 * Registry of entended context entity managers.
45 */
46 private final ThreadLocal<ExtendedRegistry> extendedRegistry = new ThreadLocal<ExtendedRegistry>() {
47 protected ExtendedRegistry initialValue() {
48 return new ExtendedRegistry();
49 }
50 };
51
52 /**
53 * Creates a JtaEntityManagerRegistry using the specified transactionSynchronizationRegistry for the registry
54 * if transaction associated entity managers.
55 */
56 public JtaEntityManagerRegistry(TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
57 this.transactionRegistry = transactionSynchronizationRegistry;
58 }
59
60 /**
61 * Gets an entity manager instance from the transaction registry, extended regitry or for a transaction scoped
62 * entity manager, creates a new one when an exisitng instance is not found.
63 * </p>
64 * It is important that a component adds extended scoped entity managers to this registry when the component is
65 * entered and removes them when exited. If this registration is not preformed, an IllegalStateException will
66 * be thrown when entity manger is fetched.
67 * @param entityManagerFactory the entity manager factory from which an entity manager is required
68 * @param properties the properties passed to the entity manager factory when an entity manager is created
69 * @param extended is the entity manager an extended context
70 * @return the new entity manager
71 * @throws IllegalStateException if the entity manger is extended and there is not an existing entity manager
72 * instance already registered
73 */
74 public EntityManager getEntityManager(EntityManagerFactory entityManagerFactory, Map properties, boolean extended) throws IllegalStateException {
75 if (entityManagerFactory == null) throw new NullPointerException("entityManagerFactory is null");
76 EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory);
77 boolean transactionActive = isTransactionActive();
78
79 // if we have an active transaction, check the tx registry
80 if (transactionActive) {
81 EntityManager entityManager = (EntityManager) transactionRegistry.getResource(txKey);
82 if (entityManager != null) {
83 return entityManager;
84 }
85 }
86
87 // if extended context, there must be an entity manager already registered with the tx
88 if (extended) {
89 EntityManager entityManager = getInheritedEntityManager(entityManagerFactory);
90 if (entityManager == null) {
91 throw new IllegalStateException("InternalError: an entity manager should already be registered for this entended persistence unit");
92 }
93
94 // if transaction is active, we need to register the entity manager with the transaction manager
95 if (transactionActive) {
96 entityManager.joinTransaction();
97 transactionRegistry.putResource(txKey, entityManager);
98 }
99
100 return entityManager;
101 } else {
102 // create a new entity manager
103 EntityManager entityManager;
104 if (properties != null) {
105 entityManager = entityManagerFactory.createEntityManager(properties);
106 } else {
107 entityManager = entityManagerFactory.createEntityManager();
108 }
109
110 // if we are in a transaction associate the entity manager with the transaction; otherwise it is
111 // expected the caller will close this entity manager after use
112 if (transactionActive) {
113 transactionRegistry.registerInterposedSynchronization(new CloseEntityManager(entityManager));
114 transactionRegistry.putResource(txKey, entityManager);
115 }
116 return entityManager;
117 }
118 }
119
120 /**
121 * Adds the entity managers for the specified component to the registry. This should be called when the component
122 * is entered.
123 * @param deploymentId the id of the component
124 * @param entityManagers the entity managers to register
125 * @throws EntityManagerAlreadyRegisteredException if an entity manager is already registered with the transaction
126 * for one of the supplied entity manager factories; for EJBs this should be caught and rethown as an EJBException
127 */
128 public void addEntityManagers(String deploymentId, Object primaryKey, Map<EntityManagerFactory, EntityManager> entityManagers) throws EntityManagerAlreadyRegisteredException {
129 extendedRegistry.get().addEntityManagers(new InstanceId(deploymentId, primaryKey), entityManagers);
130 }
131
132 /**
133 * Removed the registered entity managers for the specified component.
134 * @param deploymentId the id of the component
135 */
136 public void removeEntityManagers(String deploymentId, Object primaryKey) {
137 extendedRegistry.get().removeEntityManagers(new InstanceId(deploymentId, primaryKey));
138 }
139
140 /**
141 * Gets an exiting extended entity manager created by a component down the call stack.
142 * @param entityManagerFactory the entity manager factory from which an entity manager is needed
143 * @return the existing entity manager or null if one is not found
144 */
145 public EntityManager getInheritedEntityManager(EntityManagerFactory entityManagerFactory) {
146 return extendedRegistry.get().getInheritedEntityManager(entityManagerFactory);
147 }
148
149 /**
150 * Notifies the registry that a user transaction has been started or the specified component. When a transaction
151 * is started for a component with registered extended entity managers, the entity managers are enrolled in the
152 * transaction.
153 * @param deploymentId the id of the component
154 */
155 public void transactionStarted(String deploymentId, Object primaryKey) {
156 extendedRegistry.get().transactionStarted(new InstanceId(deploymentId, primaryKey));
157 }
158
159 /**
160 * Is a transaction active?
161 * @return true if a transaction is active; false otherwise
162 */
163 public boolean isTransactionActive() {
164 int txStatus = transactionRegistry.getTransactionStatus();
165 boolean transactionActive = txStatus == Status.STATUS_ACTIVE || txStatus == Status.STATUS_MARKED_ROLLBACK;
166 return transactionActive;
167 }
168
169 private class ExtendedRegistry {
170 private final Map<InstanceId, Map<EntityManagerFactory, EntityManager>> entityManagersByDeploymentId =
171 new HashMap<InstanceId, Map<EntityManagerFactory, EntityManager>>();
172
173 private void addEntityManagers(InstanceId instanceId, Map<EntityManagerFactory, EntityManager> entityManagers) throws EntityManagerAlreadyRegisteredException {
174 if (instanceId == null) {
175 throw new NullPointerException("instanceId is null");
176 }
177 if (entityManagers == null) {
178 throw new NullPointerException("entityManagers is null");
179 }
180
181 if (isTransactionActive()) {
182 for (Map.Entry<EntityManagerFactory, EntityManager> entry : entityManagers.entrySet()) {
183 EntityManagerFactory entityManagerFactory = entry.getKey();
184 EntityManager entityManager = entry.getValue();
185 EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory);
186 EntityManager oldEntityManager = (EntityManager) transactionRegistry.getResource(txKey);
187 if (entityManager == oldEntityManager) {
188 break;
189 }
190 if (oldEntityManager != null) {
191 throw new EntityManagerAlreadyRegisteredException("Another entity manager is already registered for this persistence unit");
192 }
193
194 entityManager.joinTransaction();
195 transactionRegistry.putResource(txKey, entityManager);
196 }
197 }
198 entityManagersByDeploymentId.put(instanceId, entityManagers);
199 }
200
201 private void removeEntityManagers(InstanceId instanceId) {
202 if (instanceId == null) {
203 throw new NullPointerException("InstanceId is null");
204 }
205
206 entityManagersByDeploymentId.remove(instanceId);
207 }
208
209 private EntityManager getInheritedEntityManager(EntityManagerFactory entityManagerFactory) {
210 if (entityManagerFactory == null) {
211 throw new NullPointerException("entityManagerFactory is null");
212 }
213
214 for (Map<EntityManagerFactory, EntityManager> entityManagers : entityManagersByDeploymentId.values()) {
215 EntityManager entityManager = entityManagers.get(entityManagerFactory);
216 if (entityManager != null) {
217 return entityManager;
218 }
219 }
220 return null;
221 }
222
223 private void transactionStarted(InstanceId instanceId) {
224 if (instanceId == null) {
225 throw new NullPointerException("instanceId is null");
226 }
227 if (!isTransactionActive()) {
228 throw new TransactionRequiredException();
229 }
230
231 Map<EntityManagerFactory, EntityManager> entityManagers = entityManagersByDeploymentId.get(instanceId);
232 if (entityManagers == null) {
233 return;
234 }
235
236 for (Map.Entry<EntityManagerFactory, EntityManager> entry : entityManagers.entrySet()) {
237 EntityManagerFactory entityManagerFactory = entry.getKey();
238 EntityManager entityManager = entry.getValue();
239 entityManager.joinTransaction();
240 EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory);
241 transactionRegistry.putResource(txKey, entityManager);
242 }
243 }
244 }
245
246 private static class InstanceId {
247 private final String deploymentId;
248 private final Object primaryKey;
249
250 public InstanceId(String deploymentId, Object primaryKey) {
251 if (deploymentId == null) {
252 throw new NullPointerException("deploymentId is null");
253 }
254 if (primaryKey == null) {
255 throw new NullPointerException("primaryKey is null");
256 }
257 this.deploymentId = deploymentId;
258 this.primaryKey = primaryKey;
259 }
260
261 public boolean equals(Object o) {
262 if (this == o) {
263 return true;
264 }
265 if (o == null || getClass() != o.getClass()) {
266 return false;
267 }
268
269 final InstanceId that = (InstanceId) o;
270 return deploymentId.equals(that.deploymentId) &&
271 primaryKey.equals(that.primaryKey);
272
273 }
274
275 public int hashCode() {
276 int result;
277 result = deploymentId.hashCode();
278 result = 29 * result + primaryKey.hashCode();
279 return result;
280 }
281 }
282
283 private static class CloseEntityManager implements Synchronization {
284 private final EntityManager entityManager;
285
286 public CloseEntityManager(EntityManager entityManager) {
287 this.entityManager = entityManager;
288 }
289
290 public void beforeCompletion() {
291 }
292
293 public void afterCompletion(int i) {
294 entityManager.close();
295 }
296 }
297 }