001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.orm.jpa;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022import java.lang.reflect.InvocationHandler;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.lang.reflect.Proxy;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029import javax.persistence.EntityManager;
030import javax.persistence.EntityManagerFactory;
031import javax.persistence.Query;
032import javax.persistence.TransactionRequiredException;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036
037import org.springframework.transaction.support.TransactionSynchronizationManager;
038import org.springframework.util.ClassUtils;
039import org.springframework.util.CollectionUtils;
040
041/**
042 * Delegate for creating a shareable JPA {@link javax.persistence.EntityManager}
043 * reference for a given {@link javax.persistence.EntityManagerFactory}.
044 *
045 * <p>A shared EntityManager will behave just like an EntityManager fetched from
046 * an application server's JNDI environment, as defined by the JPA specification.
047 * It will delegate all calls to the current transactional EntityManager, if any;
048 * otherwise it will fall back to a newly created EntityManager per operation.
049 *
050 * <p>For a behavioral definition of such a shared transactional EntityManager,
051 * see {@link javax.persistence.PersistenceContextType#TRANSACTION} and its
052 * discussion in the JPA spec document. This is also the default being used
053 * for the annotation-based {@link javax.persistence.PersistenceContext#type()}.
054 *
055 * @author Juergen Hoeller
056 * @author Rod Johnson
057 * @author Oliver Gierke
058 * @since 2.0
059 * @see javax.persistence.PersistenceContext
060 * @see javax.persistence.PersistenceContextType#TRANSACTION
061 * @see org.springframework.orm.jpa.JpaTransactionManager
062 * @see ExtendedEntityManagerCreator
063 */
064public abstract class SharedEntityManagerCreator {
065
066        private static final Class<?>[] NO_ENTITY_MANAGER_INTERFACES = new Class<?>[0];
067
068        private static final Set<String> transactionRequiringMethods = new HashSet<String>(8);
069
070        private static final Set<String> queryTerminatingMethods = new HashSet<String>(8);
071
072        static {
073                transactionRequiringMethods.add("joinTransaction");
074                transactionRequiringMethods.add("flush");
075                transactionRequiringMethods.add("persist");
076                transactionRequiringMethods.add("merge");
077                transactionRequiringMethods.add("remove");
078                transactionRequiringMethods.add("refresh");
079
080                queryTerminatingMethods.add("execute");  // JPA 2.1 StoredProcedureQuery
081                queryTerminatingMethods.add("executeUpdate");
082                queryTerminatingMethods.add("getSingleResult");
083                queryTerminatingMethods.add("getResultList");
084                queryTerminatingMethods.add("getResultStream");
085        }
086
087
088        /**
089         * Create a transactional EntityManager proxy for the given EntityManagerFactory.
090         * @param emf the EntityManagerFactory to delegate to.
091         * @return a shareable transaction EntityManager proxy
092         */
093        public static EntityManager createSharedEntityManager(EntityManagerFactory emf) {
094                return createSharedEntityManager(emf, null, true);
095        }
096
097        /**
098         * Create a transactional EntityManager proxy for the given EntityManagerFactory.
099         * @param emf the EntityManagerFactory to delegate to.
100         * @param properties the properties to be passed into the
101         * {@code createEntityManager} call (may be {@code null})
102         * @return a shareable transaction EntityManager proxy
103         */
104        public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map<?, ?> properties) {
105                return createSharedEntityManager(emf, properties, true);
106        }
107
108        /**
109         * Create a transactional EntityManager proxy for the given EntityManagerFactory.
110         * @param emf the EntityManagerFactory to delegate to.
111         * @param properties the properties to be passed into the
112         * {@code createEntityManager} call (may be {@code null})
113         * @param synchronizedWithTransaction whether to automatically join ongoing
114         * transactions (according to the JPA 2.1 SynchronizationType rules)
115         * @return a shareable transaction EntityManager proxy
116         * @since 4.0
117         */
118        public static EntityManager createSharedEntityManager(
119                        EntityManagerFactory emf, Map<?, ?> properties, boolean synchronizedWithTransaction) {
120
121                Class<?> emIfc = (emf instanceof EntityManagerFactoryInfo ?
122                                ((EntityManagerFactoryInfo) emf).getEntityManagerInterface() : EntityManager.class);
123                return createSharedEntityManager(emf, properties, synchronizedWithTransaction,
124                                (emIfc == null ? NO_ENTITY_MANAGER_INTERFACES : new Class<?>[] {emIfc}));
125        }
126
127        /**
128         * Create a transactional EntityManager proxy for the given EntityManagerFactory.
129         * @param emf the EntityManagerFactory to obtain EntityManagers from as needed
130         * @param properties the properties to be passed into the
131         * {@code createEntityManager} call (may be {@code null})
132         * @param entityManagerInterfaces the interfaces to be implemented by the
133         * EntityManager. Allows the addition or specification of proprietary interfaces.
134         * @return a shareable transactional EntityManager proxy
135         */
136        public static EntityManager createSharedEntityManager(
137                        EntityManagerFactory emf, Map<?, ?> properties, Class<?>... entityManagerInterfaces) {
138
139                return createSharedEntityManager(emf, properties, true, entityManagerInterfaces);
140        }
141
142        /**
143         * Create a transactional EntityManager proxy for the given EntityManagerFactory.
144         * @param emf the EntityManagerFactory to obtain EntityManagers from as needed
145         * @param properties the properties to be passed into the
146         * {@code createEntityManager} call (may be {@code null})
147         * @param synchronizedWithTransaction whether to automatically join ongoing
148         * transactions (according to the JPA 2.1 SynchronizationType rules)
149         * @param entityManagerInterfaces the interfaces to be implemented by the
150         * EntityManager. Allows the addition or specification of proprietary interfaces.
151         * @return a shareable transactional EntityManager proxy
152         * @since 4.0
153         */
154        public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map<?, ?> properties,
155                        boolean synchronizedWithTransaction, Class<?>... entityManagerInterfaces) {
156
157                ClassLoader cl = null;
158                if (emf instanceof EntityManagerFactoryInfo) {
159                        cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader();
160                }
161                Class<?>[] ifcs = new Class<?>[entityManagerInterfaces.length + 1];
162                System.arraycopy(entityManagerInterfaces, 0, ifcs, 0, entityManagerInterfaces.length);
163                ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class;
164                return (EntityManager) Proxy.newProxyInstance(
165                                (cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),
166                                ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
167        }
168
169
170        /**
171         * Invocation handler that delegates all calls to the current
172         * transactional EntityManager, if any; else, it will fall back
173         * to a newly created EntityManager per operation.
174         */
175        @SuppressWarnings("serial")
176        private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
177
178                private final Log logger = LogFactory.getLog(getClass());
179
180                private final EntityManagerFactory targetFactory;
181
182                private final Map<?, ?> properties;
183
184                private final boolean synchronizedWithTransaction;
185
186                private transient volatile ClassLoader proxyClassLoader;
187
188                public SharedEntityManagerInvocationHandler(
189                                EntityManagerFactory target, Map<?, ?> properties, boolean synchronizedWithTransaction) {
190
191                        this.targetFactory = target;
192                        this.properties = properties;
193                        this.synchronizedWithTransaction = synchronizedWithTransaction;
194                        initProxyClassLoader();
195                }
196
197                private void initProxyClassLoader() {
198                        if (this.targetFactory instanceof EntityManagerFactoryInfo) {
199                                this.proxyClassLoader = ((EntityManagerFactoryInfo) this.targetFactory).getBeanClassLoader();
200                        }
201                        else {
202                                this.proxyClassLoader = this.targetFactory.getClass().getClassLoader();
203                        }
204                }
205
206                @Override
207                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
208                        // Invocation on EntityManager interface coming in...
209
210                        if (method.getName().equals("equals")) {
211                                // Only consider equal when proxies are identical.
212                                return (proxy == args[0]);
213                        }
214                        else if (method.getName().equals("hashCode")) {
215                                // Use hashCode of EntityManager proxy.
216                                return hashCode();
217                        }
218                        else if (method.getName().equals("toString")) {
219                                // Deliver toString without touching a target EntityManager.
220                                return "Shared EntityManager proxy for target factory [" + this.targetFactory + "]";
221                        }
222                        else if (method.getName().equals("getEntityManagerFactory")) {
223                                // JPA 2.0: return EntityManagerFactory without creating an EntityManager.
224                                return this.targetFactory;
225                        }
226                        else if (method.getName().equals("getCriteriaBuilder") || method.getName().equals("getMetamodel")) {
227                                // JPA 2.0: return EntityManagerFactory's CriteriaBuilder/Metamodel (avoid creation of EntityManager)
228                                try {
229                                        return EntityManagerFactory.class.getMethod(method.getName()).invoke(this.targetFactory);
230                                }
231                                catch (InvocationTargetException ex) {
232                                        throw ex.getTargetException();
233                                }
234                        }
235                        else if (method.getName().equals("unwrap")) {
236                                // JPA 2.0: handle unwrap method - could be a proxy match.
237                                Class<?> targetClass = (Class<?>) args[0];
238                                if (targetClass != null && targetClass.isInstance(proxy)) {
239                                        return proxy;
240                                }
241                        }
242                        else if (method.getName().equals("isOpen")) {
243                                // Handle isOpen method: always return true.
244                                return true;
245                        }
246                        else if (method.getName().equals("close")) {
247                                // Handle close method: suppress, not valid.
248                                return null;
249                        }
250                        else if (method.getName().equals("getTransaction")) {
251                                throw new IllegalStateException(
252                                                "Not allowed to create transaction on shared EntityManager - " +
253                                                "use Spring transactions or EJB CMT instead");
254                        }
255
256                        // Determine current EntityManager: either the transactional one
257                        // managed by the factory or a temporary one for the given invocation.
258                        EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager(
259                                        this.targetFactory, this.properties, this.synchronizedWithTransaction);
260
261                        if (method.getName().equals("getTargetEntityManager")) {
262                                // Handle EntityManagerProxy interface.
263                                if (target == null) {
264                                        throw new IllegalStateException("No transactional EntityManager available");
265                                }
266                                return target;
267                        }
268                        else if (method.getName().equals("unwrap")) {
269                                Class<?> targetClass = (Class<?>) args[0];
270                                if (targetClass == null) {
271                                        return (target != null ? target : proxy);
272                                }
273                                // We need a transactional target now.
274                                if (target == null) {
275                                        throw new IllegalStateException("No transactional EntityManager available");
276                                }
277                                // Still perform unwrap call on target EntityManager.
278                        }
279                        else if (transactionRequiringMethods.contains(method.getName())) {
280                                // We need a transactional target now, according to the JPA spec.
281                                // Otherwise, the operation would get accepted but remain unflushed...
282                                if (target == null || (!TransactionSynchronizationManager.isActualTransactionActive() &&
283                                                !target.getTransaction().isActive())) {
284                                        throw new TransactionRequiredException("No EntityManager with actual transaction available " +
285                                                        "for current thread - cannot reliably process '" + method.getName() + "' call");
286                                }
287                        }
288
289                        // Regular EntityManager operations.
290                        boolean isNewEm = false;
291                        if (target == null) {
292                                logger.debug("Creating new EntityManager for shared EntityManager invocation");
293                                target = (!CollectionUtils.isEmpty(this.properties) ?
294                                                this.targetFactory.createEntityManager(this.properties) :
295                                                this.targetFactory.createEntityManager());
296                                isNewEm = true;
297                        }
298
299                        // Invoke method on current EntityManager.
300                        try {
301                                Object result = method.invoke(target, args);
302                                if (result instanceof Query) {
303                                        Query query = (Query) result;
304                                        if (isNewEm) {
305                                                Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(query.getClass(), this.proxyClassLoader);
306                                                result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
307                                                                new DeferredQueryInvocationHandler(query, target));
308                                                isNewEm = false;
309                                        }
310                                        else {
311                                                EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
312                                        }
313                                }
314                                return result;
315                        }
316                        catch (InvocationTargetException ex) {
317                                throw ex.getTargetException();
318                        }
319                        finally {
320                                if (isNewEm) {
321                                        EntityManagerFactoryUtils.closeEntityManager(target);
322                                }
323                        }
324                }
325
326                private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
327                        // Rely on default serialization, just initialize state after deserialization.
328                        ois.defaultReadObject();
329                        // Initialize transient fields.
330                        initProxyClassLoader();
331                }
332        }
333
334
335        /**
336         * Invocation handler that handles deferred Query objects created by
337         * non-transactional createQuery invocations on a shared EntityManager.
338         */
339        private static class DeferredQueryInvocationHandler implements InvocationHandler {
340
341                private final Query target;
342
343                private EntityManager entityManager;
344
345                public DeferredQueryInvocationHandler(Query target, EntityManager entityManager) {
346                        this.target = target;
347                        this.entityManager = entityManager;
348                }
349
350                @Override
351                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
352                        // Invocation on Query interface coming in...
353
354                        if (method.getName().equals("equals")) {
355                                // Only consider equal when proxies are identical.
356                                return (proxy == args[0]);
357                        }
358                        else if (method.getName().equals("hashCode")) {
359                                // Use hashCode of EntityManager proxy.
360                                return hashCode();
361                        }
362                        else if (method.getName().equals("unwrap")) {
363                                // Handle JPA 2.0 unwrap method - could be a proxy match.
364                                Class<?> targetClass = (Class<?>) args[0];
365                                if (targetClass == null) {
366                                        return this.target;
367                                }
368                                else if (targetClass.isInstance(proxy)) {
369                                        return proxy;
370                                }
371                        }
372
373                        // Invoke method on actual Query object.
374                        try {
375                                Object retVal = method.invoke(this.target, args);
376                                return (retVal == this.target ? proxy : retVal);
377                        }
378                        catch (InvocationTargetException ex) {
379                                throw ex.getTargetException();
380                        }
381                        finally {
382                                if (queryTerminatingMethods.contains(method.getName())) {
383                                        // Actual execution of the query: close the EntityManager right
384                                        // afterwards, since that was the only reason we kept it open.
385                                        EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
386                                        this.entityManager = null;
387                                }
388                        }
389                }
390        }
391
392}