diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java index 2c521230e7e..d565298e9e5 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.orm.jpa; +import java.io.NotSerializableException; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -37,6 +40,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; @@ -73,8 +79,8 @@ import org.springframework.util.CollectionUtils; * @see LocalContainerEntityManagerFactoryBean */ public abstract class AbstractEntityManagerFactoryBean implements - FactoryBean, BeanClassLoaderAware, InitializingBean, DisposableBean, - EntityManagerFactoryInfo, PersistenceExceptionTranslator { + FactoryBean, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, + InitializingBean, DisposableBean, EntityManagerFactoryInfo, PersistenceExceptionTranslator, Serializable { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @@ -95,9 +101,15 @@ public abstract class AbstractEntityManagerFactoryBean implements private ClassLoader beanClassLoader = getClass().getClassLoader(); + private BeanFactory beanFactory; + + private String beanName; + /** Raw EntityManagerFactory as returned by the PersistenceProvider */ public EntityManagerFactory nativeEntityManagerFactory; + private EntityManagerFactoryPlusOperations plusOperations; + private EntityManagerFactory entityManagerFactory; @@ -254,6 +266,14 @@ public abstract class AbstractEntityManagerFactoryBean implements return this.beanClassLoader; } + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public void setBeanName(String name) { + this.beanName = name; + } + public final void afterPropertiesSet() throws PersistenceException { if (this.jpaVendorAdapter != null) { @@ -317,14 +337,13 @@ public abstract class AbstractEntityManagerFactoryBean implements ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader)); } ifcs.add(EntityManagerFactoryInfo.class); - EntityManagerFactoryPlusOperations plusOperations = null; if (getJpaDialect() != null && getJpaDialect().supportsEntityManagerFactoryPlusOperations()) { - plusOperations = getJpaDialect().getEntityManagerFactoryPlusOperations(emf); + this.plusOperations = getJpaDialect().getEntityManagerFactoryPlusOperations(emf); ifcs.add(EntityManagerFactoryPlusOperations.class); } return (EntityManagerFactory) Proxy.newProxyInstance( this.beanClassLoader, ifcs.toArray(new Class[ifcs.size()]), - new ManagedEntityManagerFactoryInvocationHandler(emf, this, plusOperations)); + new ManagedEntityManagerFactoryInvocationHandler(this)); } /** @@ -390,25 +409,67 @@ public abstract class AbstractEntityManagerFactoryBean implements } + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + Object invokeProxyMethod(Method method, Object[] args) throws Throwable { + if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) { + return method.invoke(this, args); + } + else if (method.getDeclaringClass().equals(EntityManagerFactoryPlusOperations.class)) { + return method.invoke(this.plusOperations, args); + } + Object retVal = method.invoke(this.nativeEntityManagerFactory, args); + if (retVal instanceof EntityManager) { + EntityManager rawEntityManager = (EntityManager) retVal; + retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this); + } + return retVal; + } + + protected Object writeReplace() throws ObjectStreamException { + if (this.beanFactory != null && this.beanName != null) { + return new SerializedEntityManagerFactoryBeanReference(this.beanFactory, this.beanName); + } + else { + throw new NotSerializableException("EntityManagerFactoryBean does not run within a BeanFactory"); + } + } + + + /** + * Minimal bean reference to the surrounding AbstractEntityManagerFactoryBean. + * Resolved to the actual AbstractEntityManagerFactoryBean instance on deserialization. + */ + private static class SerializedEntityManagerFactoryBeanReference implements Serializable { + + private final BeanFactory beanFactory; + + private final String lookupName; + + public SerializedEntityManagerFactoryBeanReference(BeanFactory beanFactory, String beanName) { + this.beanFactory = beanFactory; + this.lookupName = BeanFactory.FACTORY_BEAN_PREFIX + beanName; + } + + private Object readResolve() { + return this.beanFactory.getBean(this.lookupName, AbstractEntityManagerFactoryBean.class); + } + } + + /** * Dynamic proxy invocation handler proxying an EntityManagerFactory to * return a proxy EntityManager if necessary from createEntityManager() * methods. */ - private static class ManagedEntityManagerFactoryInvocationHandler implements InvocationHandler { + private static class ManagedEntityManagerFactoryInvocationHandler implements InvocationHandler, Serializable { - private final EntityManagerFactory targetEntityManagerFactory; + private final AbstractEntityManagerFactoryBean entityManagerFactoryBean; - private final EntityManagerFactoryInfo entityManagerFactoryInfo; - - private final EntityManagerFactoryPlusOperations entityManagerFactoryPlusOperations; - - public ManagedEntityManagerFactoryInvocationHandler(EntityManagerFactory targetEmf, - EntityManagerFactoryInfo emfInfo, EntityManagerFactoryPlusOperations entityManagerFactoryPlusOperations) { - - this.targetEntityManagerFactory = targetEmf; - this.entityManagerFactoryInfo = emfInfo; - this.entityManagerFactoryPlusOperations = entityManagerFactoryPlusOperations; + public ManagedEntityManagerFactoryInvocationHandler(AbstractEntityManagerFactoryBean emfb) { + this.entityManagerFactoryBean = emfb; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { @@ -421,20 +482,7 @@ public abstract class AbstractEntityManagerFactoryBean implements // Use hashCode of EntityManagerFactory proxy. return System.identityHashCode(proxy); } - else if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) { - return method.invoke(this.entityManagerFactoryInfo, args); - } - else if (method.getDeclaringClass().equals(EntityManagerFactoryPlusOperations.class)) { - return method.invoke(this.entityManagerFactoryPlusOperations, args); - } - - Object retVal = method.invoke(this.targetEntityManagerFactory, args); - if (retVal instanceof EntityManager) { - EntityManager rawEntityManager = (EntityManager) retVal; - retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager( - rawEntityManager, this.entityManagerFactoryInfo); - } - return retVal; + return this.entityManagerFactoryBean.invokeProxyMethod(method, args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java index 19277494785..c96a01ecc6a 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java @@ -16,6 +16,9 @@ package org.springframework.orm.jpa; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -123,7 +126,7 @@ public abstract class SharedEntityManagerCreator { * transactional EntityManager, if any; else, it will fall back * to a newly created EntityManager per operation. */ - private static class SharedEntityManagerInvocationHandler implements InvocationHandler { + private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable { private final Log logger = LogFactory.getLog(getClass()); @@ -131,11 +134,15 @@ public abstract class SharedEntityManagerCreator { private final Map properties; - private final ClassLoader proxyClassLoader; + private transient volatile ClassLoader proxyClassLoader; public SharedEntityManagerInvocationHandler(EntityManagerFactory target, Map properties) { this.targetFactory = target; this.properties = properties; + initProxyClassLoader(); + } + + private void initProxyClassLoader() { if (this.targetFactory instanceof EntityManagerFactoryInfo) { this.proxyClassLoader = ((EntityManagerFactoryInfo) this.targetFactory).getBeanClassLoader(); } @@ -254,6 +261,13 @@ public abstract class SharedEntityManagerCreator { } } } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization, just initialize state after deserialization. + ois.defaultReadObject(); + // Initialize transient fields. + initProxyClassLoader(); + } } diff --git a/org.springframework.orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java b/org.springframework.orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java index b0644dc1cdc..479c52d89eb 100644 --- a/org.springframework.orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java +++ b/org.springframework.orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,14 @@ import javax.persistence.FlushModeType; import javax.persistence.NoResultException; import javax.persistence.Query; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.orm.jpa.domain.DriversLicense; import org.springframework.orm.jpa.domain.Person; import org.springframework.test.annotation.ExpectedException; import org.springframework.test.annotation.NotTransactional; import org.springframework.test.annotation.Repeat; import org.springframework.test.annotation.Timed; +import org.springframework.util.SerializationTestUtils; /** * Integration tests for LocalContainerEntityManagerFactoryBean. @@ -252,4 +254,12 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests } } + public void testCanSerializeProxies() throws Exception { + // just necessary because of AbstractJpaTests magically cloning the BeanFactory + ((DefaultListableBeanFactory) getApplicationContext().getBeanFactory()).setSerializationId("emf-it"); + + assertNotNull(SerializationTestUtils.serializeAndDeserialize(entityManagerFactory)); + assertNotNull(SerializationTestUtils.serializeAndDeserialize(sharedEntityManager)); + } + } diff --git a/org.springframework.orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java b/org.springframework.orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java index dbafff74ab1..89c41cdc24c 100644 --- a/org.springframework.orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java +++ b/org.springframework.orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package org.springframework.orm.jpa; import java.util.Map; import java.util.Properties; - import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; @@ -30,12 +29,14 @@ import javax.persistence.spi.PersistenceUnitTransactionType; import org.easymock.MockControl; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.dao.DataAccessException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.util.SerializationTestUtils; /** * @author Rod Johnson @@ -74,6 +75,13 @@ public class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityM assertNotSame("EMF must be proxied", mockEmf, emf); assertTrue(emf.equals(emf)); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setSerializationId("emf-bf"); + bf.registerSingleton("emf", cefb); + cefb.setBeanFactory(bf); + cefb.setBeanName("emf"); + assertNotNull(SerializationTestUtils.serializeAndDeserialize(emf)); } public void testApplicationManagedEntityManagerWithoutTransaction() throws Exception { diff --git a/org.springframework.orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java b/org.springframework.orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java index 762570dab6d..7f40fb0db91 100644 --- a/org.springframework.orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java +++ b/org.springframework.orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ import org.springframework.util.SerializationTestUtils; */ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanTests { - public void testPrivatePersistenceContextField() { + public void testPrivatePersistenceContextField() throws Exception { GenericApplicationContext gac = new GenericApplicationContext(); gac.getDefaultListableBeanFactory().registerSingleton("entityManagerFactory", mockEmf); gac.registerBeanDefinition("annotationProcessor", @@ -73,6 +73,9 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT "&" + FactoryBeanWithPersistenceContextField.class.getName()); assertNotNull(bean.em); assertNotNull(bean2.em); + + assertNotNull(SerializationTestUtils.serializeAndDeserialize(bean.em)); + assertNotNull(SerializationTestUtils.serializeAndDeserialize(bean2.em)); } public void testPrivateVendorSpecificPersistenceContextField() { @@ -111,8 +114,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT public void testPublicExtendedPersistenceContextSetterWithSerialization() throws Exception { DummyInvocationHandler ih = new DummyInvocationHandler(); - Object mockEm = (EntityManager) Proxy.newProxyInstance( - getClass().getClassLoader(), new Class[] {EntityManager.class}, ih); + Object mockEm = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {EntityManager.class}, ih); mockEmf.createEntityManager(); emfMc.setReturnValue(mockEm, 1); emfMc.replay(); @@ -141,7 +143,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT } public void testPublicExtendedPersistenceContextSetterWithEntityManagerInfoAndSerialization() throws Exception { - Object mockEm = (EntityManager) Proxy.newProxyInstance( + Object mockEm = Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] {EntityManager.class}, new DummyInvocationHandler()); MockControl emfMc = MockControl.createControl(EntityManagerFactoryWithInfo.class); EntityManagerFactoryWithInfo mockEmf = (EntityManagerFactoryWithInfo) emfMc.getMock(); @@ -175,7 +177,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT } public void testPublicExtendedPersistenceContextSetterWithOverriding() { - EntityManager mockEm2 = (EntityManager) MockControl.createControl(EntityManager.class).getMock(); + EntityManager mockEm2 = MockControl.createControl(EntityManager.class).getMock(); GenericApplicationContext gac = new GenericApplicationContext(); gac.getDefaultListableBeanFactory().registerSingleton("entityManagerFactory", mockEmf);