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.NotSerializableException;
021import java.io.ObjectInputStream;
022import java.io.ObjectStreamException;
023import java.io.Serializable;
024import java.lang.reflect.InvocationHandler;
025import java.lang.reflect.InvocationTargetException;
026import java.lang.reflect.Method;
027import java.lang.reflect.Proxy;
028import java.util.HashMap;
029import java.util.LinkedHashSet;
030import java.util.Map;
031import java.util.Properties;
032import java.util.Set;
033import java.util.concurrent.Callable;
034import java.util.concurrent.ExecutionException;
035import java.util.concurrent.Future;
036import javax.persistence.EntityManager;
037import javax.persistence.EntityManagerFactory;
038import javax.persistence.PersistenceException;
039import javax.persistence.Query;
040import javax.persistence.spi.PersistenceProvider;
041import javax.persistence.spi.PersistenceUnitInfo;
042import javax.sql.DataSource;
043
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046
047import org.springframework.beans.BeanUtils;
048import org.springframework.beans.factory.BeanClassLoaderAware;
049import org.springframework.beans.factory.BeanFactory;
050import org.springframework.beans.factory.BeanFactoryAware;
051import org.springframework.beans.factory.BeanNameAware;
052import org.springframework.beans.factory.DisposableBean;
053import org.springframework.beans.factory.FactoryBean;
054import org.springframework.beans.factory.InitializingBean;
055import org.springframework.core.task.AsyncTaskExecutor;
056import org.springframework.dao.DataAccessException;
057import org.springframework.dao.support.PersistenceExceptionTranslator;
058import org.springframework.util.ClassUtils;
059import org.springframework.util.CollectionUtils;
060
061/**
062 * Abstract {@link org.springframework.beans.factory.FactoryBean} that creates
063 * a local JPA {@link javax.persistence.EntityManagerFactory} instance within
064 * a Spring application context.
065 *
066 * <p>Encapsulates the common functionality between the different JPA bootstrap
067 * contracts (standalone as well as container).
068 *
069 * <p>Implements support for standard JPA configuration conventions as well as
070 * Spring's customizable {@link JpaVendorAdapter} mechanism, and controls the
071 * EntityManagerFactory's lifecycle.
072 *
073 * <p>This class also implements the
074 * {@link org.springframework.dao.support.PersistenceExceptionTranslator}
075 * interface, as autodetected by Spring's
076 * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor},
077 * for AOP-based translation of native exceptions to Spring DataAccessExceptions.
078 * Hence, the presence of e.g. LocalEntityManagerFactoryBean automatically enables
079 * a PersistenceExceptionTranslationPostProcessor to translate JPA exceptions.
080 *
081 * @author Juergen Hoeller
082 * @author Rod Johnson
083 * @since 2.0
084 * @see LocalEntityManagerFactoryBean
085 * @see LocalContainerEntityManagerFactoryBean
086 */
087@SuppressWarnings("serial")
088public abstract class AbstractEntityManagerFactoryBean implements
089                FactoryBean<EntityManagerFactory>, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware,
090                InitializingBean, DisposableBean, EntityManagerFactoryInfo, PersistenceExceptionTranslator, Serializable {
091
092        /** Logger available to subclasses */
093        protected final Log logger = LogFactory.getLog(getClass());
094
095        private PersistenceProvider persistenceProvider;
096
097        private String persistenceUnitName;
098
099        private final Map<String, Object> jpaPropertyMap = new HashMap<String, Object>();
100
101        private Class<? extends EntityManagerFactory> entityManagerFactoryInterface;
102
103        private Class<? extends EntityManager> entityManagerInterface;
104
105        private JpaDialect jpaDialect;
106
107        private JpaVendorAdapter jpaVendorAdapter;
108
109        private AsyncTaskExecutor bootstrapExecutor;
110
111        private ClassLoader beanClassLoader = getClass().getClassLoader();
112
113        private BeanFactory beanFactory;
114
115        private String beanName;
116
117        /** Raw EntityManagerFactory as returned by the PersistenceProvider */
118        private EntityManagerFactory nativeEntityManagerFactory;
119
120        /** Future for lazily initializing raw target EntityManagerFactory */
121        private Future<EntityManagerFactory> nativeEntityManagerFactoryFuture;
122
123        /** Exposed client-level EntityManagerFactory proxy */
124        private EntityManagerFactory entityManagerFactory;
125
126
127        /**
128         * Set the PersistenceProvider implementation class to use for creating the
129         * EntityManagerFactory. If not specified, the persistence provider will be
130         * taken from the JpaVendorAdapter (if any) or retrieved through scanning
131         * (as far as possible).
132         * @see JpaVendorAdapter#getPersistenceProvider()
133         * @see javax.persistence.spi.PersistenceProvider
134         * @see javax.persistence.Persistence
135         */
136        public void setPersistenceProviderClass(Class<? extends PersistenceProvider> persistenceProviderClass) {
137                this.persistenceProvider = BeanUtils.instantiateClass(persistenceProviderClass);
138        }
139
140        /**
141         * Set the PersistenceProvider instance to use for creating the
142         * EntityManagerFactory. If not specified, the persistence provider
143         * will be taken from the JpaVendorAdapter (if any) or determined
144         * by the persistence unit deployment descriptor (as far as possible).
145         * @see JpaVendorAdapter#getPersistenceProvider()
146         * @see javax.persistence.spi.PersistenceProvider
147         * @see javax.persistence.Persistence
148         */
149        public void setPersistenceProvider(PersistenceProvider persistenceProvider) {
150                this.persistenceProvider = persistenceProvider;
151        }
152
153        @Override
154        public PersistenceProvider getPersistenceProvider() {
155                return this.persistenceProvider;
156        }
157
158        /**
159         * Specify the name of the EntityManagerFactory configuration.
160         * <p>Default is none, indicating the default EntityManagerFactory
161         * configuration. The persistence provider will throw an exception if
162         * ambiguous EntityManager configurations are found.
163         * @see javax.persistence.Persistence#createEntityManagerFactory(String)
164         */
165        public void setPersistenceUnitName(String persistenceUnitName) {
166                this.persistenceUnitName = persistenceUnitName;
167        }
168
169        @Override
170        public String getPersistenceUnitName() {
171                return this.persistenceUnitName;
172        }
173
174        /**
175         * Specify JPA properties, to be passed into
176         * {@code Persistence.createEntityManagerFactory} (if any).
177         * <p>Can be populated with a String "value" (parsed via PropertiesEditor) or a
178         * "props" element in XML bean definitions.
179         * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
180         * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
181         */
182        public void setJpaProperties(Properties jpaProperties) {
183                CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
184        }
185
186        /**
187         * Specify JPA properties as a Map, to be passed into
188         * {@code Persistence.createEntityManagerFactory} (if any).
189         * <p>Can be populated with a "map" or "props" element in XML bean definitions.
190         * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
191         * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
192         */
193        public void setJpaPropertyMap(Map<String, ?> jpaProperties) {
194                if (jpaProperties != null) {
195                        this.jpaPropertyMap.putAll(jpaProperties);
196                }
197        }
198
199        /**
200         * Allow Map access to the JPA properties to be passed to the persistence
201         * provider, with the option to add or override specific entries.
202         * <p>Useful for specifying entries directly, for example via
203         * "jpaPropertyMap[myKey]".
204         */
205        public Map<String, Object> getJpaPropertyMap() {
206                return this.jpaPropertyMap;
207        }
208
209        /**
210         * Specify the (potentially vendor-specific) EntityManagerFactory interface
211         * that this EntityManagerFactory proxy is supposed to implement.
212         * <p>The default will be taken from the specific JpaVendorAdapter, if any,
213         * or set to the standard {@code javax.persistence.EntityManagerFactory}
214         * interface else.
215         * @see JpaVendorAdapter#getEntityManagerFactoryInterface()
216         */
217        public void setEntityManagerFactoryInterface(Class<? extends EntityManagerFactory> emfInterface) {
218                this.entityManagerFactoryInterface = emfInterface;
219        }
220
221        /**
222         * Specify the (potentially vendor-specific) EntityManager interface
223         * that this factory's EntityManagers are supposed to implement.
224         * <p>The default will be taken from the specific JpaVendorAdapter, if any,
225         * or set to the standard {@code javax.persistence.EntityManager}
226         * interface else.
227         * @see JpaVendorAdapter#getEntityManagerInterface()
228         * @see EntityManagerFactoryInfo#getEntityManagerInterface()
229         */
230        public void setEntityManagerInterface(Class<? extends EntityManager> emInterface) {
231                this.entityManagerInterface = emInterface;
232        }
233
234        @Override
235        public Class<? extends EntityManager> getEntityManagerInterface() {
236                return this.entityManagerInterface;
237        }
238
239        /**
240         * Specify the vendor-specific JpaDialect implementation to associate with
241         * this EntityManagerFactory. This will be exposed through the
242         * EntityManagerFactoryInfo interface, to be picked up as default dialect by
243         * accessors that intend to use JpaDialect functionality.
244         * @see EntityManagerFactoryInfo#getJpaDialect()
245         */
246        public void setJpaDialect(JpaDialect jpaDialect) {
247                this.jpaDialect = jpaDialect;
248        }
249
250        @Override
251        public JpaDialect getJpaDialect() {
252                return this.jpaDialect;
253        }
254
255        /**
256         * Specify the JpaVendorAdapter implementation for the desired JPA provider,
257         * if any. This will initialize appropriate defaults for the given provider,
258         * such as persistence provider class and JpaDialect, unless locally
259         * overridden in this FactoryBean.
260         */
261        public void setJpaVendorAdapter(JpaVendorAdapter jpaVendorAdapter) {
262                this.jpaVendorAdapter = jpaVendorAdapter;
263        }
264
265        /**
266         * Return the JpaVendorAdapter implementation for this EntityManagerFactory,
267         * or {@code null} if not known.
268         */
269        public JpaVendorAdapter getJpaVendorAdapter() {
270                return this.jpaVendorAdapter;
271        }
272
273        /**
274         * Specify an asynchronous executor for background bootstrapping,
275         * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
276         * <p>{@code EntityManagerFactory} initialization will then switch into background
277         * bootstrap mode, with a {@code EntityManagerFactory} proxy immediately returned for
278         * injection purposes instead of waiting for the JPA provider's bootstrapping to complete.
279         * However, note that the first actual call to a {@code EntityManagerFactory} method will
280         * then block until the JPA provider's bootstrapping completed, if not ready by then.
281         * For maximum benefit, make sure to avoid early {@code EntityManagerFactory} calls
282         * in init methods of related beans, even for metadata introspection purposes.
283         * @since 4.3
284         */
285        public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
286                this.bootstrapExecutor = bootstrapExecutor;
287        }
288
289        /**
290         * Return the asynchronous executor for background bootstrapping, if any.
291         * @since 4.3
292         */
293        public AsyncTaskExecutor getBootstrapExecutor() {
294                return this.bootstrapExecutor;
295        }
296
297        @Override
298        public void setBeanClassLoader(ClassLoader classLoader) {
299                this.beanClassLoader = classLoader;
300        }
301
302        @Override
303        public ClassLoader getBeanClassLoader() {
304                return this.beanClassLoader;
305        }
306
307        @Override
308        public void setBeanFactory(BeanFactory beanFactory) {
309                this.beanFactory = beanFactory;
310        }
311
312        @Override
313        public void setBeanName(String name) {
314                this.beanName = name;
315        }
316
317
318        @Override
319        public void afterPropertiesSet() throws PersistenceException {
320                JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter();
321                if (jpaVendorAdapter != null) {
322                        if (this.persistenceProvider == null) {
323                                this.persistenceProvider = jpaVendorAdapter.getPersistenceProvider();
324                        }
325                        PersistenceUnitInfo pui = getPersistenceUnitInfo();
326                        Map<String, ?> vendorPropertyMap = null;
327                        if (pui != null) {
328                                try {
329                                        vendorPropertyMap = jpaVendorAdapter.getJpaPropertyMap(pui);
330                                }
331                                catch (AbstractMethodError err) {
332                                        // Spring 4.3.13 getJpaPropertyMap(PersistenceUnitInfo) not implemented
333                                }
334                        }
335                        if (vendorPropertyMap == null) {
336                                vendorPropertyMap = jpaVendorAdapter.getJpaPropertyMap();
337                        }
338                        if (!CollectionUtils.isEmpty(vendorPropertyMap)) {
339                                for (Map.Entry<String, ?> entry : vendorPropertyMap.entrySet()) {
340                                        if (!this.jpaPropertyMap.containsKey(entry.getKey())) {
341                                                this.jpaPropertyMap.put(entry.getKey(), entry.getValue());
342                                        }
343                                }
344                        }
345                        if (this.entityManagerFactoryInterface == null) {
346                                this.entityManagerFactoryInterface = jpaVendorAdapter.getEntityManagerFactoryInterface();
347                                if (!ClassUtils.isVisible(this.entityManagerFactoryInterface, this.beanClassLoader)) {
348                                        this.entityManagerFactoryInterface = EntityManagerFactory.class;
349                                }
350                        }
351                        if (this.entityManagerInterface == null) {
352                                this.entityManagerInterface = jpaVendorAdapter.getEntityManagerInterface();
353                                if (!ClassUtils.isVisible(this.entityManagerInterface, this.beanClassLoader)) {
354                                        this.entityManagerInterface = EntityManager.class;
355                                }
356                        }
357                        if (this.jpaDialect == null) {
358                                this.jpaDialect = jpaVendorAdapter.getJpaDialect();
359                        }
360                }
361
362                if (this.bootstrapExecutor != null) {
363                        this.nativeEntityManagerFactoryFuture = this.bootstrapExecutor.submit(new Callable<EntityManagerFactory>() {
364                                @Override
365                                public EntityManagerFactory call() {
366                                        return buildNativeEntityManagerFactory();
367                                }
368                        });
369                }
370                else {
371                        this.nativeEntityManagerFactory = buildNativeEntityManagerFactory();
372                }
373
374                // Wrap the EntityManagerFactory in a factory implementing all its interfaces.
375                // This allows interception of createEntityManager methods to return an
376                // application-managed EntityManager proxy that automatically joins
377                // existing transactions.
378                this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
379        }
380
381        private EntityManagerFactory buildNativeEntityManagerFactory() {
382                EntityManagerFactory emf;
383                try {
384                        emf = createNativeEntityManagerFactory();
385                }
386                catch (PersistenceException ex) {
387                        if (ex.getClass() == PersistenceException.class) {
388                                // Plain PersistenceException wrapper for underlying exception?
389                                // Make sure the nested exception message is properly exposed,
390                                // along the lines of Spring's NestedRuntimeException.getMessage()
391                                Throwable cause = ex.getCause();
392                                if (cause != null) {
393                                        String message = ex.getMessage();
394                                        String causeString = cause.toString();
395                                        if (!message.endsWith(causeString)) {
396                                                throw new PersistenceException(message + "; nested exception is " + causeString, cause);
397                                        }
398                                }
399                        }
400                        throw ex;
401                }
402
403                if (emf == null) {
404                        throw new IllegalStateException(
405                                        "JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
406                }
407
408                JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter();
409                if (jpaVendorAdapter != null) {
410                        jpaVendorAdapter.postProcessEntityManagerFactory(emf);
411                }
412
413                if (logger.isInfoEnabled()) {
414                        logger.info("Initialized JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
415                }
416                return emf;
417        }
418
419        /**
420         * Create a proxy for the given {@link EntityManagerFactory}. We do this to be able to
421         * return a transaction-aware proxy for an application-managed {@link EntityManager}.
422         * @param emf the EntityManagerFactory as returned by the persistence provider,
423         * if initialized already
424         * @return the EntityManagerFactory proxy
425         */
426        protected EntityManagerFactory createEntityManagerFactoryProxy(EntityManagerFactory emf) {
427                Set<Class<?>> ifcs = new LinkedHashSet<Class<?>>();
428                Class<?> entityManagerFactoryInterface = this.entityManagerFactoryInterface;
429                if (entityManagerFactoryInterface != null) {
430                        ifcs.add(entityManagerFactoryInterface);
431                }
432                else if (emf != null) {
433                        ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader));
434                }
435                else {
436                        ifcs.add(EntityManagerFactory.class);
437                }
438                ifcs.add(EntityManagerFactoryInfo.class);
439
440                try {
441                        return (EntityManagerFactory) Proxy.newProxyInstance(this.beanClassLoader,
442                                        ClassUtils.toClassArray(ifcs), new ManagedEntityManagerFactoryInvocationHandler(this));
443                }
444                catch (IllegalArgumentException ex) {
445                        if (entityManagerFactoryInterface != null) {
446                                throw new IllegalStateException("EntityManagerFactory interface [" + entityManagerFactoryInterface +
447                                                "] seems to conflict with Spring's EntityManagerFactoryInfo mixin - consider resetting the "+
448                                                "'entityManagerFactoryInterface' property to plain [javax.persistence.EntityManagerFactory]", ex);
449                        }
450                        else {
451                                throw new IllegalStateException("Conflicting EntityManagerFactory interfaces - " +
452                                                "consider specifying the 'jpaVendorAdapter' or 'entityManagerFactoryInterface' property " +
453                                                "to select a specific EntityManagerFactory interface to proceed with", ex);
454                        }
455                }
456        }
457
458        /**
459         * Delegate an incoming invocation from the proxy, dispatching to EntityManagerFactoryInfo
460         * or the native EntityManagerFactory accordingly.
461         */
462        Object invokeProxyMethod(Method method, Object[] args) throws Throwable {
463                if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
464                        return method.invoke(this, args);
465                }
466                else if (method.getName().equals("createEntityManager") && args != null && args.length > 0 &&
467                                args[0] != null && args[0].getClass().isEnum() && "SYNCHRONIZED".equals(args[0].toString())) {
468                        // JPA 2.1's createEntityManager(SynchronizationType, Map)
469                        // Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
470                        EntityManager rawEntityManager = (args.length > 1 ?
471                                        getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
472                                        getNativeEntityManagerFactory().createEntityManager());
473                        return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
474                }
475
476                // Look for Query arguments, primarily JPA 2.1's addNamedQuery(String, Query)
477                if (args != null) {
478                        for (int i = 0; i < args.length; i++) {
479                                Object arg = args[i];
480                                if (arg instanceof Query && Proxy.isProxyClass(arg.getClass())) {
481                                        // Assumably a Spring-generated proxy from SharedEntityManagerCreator:
482                                        // since we're passing it back to the native EntityManagerFactory,
483                                        // let's unwrap it to the original Query object from the provider.
484                                        try {
485                                                args[i] = ((Query) arg).unwrap(null);
486                                        }
487                                        catch (RuntimeException ex) {
488                                                // Ignore - simply proceed with given Query object then
489                                        }
490                                }
491                        }
492                }
493
494                // Standard delegation to the native factory, just post-processing EntityManager return values
495                Object retVal = method.invoke(getNativeEntityManagerFactory(), args);
496                if (retVal instanceof EntityManager) {
497                        // Any other createEntityManager variant - expecting non-synchronized semantics
498                        EntityManager rawEntityManager = (EntityManager) retVal;
499                        retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, false);
500                }
501                return retVal;
502        }
503
504        /**
505         * Subclasses must implement this method to create the EntityManagerFactory
506         * that will be returned by the {@code getObject()} method.
507         * @return EntityManagerFactory instance returned by this FactoryBean
508         * @throws PersistenceException if the EntityManager cannot be created
509         */
510        protected abstract EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException;
511
512
513        /**
514         * Implementation of the PersistenceExceptionTranslator interface, as
515         * autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
516         * <p>Uses the dialect's conversion if possible; otherwise falls back to
517         * standard JPA exception conversion.
518         * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
519         * @see JpaDialect#translateExceptionIfPossible
520         * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible
521         */
522        @Override
523        public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
524                JpaDialect jpaDialect = getJpaDialect();
525                return (jpaDialect != null ? jpaDialect.translateExceptionIfPossible(ex) :
526                                EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex));
527        }
528
529        @Override
530        public EntityManagerFactory getNativeEntityManagerFactory() {
531                if (this.nativeEntityManagerFactory != null) {
532                        return this.nativeEntityManagerFactory;
533                }
534                else {
535                        try {
536                                return this.nativeEntityManagerFactoryFuture.get();
537                        }
538                        catch (InterruptedException ex) {
539                                Thread.currentThread().interrupt();
540                                throw new IllegalStateException("Interrupted during initialization of native EntityManagerFactory", ex);
541                        }
542                        catch (ExecutionException ex) {
543                                Throwable cause = ex.getCause();
544                                if (cause instanceof PersistenceException) {
545                                        // Rethrow a provider configuration exception (possibly with a nested cause) directly
546                                        throw (PersistenceException) cause;
547                                }
548                                throw new IllegalStateException("Failed to asynchronously initialize native EntityManagerFactory: " +
549                                                ex.getMessage(), cause);
550                        }
551                }
552        }
553
554        @Override
555        public PersistenceUnitInfo getPersistenceUnitInfo() {
556                return null;
557        }
558
559        @Override
560        public DataSource getDataSource() {
561                return null;
562        }
563
564
565        /**
566         * Return the singleton EntityManagerFactory.
567         */
568        @Override
569        public EntityManagerFactory getObject() {
570                return this.entityManagerFactory;
571        }
572
573        @Override
574        public Class<? extends EntityManagerFactory> getObjectType() {
575                return (this.entityManagerFactory != null ? this.entityManagerFactory.getClass() : EntityManagerFactory.class);
576        }
577
578        @Override
579        public boolean isSingleton() {
580                return true;
581        }
582
583
584        /**
585         * Close the EntityManagerFactory on bean factory shutdown.
586         */
587        @Override
588        public void destroy() {
589                if (logger.isInfoEnabled()) {
590                        logger.info("Closing JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
591                }
592                this.entityManagerFactory.close();
593        }
594
595
596        //---------------------------------------------------------------------
597        // Serialization support
598        //---------------------------------------------------------------------
599
600        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
601                throw new NotSerializableException("An EntityManagerFactoryBean itself is not deserializable - " +
602                                "just a SerializedEntityManagerFactoryBeanReference is");
603        }
604
605        protected Object writeReplace() throws ObjectStreamException {
606                if (this.beanFactory != null && this.beanName != null) {
607                        return new SerializedEntityManagerFactoryBeanReference(this.beanFactory, this.beanName);
608                }
609                else {
610                        throw new NotSerializableException("EntityManagerFactoryBean does not run within a BeanFactory");
611                }
612        }
613
614
615        /**
616         * Minimal bean reference to the surrounding AbstractEntityManagerFactoryBean.
617         * Resolved to the actual AbstractEntityManagerFactoryBean instance on deserialization.
618         */
619        @SuppressWarnings("serial")
620        private static class SerializedEntityManagerFactoryBeanReference implements Serializable {
621
622                private final BeanFactory beanFactory;
623
624                private final String lookupName;
625
626                public SerializedEntityManagerFactoryBeanReference(BeanFactory beanFactory, String beanName) {
627                        this.beanFactory = beanFactory;
628                        this.lookupName = BeanFactory.FACTORY_BEAN_PREFIX + beanName;
629                }
630
631                private Object readResolve() {
632                        return this.beanFactory.getBean(this.lookupName, AbstractEntityManagerFactoryBean.class);
633                }
634        }
635
636
637        /**
638         * Dynamic proxy invocation handler for an {@link EntityManagerFactory}, returning a
639         * proxy {@link EntityManager} (if necessary) from {@code createEntityManager} methods.
640         */
641        @SuppressWarnings("serial")
642        private static class ManagedEntityManagerFactoryInvocationHandler implements InvocationHandler, Serializable {
643
644                private final AbstractEntityManagerFactoryBean entityManagerFactoryBean;
645
646                public ManagedEntityManagerFactoryInvocationHandler(AbstractEntityManagerFactoryBean emfb) {
647                        this.entityManagerFactoryBean = emfb;
648                }
649
650                @Override
651                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
652                        try {
653                                if (method.getName().equals("equals")) {
654                                        // Only consider equal when proxies are identical.
655                                        return (proxy == args[0]);
656                                }
657                                else if (method.getName().equals("hashCode")) {
658                                        // Use hashCode of EntityManagerFactory proxy.
659                                        return System.identityHashCode(proxy);
660                                }
661                                else if (method.getName().equals("unwrap")) {
662                                        // Handle JPA 2.1 unwrap method - could be a proxy match.
663                                        Class<?> targetClass = (Class<?>) args[0];
664                                        if (targetClass == null) {
665                                                return this.entityManagerFactoryBean.getNativeEntityManagerFactory();
666                                        }
667                                        else if (targetClass.isInstance(proxy)) {
668                                                return proxy;
669                                        }
670                                }
671                                return this.entityManagerFactoryBean.invokeProxyMethod(method, args);
672                        }
673                        catch (InvocationTargetException ex) {
674                                throw ex.getTargetException();
675                        }
676                }
677        }
678
679}