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