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: Class[] ifcs = new Class[entityManagerInterfaces.length + 1];
112: System.arraycopy(entityManagerInterfaces, 0, ifcs, 0,
113: entityManagerInterfaces.length);
114: ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class;
115: return (EntityManager) Proxy.newProxyInstance(
116: SharedEntityManagerCreator.class.getClassLoader(),
117: ifcs, new SharedEntityManagerInvocationHandler(emf,
118: properties));
119: }
120:
121: /**
122: * Invocation handler that delegates all calls to the current
123: * transactional EntityManager, if any; else, it will fall back
124: * to a newly created EntityManager per operation.
125: */
126: private static class SharedEntityManagerInvocationHandler implements
127: InvocationHandler {
128:
129: private final Log logger = LogFactory.getLog(getClass());
130:
131: private final EntityManagerFactory targetFactory;
132:
133: private final Map properties;
134:
135: public SharedEntityManagerInvocationHandler(
136: EntityManagerFactory target, Map properties) {
137: this .targetFactory = target;
138: this .properties = properties;
139: }
140:
141: public Object invoke(Object proxy, Method method, Object[] args)
142: throws Throwable {
143: // Invocation on EntityManager interface coming in...
144:
145: if (method.getName().equals("equals")) {
146: // Only consider equal when proxies are identical.
147: return (proxy == args[0]);
148: } else if (method.getName().equals("hashCode")) {
149: // Use hashCode of EntityManager proxy.
150: return hashCode();
151: } else if (method.getName().equals("toString")) {
152: // Deliver toString without touching a target EntityManager.
153: return "Shared EntityManager proxy for target factory ["
154: + this .targetFactory + "]";
155: } else if (method.getName().equals("isOpen")) {
156: // Handle isOpen method: always return true.
157: return true;
158: } else if (method.getName().equals("close")) {
159: // Handle close method: suppress, not valid.
160: return null;
161: } else if (method.getName().equals("getTransaction")) {
162: throw new IllegalStateException(
163: "Not allowed to create transaction on shared EntityManager - "
164: + "use Spring transactions or EJB CMT instead");
165: } else if (method.getName().equals("joinTransaction")) {
166: throw new IllegalStateException(
167: "Not allowed to join transaction on shared EntityManager - "
168: + "use Spring transactions or EJB CMT instead");
169: }
170:
171: // Determine current EntityManager: either the transactional one
172: // managed by the factory or a temporary one for the given invocation.
173: EntityManager target = EntityManagerFactoryUtils
174: .doGetTransactionalEntityManager(
175: this .targetFactory, this .properties);
176:
177: // Handle EntityManagerProxy interface.
178: if (method.getName().equals("getTargetEntityManager")) {
179: if (target == null) {
180: throw new IllegalStateException(
181: "No transactional EntityManager available");
182: }
183: return target;
184: }
185:
186: // Regular EntityManager operations.
187: boolean isNewEm = false;
188: if (target == null) {
189: logger
190: .debug("Creating new EntityManager for shared EntityManager invocation");
191: target = (!CollectionUtils.isEmpty(this .properties) ? this .targetFactory
192: .createEntityManager(this .properties)
193: : this .targetFactory.createEntityManager());
194: isNewEm = true;
195: }
196:
197: // Invoke method on current EntityManager.
198: try {
199: return method.invoke(target, args);
200: } catch (InvocationTargetException ex) {
201: throw ex.getTargetException();
202: } finally {
203: if (isNewEm) {
204: target.close();
205: }
206: }
207: }
208: }
209:
210: }
|