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:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import org.springframework.util.CollectionUtils;
032:
033: /**
034: * Factory for a shared JPA EntityManager for a given EntityManagerFactory.
035: *
036: * <p>The shared EntityManager will behave just like an EntityManager fetched
037: * from an application server's JNDI environment, as defined by the JPA
038: * specification. It will delegate all calls to the current transactional
039: * EntityManager, if any; else, it will fall back to a newly created
040: * EntityManager per operation.
041: *
042: * @author Juergen Hoeller
043: * @author Rod Johnson
044: * @since 2.0
045: * @see org.springframework.orm.jpa.LocalEntityManagerFactoryBean
046: * @see org.springframework.orm.jpa.JpaTransactionManager
047: */
048: public abstract class SharedEntityManagerCreator {
049:
050: /**
051: * Create a shared transactional EntityManager proxy,
052: * given this EntityManagerFactory
053: * @param emf the EntityManagerFactory to delegate to.
054: * If this implements the EntityManagerFactoryInfo interface, appropriate handling
055: * of the native EntityManagerFactory and available EntityManagerPlusOperations
056: * will automatically apply.
057: * @return a shareable transaction EntityManager proxy
058: */
059: public static EntityManager createSharedEntityManager(
060: EntityManagerFactory emf) {
061: return createSharedEntityManager(emf, null);
062: }
063:
064: /**
065: * Create a shared transactional EntityManager proxy,
066: * given this EntityManagerFactory
067: * @param emf the EntityManagerFactory to delegate to.
068: * If this implements the EntityManagerFactoryInfo interface, appropriate handling
069: * of the native EntityManagerFactory and available EntityManagerPlusOperations
070: * will automatically apply.
071: * @param properties the properties to be passed into the <code>createEntityManager</code>
072: * call (may be <code>null</code>)
073: * @return a shareable transaction EntityManager proxy
074: */
075: public static EntityManager createSharedEntityManager(
076: EntityManagerFactory emf, Map properties) {
077: Class[] entityManagerInterfaces = null;
078: if (emf instanceof EntityManagerFactoryInfo) {
079: EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
080: Class entityManagerInterface = emfInfo
081: .getEntityManagerInterface();
082: JpaDialect jpaDialect = emfInfo.getJpaDialect();
083: if (jpaDialect != null
084: && jpaDialect.supportsEntityManagerPlusOperations()) {
085: entityManagerInterfaces = new Class[] {
086: entityManagerInterface, EntityManagerPlus.class };
087: } else {
088: entityManagerInterfaces = new Class[] { entityManagerInterface };
089: }
090: } else {
091: entityManagerInterfaces = new Class[] { EntityManager.class };
092: }
093: return createSharedEntityManager(emf, properties,
094: entityManagerInterfaces);
095: }
096:
097: /**
098: * Create a shared transactional EntityManager proxy,
099: * given this EntityManagerFactory
100: * @param emf EntityManagerFactory to obtain EntityManagers from as needed
101: * @param properties the properties to be passed into the <code>createEntityManager</code>
102: * call (may be <code>null</code>)
103: * @param entityManagerInterfaces interfaces to be implemented by the
104: * EntityManager. Allows the addition or specification of proprietary interfaces.
105: * @return a shareable transaction EntityManager proxy
106: */
107: public static EntityManager createSharedEntityManager(
108: EntityManagerFactory emf, Map properties,
109: Class... entityManagerInterfaces) {
110:
111: return (EntityManager) Proxy.newProxyInstance(
112: SharedEntityManagerCreator.class.getClassLoader(),
113: entityManagerInterfaces,
114: new SharedEntityManagerInvocationHandler(emf,
115: properties));
116: }
117:
118: /**
119: * Invocation handler that delegates all calls to the current
120: * transactional EntityManager, if any; else, it will fall back
121: * to a newly created EntityManager per operation.
122: */
123: private static class SharedEntityManagerInvocationHandler implements
124: InvocationHandler {
125:
126: private final Log logger = LogFactory.getLog(getClass());
127:
128: private final EntityManagerFactory targetFactory;
129:
130: private final Map properties;
131:
132: public SharedEntityManagerInvocationHandler(
133: EntityManagerFactory target, Map properties) {
134: this .targetFactory = target;
135: this .properties = properties;
136: }
137:
138: public Object invoke(Object proxy, Method method, Object[] args)
139: throws Throwable {
140: // Invocation on EntityManager interface coming in...
141:
142: if (method.getName().equals("equals")) {
143: // Only consider equal when proxies are identical.
144: return (proxy == args[0]);
145: } else if (method.getName().equals("hashCode")) {
146: // Use hashCode of EntityManager proxy.
147: return hashCode();
148: } else if (method.getName().equals("toString")) {
149: // Deliver toString without touching a target EntityManager.
150: return "Shared EntityManager proxy for target factory ["
151: + this .targetFactory + "]";
152: } else if (method.getName().equals("isOpen")) {
153: // Handle isOpen method: always return true.
154: return true;
155: } else if (method.getName().equals("close")) {
156: // Handle close method: suppress, not valid.
157: return null;
158: } else if (method.getName().equals("getTransaction")) {
159: throw new IllegalStateException(
160: "Not allowed to create transaction on shared EntityManager - "
161: + "use Spring transactions or EJB CMT instead");
162: } else if (method.getName().equals("joinTransaction")) {
163: throw new IllegalStateException(
164: "Not allowed to join transaction on shared EntityManager - "
165: + "use Spring transactions or EJB CMT instead");
166: }
167:
168: // Determine current EntityManager: either the transactional one
169: // managed by the factory or a temporary one for the given invocation.
170: EntityManager target = EntityManagerFactoryUtils
171: .doGetTransactionalEntityManager(
172: this .targetFactory, this .properties);
173: boolean isNewEm = false;
174: if (target == null) {
175: logger
176: .debug("Creating new EntityManager for shared EntityManager invocation");
177: target = (!CollectionUtils.isEmpty(this .properties) ? this .targetFactory
178: .createEntityManager(this .properties)
179: : this .targetFactory.createEntityManager());
180: isNewEm = true;
181: }
182:
183: // Invoke method on current EntityManager.
184: try {
185: return method.invoke(target, args);
186: } catch (InvocationTargetException ex) {
187: throw ex.getTargetException();
188: } finally {
189: if (isNewEm) {
190: target.close();
191: }
192: }
193: }
194: }
195:
196: }
|