001: /**
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package org.apache.openejb.persistence;
018:
019: import java.util.Map;
020: import java.util.HashMap;
021: import javax.persistence.EntityManager;
022: import javax.persistence.EntityManagerFactory;
023: import javax.persistence.TransactionRequiredException;
024: import javax.transaction.Status;
025: import javax.transaction.Synchronization;
026: import javax.transaction.TransactionSynchronizationRegistry;
027:
028: /**
029: * The JtaEntityManagerRegistry tracks JTA entity managers for transation and extended scoped
030: * entity managers. A signle instance of this object should be created and shared by all
031: * JtaEntityManagers in the server instance. Failure to do this will result in multiple entity
032: * managers being created for a single persistence until, and that will result in cache
033: * incoherence.
034: */
035: public class JtaEntityManagerRegistry {
036: /**
037: * Registry of transaction associated entity managers.
038: */
039: private final TransactionSynchronizationRegistry transactionRegistry;
040:
041: /**
042: * Registry of entended context entity managers.
043: */
044: private final ThreadLocal<ExtendedRegistry> extendedRegistry = new ThreadLocal<ExtendedRegistry>() {
045: protected ExtendedRegistry initialValue() {
046: return new ExtendedRegistry();
047: }
048: };
049:
050: /**
051: * Creates a JtaEntityManagerRegistry using the specified transactionSynchronizationRegistry for the registry
052: * if transaction associated entity managers.
053: */
054: public JtaEntityManagerRegistry(
055: TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
056: this .transactionRegistry = transactionSynchronizationRegistry;
057: }
058:
059: /**
060: * Gets an entity manager instance from the transaction registry, extended regitry or for a transaction scoped
061: * entity manager, creates a new one when an exisitng instance is not found.
062: * </p>
063: * It is important that a component adds extended scoped entity managers to this registry when the component is
064: * entered and removes them when exited. If this registration is not preformed, an IllegalStateException will
065: * be thrown when entity manger is fetched.
066: * @param entityManagerFactory the entity manager factory from which an entity manager is required
067: * @param properties the properties passed to the entity manager factory when an entity manager is created
068: * @param extended is the entity manager an extended context
069: * @return the new entity manager
070: * @throws IllegalStateException if the entity manger is extended and there is not an existing entity manager
071: * instance already registered
072: */
073: public EntityManager getEntityManager(
074: EntityManagerFactory entityManagerFactory, Map properties,
075: boolean extended) throws IllegalStateException {
076: if (entityManagerFactory == null)
077: throw new NullPointerException(
078: "entityManagerFactory is null");
079: EntityManagerTxKey txKey = new EntityManagerTxKey(
080: entityManagerFactory);
081: boolean transactionActive = isTransactionActive();
082:
083: // if we have an active transaction, check the tx registry
084: if (transactionActive) {
085: EntityManager entityManager = (EntityManager) transactionRegistry
086: .getResource(txKey);
087: if (entityManager != null) {
088: return entityManager;
089: }
090: }
091:
092: // if extended context, there must be an entity manager already registered with the tx
093: if (extended) {
094: EntityManager entityManager = getInheritedEntityManager(entityManagerFactory);
095: if (entityManager == null) {
096: throw new IllegalStateException(
097: "InternalError: an entity manager should already be registered for this entended persistence unit");
098: }
099:
100: // if transaction is active, we need to register the entity manager with the transaction manager
101: if (transactionActive) {
102: entityManager.joinTransaction();
103: transactionRegistry.putResource(txKey, entityManager);
104: }
105:
106: return entityManager;
107: } else {
108: // create a new entity manager
109: EntityManager entityManager;
110: if (properties != null) {
111: entityManager = entityManagerFactory
112: .createEntityManager(properties);
113: } else {
114: entityManager = entityManagerFactory
115: .createEntityManager();
116: }
117:
118: // if we are in a transaction associate the entity manager with the transaction; otherwise it is
119: // expected the caller will close this entity manager after use
120: if (transactionActive) {
121: transactionRegistry
122: .registerInterposedSynchronization(new CloseEntityManager(
123: entityManager));
124: transactionRegistry.putResource(txKey, entityManager);
125: }
126: return entityManager;
127: }
128: }
129:
130: /**
131: * Adds the entity managers for the specified component to the registry. This should be called when the component
132: * is entered.
133: * @param deploymentId the id of the component
134: * @param entityManagers the entity managers to register
135: * @throws EntityManagerAlreadyRegisteredException if an entity manager is already registered with the transaction
136: * for one of the supplied entity manager factories; for EJBs this should be caught and rethown as an EJBException
137: */
138: public void addEntityManagers(String deploymentId,
139: Object primaryKey,
140: Map<EntityManagerFactory, EntityManager> entityManagers)
141: throws EntityManagerAlreadyRegisteredException {
142: extendedRegistry.get().addEntityManagers(
143: new InstanceId(deploymentId, primaryKey),
144: entityManagers);
145: }
146:
147: /**
148: * Removed the registered entity managers for the specified component.
149: * @param deploymentId the id of the component
150: */
151: public void removeEntityManagers(String deploymentId,
152: Object primaryKey) {
153: extendedRegistry.get().removeEntityManagers(
154: new InstanceId(deploymentId, primaryKey));
155: }
156:
157: /**
158: * Gets an exiting extended entity manager created by a component down the call stack.
159: * @param entityManagerFactory the entity manager factory from which an entity manager is needed
160: * @return the existing entity manager or null if one is not found
161: */
162: public EntityManager getInheritedEntityManager(
163: EntityManagerFactory entityManagerFactory) {
164: return extendedRegistry.get().getInheritedEntityManager(
165: entityManagerFactory);
166: }
167:
168: /**
169: * Notifies the registry that a user transaction has been started or the specified component. When a transaction
170: * is started for a component with registered extended entity managers, the entity managers are enrolled in the
171: * transaction.
172: * @param deploymentId the id of the component
173: */
174: public void transactionStarted(String deploymentId,
175: Object primaryKey) {
176: extendedRegistry.get().transactionStarted(
177: new InstanceId(deploymentId, primaryKey));
178: }
179:
180: /**
181: * Is a transaction active?
182: * @return true if a transaction is active; false otherwise
183: */
184: public boolean isTransactionActive() {
185: int txStatus = transactionRegistry.getTransactionStatus();
186: boolean transactionActive = txStatus == Status.STATUS_ACTIVE
187: || txStatus == Status.STATUS_MARKED_ROLLBACK;
188: return transactionActive;
189: }
190:
191: private class ExtendedRegistry {
192: private final Map<InstanceId, Map<EntityManagerFactory, EntityManager>> entityManagersByDeploymentId = new HashMap<InstanceId, Map<EntityManagerFactory, EntityManager>>();
193:
194: private void addEntityManagers(InstanceId instanceId,
195: Map<EntityManagerFactory, EntityManager> entityManagers)
196: throws EntityManagerAlreadyRegisteredException {
197: if (instanceId == null) {
198: throw new NullPointerException("instanceId is null");
199: }
200: if (entityManagers == null) {
201: throw new NullPointerException("entityManagers is null");
202: }
203:
204: if (isTransactionActive()) {
205: for (Map.Entry<EntityManagerFactory, EntityManager> entry : entityManagers
206: .entrySet()) {
207: EntityManagerFactory entityManagerFactory = entry
208: .getKey();
209: EntityManager entityManager = entry.getValue();
210: EntityManagerTxKey txKey = new EntityManagerTxKey(
211: entityManagerFactory);
212: EntityManager oldEntityManager = (EntityManager) transactionRegistry
213: .getResource(txKey);
214: if (entityManager == oldEntityManager) {
215: break;
216: }
217: if (oldEntityManager != null) {
218: throw new EntityManagerAlreadyRegisteredException(
219: "Another entity manager is already registered for this persistence unit");
220: }
221:
222: entityManager.joinTransaction();
223: transactionRegistry.putResource(txKey,
224: entityManager);
225: }
226: }
227: entityManagersByDeploymentId
228: .put(instanceId, entityManagers);
229: }
230:
231: private void removeEntityManagers(InstanceId instanceId) {
232: if (instanceId == null) {
233: throw new NullPointerException("InstanceId is null");
234: }
235:
236: entityManagersByDeploymentId.remove(instanceId);
237: }
238:
239: private EntityManager getInheritedEntityManager(
240: EntityManagerFactory entityManagerFactory) {
241: if (entityManagerFactory == null) {
242: throw new NullPointerException(
243: "entityManagerFactory is null");
244: }
245:
246: for (Map<EntityManagerFactory, EntityManager> entityManagers : entityManagersByDeploymentId
247: .values()) {
248: EntityManager entityManager = entityManagers
249: .get(entityManagerFactory);
250: if (entityManager != null) {
251: return entityManager;
252: }
253: }
254: return null;
255: }
256:
257: private void transactionStarted(InstanceId instanceId) {
258: if (instanceId == null) {
259: throw new NullPointerException("instanceId is null");
260: }
261: if (!isTransactionActive()) {
262: throw new TransactionRequiredException();
263: }
264:
265: Map<EntityManagerFactory, EntityManager> entityManagers = entityManagersByDeploymentId
266: .get(instanceId);
267: if (entityManagers == null) {
268: return;
269: }
270:
271: for (Map.Entry<EntityManagerFactory, EntityManager> entry : entityManagers
272: .entrySet()) {
273: EntityManagerFactory entityManagerFactory = entry
274: .getKey();
275: EntityManager entityManager = entry.getValue();
276: entityManager.joinTransaction();
277: EntityManagerTxKey txKey = new EntityManagerTxKey(
278: entityManagerFactory);
279: transactionRegistry.putResource(txKey, entityManager);
280: }
281: }
282: }
283:
284: private static class InstanceId {
285: private final String deploymentId;
286: private final Object primaryKey;
287:
288: public InstanceId(String deploymentId, Object primaryKey) {
289: if (deploymentId == null) {
290: throw new NullPointerException("deploymentId is null");
291: }
292: if (primaryKey == null) {
293: throw new NullPointerException("primaryKey is null");
294: }
295: this .deploymentId = deploymentId;
296: this .primaryKey = primaryKey;
297: }
298:
299: public boolean equals(Object o) {
300: if (this == o) {
301: return true;
302: }
303: if (o == null || getClass() != o.getClass()) {
304: return false;
305: }
306:
307: final InstanceId that = (InstanceId) o;
308: return deploymentId.equals(that.deploymentId)
309: && primaryKey.equals(that.primaryKey);
310:
311: }
312:
313: public int hashCode() {
314: int result;
315: result = deploymentId.hashCode();
316: result = 29 * result + primaryKey.hashCode();
317: return result;
318: }
319: }
320:
321: private static class CloseEntityManager implements Synchronization {
322: private final EntityManager entityManager;
323:
324: public CloseEntityManager(EntityManager entityManager) {
325: this .entityManager = entityManager;
326: }
327:
328: public void beforeCompletion() {
329: }
330:
331: public void afterCompletion(int i) {
332: entityManager.close();
333: }
334: }
335: }
|