diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/AbstractJDBCSavepointManager.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/AbstractJDBCSavepointManager.java index 240106015..847419712 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/AbstractJDBCSavepointManager.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/AbstractJDBCSavepointManager.java @@ -18,6 +18,9 @@ */ package org.apache.openjpa.jdbc.kernel; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; import java.sql.Connection; import java.util.Collection; @@ -134,5 +137,10 @@ public abstract class AbstractJDBCSavepointManager AbstractJDBCSavepointManager.this.setDataStore(this); super.save(states); } + + private void writeObject(ObjectOutputStream out) + throws IOException { + throw new NotSerializableException(); + } } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/EmbedFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/EmbedFieldStrategy.java index 06b0c342a..e792d1180 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/EmbedFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/EmbedFieldStrategy.java @@ -582,8 +582,8 @@ public class EmbedFieldStrategy return _owner; } - public ValueMetaData getOwnerMetaData() { - return _vmd; + public int getOwnerIndex() { + return _vmd.getFieldMetaData().getIndex(); } public boolean isEmbedded() { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java index e57b4acae..aa45abee0 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java @@ -245,6 +245,36 @@ public class PCEnhancer { + cls.getName().replace('.', '$') + "$pcsubclass"; } + /** + * Whether or not className is the name for a + * dynamically-created persistence-capable subclass. + * + * @since 1.1.0 + */ + public static boolean isPCSubclassName(String className) { + return className.startsWith(Strings.getPackageName(PCEnhancer.class)) + && className.endsWith("$pcsubclass"); + } + + /** + * If className is a dynamically-created persistence-capable + * subclass name, returns the name of the class that it subclasses. + * Otherwise, returns className. + * + * @since 1.1.0 + */ + public static String toManagedTypeName(String className) { + if (isPCSubclassName(className)) { + className = className.substring( + Strings.getPackageName(PCEnhancer.class).length() + 1); + className = className.substring(0, className.lastIndexOf("$")); + // this is not correct for nested PCs + className = className.replace('$', '.'); + } + + return className; + } + /** * Constructor. Supply configuration, type, and metadata. */ @@ -2718,6 +2748,9 @@ public class PCEnhancer { return; if (getCreateSubclass()) { + // ##### what should happen if a type is Externalizable? It looks + // ##### like Externalizable classes will not be serialized as PCs + // ##### based on this logic. if (!Externalizable.class.isAssignableFrom( _meta.getDescribedType())) addSubclassSerializationCode(); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCRegistry.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCRegistry.java index 308669890..d69efafec 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCRegistry.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCRegistry.java @@ -121,6 +121,17 @@ public class PCRegistry { return (meta.pc == null) ? null : meta.pc.pcNewInstance(sm, oid, clear); } + /** + * Return the persistence-capable type for type. This might + * be a generated subclass of type. + * + * @since 1.1.0 + */ + public static Class getPCType(Class type) { + Meta meta = getMeta(type); + return (meta.pc == null) ? null : meta.pc.getClass(); + } + /** * Create a new identity object for the given * PersistenceCapable class. diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/ReflectingPersistenceCapable.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/ReflectingPersistenceCapable.java index 9e2502050..869c5e81f 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/ReflectingPersistenceCapable.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/ReflectingPersistenceCapable.java @@ -18,17 +18,23 @@ */ package org.apache.openjpa.enhance; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; -import org.apache.openjpa.meta.ClassMetaData; -import org.apache.openjpa.meta.JavaTypes; -import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.conf.OpenJPAConfiguration; +import org.apache.openjpa.kernel.OpenJPAStateManager; +import org.apache.openjpa.kernel.StateManagerImpl; +import org.apache.openjpa.meta.ClassMetaData; +import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.util.ApplicationIds; +import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.ObjectId; -import org.apache.openjpa.kernel.StateManagerImpl; /** * Implementation of the {@link PersistenceCapable} interface that can handle @@ -38,12 +44,19 @@ import org.apache.openjpa.kernel.StateManagerImpl; * @since 1.0.0 */ public class ReflectingPersistenceCapable - implements PersistenceCapable, ManagedInstanceProvider { + implements PersistenceCapable, ManagedInstanceProvider, Serializable { private Object o; private StateManager sm; - private PersistenceCapable pcSubclassInstance; - private ClassMetaData meta; + + // this will be reconstituted in readObject() + private transient PersistenceCapable pcSubclassInstance; + + // this will reconstituted by a call to pcReplaceStateManager() by the + // instance that has a reference to the deserialized data + private transient ClassMetaData meta; + + private boolean serializationUserVisible = true; public ReflectingPersistenceCapable(Object o, OpenJPAConfiguration conf) { this.o = o; @@ -70,6 +83,8 @@ public class ReflectingPersistenceCapable public void pcReplaceStateManager(StateManager sm) { this.sm = sm; + if (meta == null && sm instanceof OpenJPAStateManager) + meta = ((OpenJPAStateManager) sm).getMetaData(); } public void pcProvideField(int i) { @@ -169,6 +184,10 @@ public class ReflectingPersistenceCapable } public void pcCopyFields(Object fromObject, int[] fieldIndices) { + if (fromObject instanceof ReflectingPersistenceCapable) + fromObject = ((ReflectingPersistenceCapable) fromObject) + .getManagedInstance(); + for(int i = 0; i < fieldIndices.length; i++) pcCopyField(fromObject, fieldIndices[i]); } @@ -305,21 +324,23 @@ public class ReflectingPersistenceCapable // ##### we can implement this if a state field has been set } + public void pcSetSerializationUserVisible(boolean userVisible) { + serializationUserVisible = userVisible; + } + + public boolean pcIsSerializationUserVisible() { + return serializationUserVisible; + } + public Object getManagedInstance() { return o; } private Object getValue(int i, Object o) { if (meta.getAccessType() == ClassMetaData.ACCESS_PROPERTY) { - if (!meta.isIntercepting()) { - Method meth = Reflection.findGetter(meta.getDescribedType(), - meta.getField(i).getName(), true); - return Reflection.get(o, meth); - } else { - Field field = Reflection.findField(meta.getDescribedType(), - toFieldName(i), true); - return Reflection.get(o, field); - } + Field field = Reflection.findField(meta.getDescribedType(), + toFieldName(i), true); + return Reflection.get(o, field); } else { Field field = (Field) meta.getField(i).getBackingMember(); return Reflection.get(o, field); @@ -350,4 +371,17 @@ public class ReflectingPersistenceCapable Reflection.set(o, field, val); } } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(meta.getDescribedType()); + } + + private void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException { + in.defaultReadObject(); + Class type = (Class) in.readObject(); + pcSubclassInstance = PCRegistry.newInstance(type, null, false); + ImplHelper.registerPersistenceCapable(this); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java index 71e3cb819..66bdbb219 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.event; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -44,7 +45,7 @@ import org.apache.openjpa.meta.MetaDataDefaults; * @nojavadoc */ public class LifecycleEventManager - implements CallbackModes { + implements CallbackModes, Serializable { private static final Exception[] EMPTY_EXCEPTIONS = new Exception[0]; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java index 6d15b736a..3544d5111 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java @@ -19,16 +19,16 @@ package org.apache.openjpa.kernel; import java.io.ObjectStreamException; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.Map; -import java.util.Properties; import java.util.LinkedList; import java.util.List; -import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.Properties; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.Transaction; @@ -41,23 +41,24 @@ import org.apache.openjpa.datacache.DataCacheStoreManager; import org.apache.openjpa.ee.ManagedRuntime; import org.apache.openjpa.enhance.PCRegistry; import org.apache.openjpa.enhance.PersistenceCapable; -import org.apache.openjpa.event.RemoteCommitEventManager; import org.apache.openjpa.event.BrokerFactoryEvent; +import org.apache.openjpa.event.RemoteCommitEventManager; import org.apache.openjpa.lib.conf.Configuration; +import org.apache.openjpa.lib.conf.Configurations; import org.apache.openjpa.lib.log.Log; import org.apache.openjpa.lib.util.J2DoPrivHelper; +import org.apache.openjpa.lib.util.JavaVersions; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.ReferenceHashSet; -import org.apache.openjpa.lib.util.JavaVersions; import org.apache.openjpa.lib.util.concurrent.ConcurrentHashMap; import org.apache.openjpa.lib.util.concurrent.ConcurrentReferenceHashSet; import org.apache.openjpa.lib.util.concurrent.ReentrantLock; import org.apache.openjpa.meta.MetaDataRepository; import org.apache.openjpa.util.GeneralException; +import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InvalidStateException; import org.apache.openjpa.util.OpenJPAException; import org.apache.openjpa.util.UserException; -import org.apache.openjpa.util.InternalException; /** * Abstract implementation of the {@link BrokerFactory} @@ -114,11 +115,12 @@ public abstract class AbstractBrokerFactory /** * Return an internal factory pool key for the given configuration. - * We use the conf properties as given by the user because that is what's - * passed to {@link #getPooledFactory} when looking for an existing factory. */ - private static Map toPoolKey(OpenJPAConfiguration conf) { - return conf.toProperties(false); + private static Object toPoolKey(OpenJPAConfiguration conf) { + if (conf.getId() != null) + return conf.getId(); + else + return conf.toProperties(false); } /** @@ -126,7 +128,18 @@ public abstract class AbstractBrokerFactory * if none. */ protected static AbstractBrokerFactory getPooledFactory(Map map) { - return (AbstractBrokerFactory) _pool.get(map); + Object key = Configurations.getProperty("Id", map); + if (key == null) + key = map; + return getPooledFactoryForKey(key); + } + + /** + * Return the pooled factory matching the given key, or null + * if none. The key must be of the form created by {@link #getPoolKey}. + */ + public static AbstractBrokerFactory getPooledFactoryForKey(Object key) { + return (AbstractBrokerFactory) _pool.get(key); } /** @@ -174,32 +187,9 @@ public abstract class AbstractBrokerFactory if (findExisting) broker = findBroker(user, pass, managed); if (broker == null) { - // decorate the store manager for data caching and custom - // result object providers; always make sure it's a delegating - // store manager, because it's easier for users to deal with - // that way - StoreManager sm = newStoreManager(); - DelegatingStoreManager dsm = null; - if (_conf.getDataCacheManagerInstance().getSystemDataCache() - != null) - dsm = new DataCacheStoreManager(sm); - dsm = new ROPStoreManager((dsm == null) ? sm : dsm); - broker = newBrokerImpl(user, pass); - broker.initialize(this, dsm, managed, connRetainMode); - addListeners(broker); - - // if we're using remote events, register the event manager so - // that it can broadcast commit notifications from the broker - RemoteCommitEventManager remote = _conf. - getRemoteCommitEventManager(); - if (remote.areRemoteEventsEnabled()) - broker.addTransactionListener(remote); - - loadPersistentTypes(broker.getClassLoader()); + initializeBroker(managed, connRetainMode, broker, false); } - _brokers.add(broker); - _conf.setReadOnly(Configuration.INIT_STATE_FROZEN); return broker; } catch (OpenJPAException ke) { throw ke; @@ -208,6 +198,39 @@ public abstract class AbstractBrokerFactory } } + void initializeBroker(boolean managed, int connRetainMode, + BrokerImpl broker, boolean fromDeserialization) { + assertOpen(); + makeReadOnly(); + + // decorate the store manager for data caching and custom + // result object providers; always make sure it's a delegating + // store manager, because it's easier for users to deal with + // that way + StoreManager sm = newStoreManager(); + DelegatingStoreManager dsm = null; + if (_conf.getDataCacheManagerInstance().getSystemDataCache() + != null) + dsm = new DataCacheStoreManager(sm); + dsm = new ROPStoreManager((dsm == null) ? sm : dsm); + + broker.initialize(this, dsm, managed, connRetainMode, + fromDeserialization); + if (!fromDeserialization) + addListeners(broker); + + // if we're using remote events, register the event manager so + // that it can broadcast commit notifications from the broker + RemoteCommitEventManager remote = _conf. + getRemoteCommitEventManager(); + if (remote.areRemoteEventsEnabled()) + broker.addTransactionListener(remote); + + loadPersistentTypes(broker.getClassLoader()); + _brokers.add(broker); + _conf.setReadOnly(Configuration.INIT_STATE_FROZEN); + } + /** * Add factory-registered lifecycle listeners to the broker. */ @@ -374,10 +397,10 @@ public abstract class AbstractBrokerFactory assertNoActiveTransaction(); // remove from factory pool - Map map = toPoolKey(_conf); + Object key = toPoolKey(_conf); synchronized (_pool) { - if (_pool.get(map) == this) - _pool.remove(map); + if (_pool.get(key) == this) + _pool.remove(key); } // close all brokers @@ -756,6 +779,15 @@ public abstract class AbstractBrokerFactory return Collections.unmodifiableCollection(_brokers); } + /** + * @return a key that can be used to obtain this broker factory from the + * pool at a later time. + * @since 1.1.0 + */ + public Object getPoolKey() { + return toPoolKey(getConfiguration()); + } + /** * Simple synchronization listener to remove completed transactions * from our cache. diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java index 2e02262b0..17ac0e9da 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java @@ -18,6 +18,9 @@ */ package org.apache.openjpa.kernel; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Modifier; import java.security.AccessController; @@ -91,7 +94,7 @@ import org.apache.openjpa.util.UserException; * @author Abe White */ public class BrokerImpl - implements Broker, FindCallbacks, Cloneable { + implements Broker, FindCallbacks, Cloneable, Serializable { /** * Incremental flush. @@ -132,30 +135,35 @@ public class BrokerImpl private static final int FLAG_RETAINED_CONN = 2 << 10; private static final int FLAG_TRANS_ENDING = 2 << 11; + private static final Object[] EMPTY_OBJECTS = new Object[0]; + private static final Localizer _loc = Localizer.forPackage(BrokerImpl.class); // the store manager in use; this may be a decorator such as a // data cache store manager around the native store manager - private DelegatingStoreManager _store = null; + private transient DelegatingStoreManager _store = null; - // ref to producing factory and configuration - private AbstractBrokerFactory _factory = null; - private OpenJPAConfiguration _conf = null; - private Compatibility _compat = null; private FetchConfiguration _fc = null; - private Log _log = null; private String _user = null; private String _pass = null; - private ManagedRuntime _runtime = null; - private LockManager _lm = null; - private InverseManager _im = null; - private ReentrantLock _lock = null; - private OpCallbacks _call = null; - private RuntimeExceptionTranslator _extrans = null; + + // these must be rebuilt by the facade layer during its deserialization + private transient Log _log = null; + private transient Compatibility _compat = null; + private transient ManagedRuntime _runtime = null; + private transient LockManager _lm = null; + private transient InverseManager _im = null; + private transient ReentrantLock _lock = null; + private transient OpCallbacks _call = null; + private transient RuntimeExceptionTranslator _extrans = null; + + // ref to producing factory and configuration + private transient AbstractBrokerFactory _factory = null; + private transient OpenJPAConfiguration _conf = null; // cache class loader associated with the broker - private ClassLoader _loader = null; + private transient ClassLoader _loader = null; // user state private Synchronization _sync = null; @@ -167,8 +175,11 @@ public class BrokerImpl private Set _transAdditions = null; private Set _derefCache = null; private Set _derefAdditions = null; - private Map _loading = null; - private Set _operating = null; + + // these are used for method-internal state only + private transient Map _loading = null; + private transient Set _operating = null; + private Set _persistedClss = null; private Set _updatedClss = null; private Set _deletedClss = null; @@ -179,14 +190,15 @@ public class BrokerImpl // (the first uses the transactional cache) private Set _savepointCache = null; private LinkedMap _savepoints = null; - private SavepointManager _spm = null; + private transient SavepointManager _spm = null; // track open queries and extents so we can free their resources on close - private ReferenceHashSet _queries = null; - private ReferenceHashSet _extents = null; + private transient ReferenceHashSet _queries = null; + private transient ReferenceHashSet _extents = null; - // track operation stack depth - private int _operationCount = 0; + // track operation stack depth. Transient because operations cannot + // span serialization. + private transient int _operationCount = 0; // options private boolean _nontransRead = false; @@ -210,8 +222,13 @@ public class BrokerImpl // status private int _flags = 0; - private boolean _closed = false; - private RuntimeException _closedException = null; + + // this is not in status because it should not be serialized + private transient boolean _isSerializing = false; + + // transient because closed brokers can't be serialized + private transient boolean _closed = false; + private transient RuntimeException _closedException = null; // event managers private TransactionEventManager _transEventManager = null; @@ -219,8 +236,7 @@ public class BrokerImpl private LifecycleEventManager _lifeEventManager = null; private int _lifeCallbackMode = 0; - private boolean _initializeWasInvoked = false; - private static final Object[] EMPTY_OBJECTS = new Object[0]; + private transient boolean _initializeWasInvoked = false; /** * Set the persistence manager's authentication. This is the first @@ -245,17 +261,22 @@ public class BrokerImpl * handle interaction with the data store * @param managed the transaction mode * @param connMode the connection retain mode + * @param fromDeserialization whether this call happened because of a + * deserialization or creation of a new BrokerImpl. */ - public void initialize(AbstractBrokerFactory factory, - DelegatingStoreManager sm, boolean managed, int connMode) { + void initialize(AbstractBrokerFactory factory, + DelegatingStoreManager sm, boolean managed, int connMode, + boolean fromDeserialization) { _initializeWasInvoked = true; _loader = (ClassLoader) AccessController.doPrivileged( J2DoPrivHelper.getContextClassLoaderAction()); - _conf = factory.getConfiguration(); + if (!fromDeserialization) + _conf = factory.getConfiguration(); _compat = _conf.getCompatibilityInstance(); _factory = factory; _log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); - _cache = new ManagedCache(); + if (!fromDeserialization) + _cache = new ManagedCache(this); initializeOperatingSet(); _connRetainMode = connMode; _managed = managed; @@ -264,15 +285,17 @@ public class BrokerImpl else _runtime = new LocalManagedRuntime(this); - _lifeEventManager = new LifecycleEventManager(); - _transEventManager = new TransactionEventManager(); - int cmode = _conf.getMetaDataRepositoryInstance(). - getMetaDataFactory().getDefaults().getCallbackMode(); - setLifecycleListenerCallbackMode(cmode); - setTransactionListenerCallbackMode(cmode); + if (!fromDeserialization) { + _lifeEventManager = new LifecycleEventManager(); + _transEventManager = new TransactionEventManager(); + int cmode = _conf.getMetaDataRepositoryInstance(). + getMetaDataFactory().getDefaults().getCallbackMode(); + setLifecycleListenerCallbackMode(cmode); + setTransactionListenerCallbackMode(cmode); - // setup default options - _factory.configureBroker(this); + // setup default options + _factory.configureBroker(this); + } // make sure to do this after configuring broker so that store manager // can look to broker configuration; we set both store and lock managers @@ -287,8 +310,10 @@ public class BrokerImpl if (_connRetainMode == CONN_RETAIN_ALWAYS) retainConnection(); - _fc = _store.newFetchConfiguration(); - _fc.setContext(this); + if (!fromDeserialization) { + _fc = _store.newFetchConfiguration(); + _fc.setContext(this); + } // synch with the global transaction in progress, if any if (_factory.syncWithManagedTransaction(this, false)) @@ -749,7 +774,7 @@ public class BrokerImpl // cached instance? StateManagerImpl sm = getStateManagerImplById(oid, - (flags & OID_ALLOW_NEW) != 0 || (_flags & FLAG_FLUSHED) != 0); + (flags & OID_ALLOW_NEW) != 0 || hasFlushed()); if (sm != null) { if (!requiresLoad(sm, true, fetch, edata, flags)) return call.processReturn(oid, sm); @@ -911,7 +936,7 @@ public class BrokerImpl // if we don't have a cached instance or it is not transactional // and is hollow or we need to validate, load it sm = getStateManagerImplById(oid, (flags & OID_ALLOW_NEW) != 0 - || (_flags & FLAG_FLUSHED) != 0); + || hasFlushed()); initialized = sm != null; if (!initialized) sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0); @@ -986,6 +1011,10 @@ public class BrokerImpl } } + private boolean hasFlushed() { + return (_flags & FLAG_FLUSHED) != 0; + } + /** * Return whether the given instance needs loading before being returned * to the user. @@ -1457,8 +1486,7 @@ public class BrokerImpl if (_savepoints != null && _savepoints.containsKey(name)) throw new UserException(_loc.get("savepoint-exists", name)); - if ((_flags & FLAG_FLUSHED) != 0 - && !_spm.supportsIncrementalFlush()) + if (hasFlushed() && !_spm.supportsIncrementalFlush()) throw new UnsupportedException(_loc.get ("savepoint-flush-not-supported")); @@ -2359,8 +2387,7 @@ public class BrokerImpl // an embedded field; notify the owner that the value has // changed by becoming independently persistent - sm.getOwner().dirty(sm.getOwnerMetaData(). - getFieldMetaData().getIndex()); + sm.getOwner().dirty(sm.getOwnerIndex()); _cache.persist(sm); pc = sm.getPersistenceCapable(); } else { @@ -4357,278 +4384,69 @@ public class BrokerImpl return (sm == null) ? null : sm.getManagedInstance(); } + private void writeObject(ObjectOutputStream out) throws IOException { + assertOpen(); + lock(); + try { + if (isActive()) { + if (!getOptimistic()) + throw new InvalidStateException( + _loc.get("cant-serialize-pessimistic-broker")); + if (hasFlushed()) + throw new InvalidStateException( + _loc.get("cant-serialize-flushed-broker")); + if (hasConnection()) + throw new InvalidStateException( + _loc.get("cant-serialize-connected-broker")); + } + + try { + _isSerializing = true; + out.writeObject(_factory.getPoolKey()); + out.defaultWriteObject(); + } finally { + _isSerializing = false; + } + } finally { + unlock(); + } + } + + private void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException { + Object factoryKey = in.readObject(); + AbstractBrokerFactory factory = + AbstractBrokerFactory.getPooledFactoryForKey(factoryKey); + + // this needs to happen before defaultReadObject so that it's + // available for calls to broker.getConfiguration() during + // StateManager deserialization + _conf = factory.getConfiguration(); + + in.defaultReadObject(); + factory.initializeBroker(_managed, _connRetainMode, this, true); + + // re-initialize the lock if needed. + setMultithreaded(_multithreaded); + + if (isActive() && _runtime instanceof LocalManagedRuntime) + ((LocalManagedRuntime) _runtime).begin(); + } + /** - * Cache of managed objects. + * Whether or not this broker is in the midst of being serialized. + * + * @since 1.1.0 */ - private class ManagedCache { - - private Map _main; // oid -> sm - private Map _conflicts = null; // conflict oid -> new sm - private Map _news = null; // tmp id -> new sm - private Collection _embeds = null; // embedded/non-persistent sms - private Collection _untracked = null; // hard refs to untracked sms - - /** - * Constructor; supply primary cache map. - */ - private ManagedCache() { - _main = newManagedObjectCache(); - } - - /** - * Return the instance for the given oid, optionally allowing - * new instances. - */ - public StateManagerImpl getById(Object oid, boolean allowNew) { - if (oid == null) - return null; - - // check main cache for oid - StateManagerImpl sm = (StateManagerImpl) _main.get(oid); - StateManagerImpl sm2; - if (sm != null) { - // if it's a new instance, we know it's the only match, because - // other pers instances override new instances in _cache - if (sm.isNew()) - return (allowNew) ? sm : null; - if (!allowNew || !sm.isDeleted()) - return sm; - - // sm is deleted; check conflict cache - if (_conflicts != null) { - sm2 = (StateManagerImpl) _conflicts.get(oid); - if (sm2 != null) - return sm2; - } - } - - // at this point sm is null or deleted; check the new cache for - // any matches. this allows us to match app id objects to new - // instances without permanant oids - if (allowNew && _news != null && !_news.isEmpty()) { - sm2 = (StateManagerImpl) _news.get(oid); - if (sm2 != null) - return sm2; - } - return sm; - } - - /** - * Call this method when a new state manager initializes itself. - */ - public void add(StateManagerImpl sm) { - if (!sm.isIntercepting()) { - if (_untracked == null) - _untracked = new HashSet(); - _untracked.add(sm); - } - - if (!sm.isPersistent() || sm.isEmbedded()) { - if (_embeds == null) - _embeds = new ReferenceHashSet(ReferenceHashSet.WEAK); - _embeds.add(sm); - return; - } - - // initializing new instance; put in new cache because won't have - // permanent oid yet - if (sm.isNew()) { - if (_news == null) - _news = new HashMap(); - _news.put(sm.getId(), sm); - return; - } - - // initializing persistent instance; put in main cache - StateManagerImpl orig = (StateManagerImpl) _main.put - (sm.getObjectId(), sm); - if (orig != null) { - _main.put(sm.getObjectId(), orig); - throw new UserException(_loc.get("dup-load", - sm.getObjectId(), Exceptions.toString - (orig.getManagedInstance()))). - setFailedObject(sm.getManagedInstance()); - } - } - - /** - * Remove the given state manager from the cache when it transitions - * to transient. - */ - public void remove(Object id, StateManagerImpl sm) { - // if it has a permanent oid, remove from main / conflict cache, - // else remove from embedded/nontrans cache, and if not there - // remove from new cache - Object orig; - if (sm.getObjectId() != null) { - orig = _main.remove(id); - if (orig != sm) { - if (orig != null) - _main.put(id, orig); // put back - if (_conflicts != null) { - orig = _conflicts.remove(id); - if (orig != null && orig != sm) - _conflicts.put(id, orig); // put back - } - } - } else if ((_embeds == null || !_embeds.remove(sm)) - && _news != null) { - orig = _news.remove(id); - if (orig != null && orig != sm) - _news.put(id, orig); // put back - } - - if (_untracked != null) - _untracked.remove(sm); - } - - /** - * An embedded or nonpersistent managed instance has been persisted. - */ - public void persist(StateManagerImpl sm) { - if (_embeds != null) - _embeds.remove(sm); - } - - /** - * A new instance has just been assigned a permanent oid. - */ - public void assignObjectId(Object id, StateManagerImpl sm) { - // if assigning oid, remove from new cache and put in primary; may - // not be in new cache if another new instance had same id - StateManagerImpl orig = (StateManagerImpl) _news.remove(id); - if (orig != null && orig != sm) - _news.put(id, orig); // put back - - // put in main cache, but make sure we don't replace another - // instance with the same oid - orig = (StateManagerImpl) _main.put(sm.getObjectId(), sm); - if (orig != null) { - _main.put(sm.getObjectId(), orig); - if (!orig.isDeleted()) - throw new UserException(_loc.get("dup-oid-assign", - sm.getObjectId(), Exceptions.toString - (sm.getManagedInstance()))). - setFailedObject(sm.getManagedInstance()); - - // same oid as deleted instance; put in conflict cache - if (_conflicts == null) - _conflicts = new HashMap(); - _conflicts.put(sm.getObjectId(), sm); - } - } - - /** - * A new instance has committed; recache under permanent oid. - */ - public void commitNew(Object id, StateManagerImpl sm) { - // if the id didn't change, the instance was already assigned an - // id, but it could have been in conflict cache - StateManagerImpl orig; - if (sm.getObjectId() == id) { - orig = (_conflicts == null) ? null - : (StateManagerImpl) _conflicts.remove(id); - if (orig == sm) { - orig = (StateManagerImpl) _main.put(id, sm); - if (orig != null && !orig.isDeleted()) { - _main.put(sm.getObjectId(), orig); - throw new UserException(_loc.get("dup-oid-assign", - sm.getObjectId(), Exceptions.toString - (sm.getManagedInstance()))).setFailedObject - (sm.getManagedInstance()).setFatal(true); - } - } - return; - } - - // oid changed, so it must previously have been a new instance - // without an assigned oid. remove it from the new cache; ok if - // we end up removing another instance with same id - if (_news != null) - _news.remove(id); - - // and put into main cache now that id is asssigned - orig = (StateManagerImpl) _main.put(sm.getObjectId(), sm); - if (orig != null && orig != sm && !orig.isDeleted()) { - // put back orig and throw error - _main.put(sm.getObjectId(), orig); - throw new UserException(_loc.get("dup-oid-assign", - sm.getObjectId(), Exceptions.toString - (sm.getManagedInstance()))).setFailedObject - (sm.getManagedInstance()).setFatal(true); - } - } - - /** - * Return a copy of all cached persistent objects. - */ - public Collection copy() { - // proxies not included here because the state manager is always - // present in other caches too - - int size = _main.size(); - if (_conflicts != null) - size += _conflicts.size(); - if (_news != null) - size += _news.size(); - if (_embeds != null) - size += _embeds.size(); - if (size == 0) - return Collections.EMPTY_LIST; - - List copy = new ArrayList(size); - for (Iterator itr = _main.values().iterator(); itr.hasNext();) - copy.add(itr.next()); - if (_conflicts != null && !_conflicts.isEmpty()) - for (Iterator itr = _conflicts.values().iterator(); - itr.hasNext();) - copy.add(itr.next()); - if (_news != null && !_news.isEmpty()) - for (Iterator itr = _news.values().iterator(); itr.hasNext();) - copy.add(itr.next()); - if (_embeds != null && !_embeds.isEmpty()) - for (Iterator itr = _embeds.iterator(); itr.hasNext();) - copy.add(itr.next()); - return copy; - } - - /** - * Clear the cache. - */ - public void clear() { - _main = newManagedObjectCache(); - if (_conflicts != null) - _conflicts = null; - if (_news != null) - _news = null; - if (_embeds != null) - _embeds = null; - if (_untracked != null) - _untracked = null; - } - - /** - * Clear new instances without permanent oids. - */ - public void clearNew() { - if (_news != null) - _news = null; - } - - private void dirtyCheck() { - if (_untracked == null) - return; - - for (Iterator iter = _untracked.iterator(); iter.hasNext(); ) - ((StateManagerImpl) iter.next()).dirtyCheck(); - } + boolean isSerializing() { + return _isSerializing; } /** * Transactional cache that holds soft refs to clean instances. */ - private static class TransactionalCache - implements Set { + static class TransactionalCache + implements Set, Serializable { private final boolean _orderDirty; private Set _dirty = null; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateAttachStrategy.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateAttachStrategy.java index ce3f23845..51fc74de7 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateAttachStrategy.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateAttachStrategy.java @@ -171,8 +171,7 @@ class DetachedStateAttachStrategy BitSet toLoad = (BitSet) fields.clone(); toLoad.andNot(sm.getLoaded()); // skip already loaded fields if (toLoad.length() > 0) - sm.loadFields(toLoad, null, LockLevels.LOCK_NONE, null, - false); + sm.loadFields(toLoad, null, LockLevels.LOCK_NONE, null); //### we should calculate lock level above } Object version = state[offset]; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateManager.java index 7bf1b6325..aafa6db89 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedStateManager.java @@ -145,7 +145,7 @@ public class DetachedStateManager } } FetchConfiguration fc = broker.getFetchConfiguration(); - sm.loadFields(load, fc, fc.getWriteLockLevel(), null, true); + sm.loadFields(load, fc, fc.getWriteLockLevel(), null); } Object origVersion = sm.getVersion(); sm.setVersion(_version); @@ -698,7 +698,7 @@ public class DetachedStateManager throw new UnsupportedOperationException(); } - public ValueMetaData getOwnerMetaData() { + public int getOwnerIndex() { throw new UnsupportedOperationException(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedValueStateManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedValueStateManager.java index 28b409775..174e70dcd 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedValueStateManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachedValueStateManager.java @@ -88,8 +88,8 @@ public class DetachedValueStateManager return null; } - public ValueMetaData getOwnerMetaData() { - return null; + public int getOwnerIndex() { + throw new UnsupportedOperationException(); } public boolean isEmbedded() { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ManagedCache.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ManagedCache.java new file mode 100644 index 000000000..698d65eba --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ManagedCache.java @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.lib.util.ReferenceHashSet; +import org.apache.openjpa.util.Exceptions; +import org.apache.openjpa.util.InternalException; +import org.apache.openjpa.util.UserException; + +/** + * Cache of managed objects. Must be static for serialization reasons. + */ +class ManagedCache implements Serializable { + + private static final Localizer _loc = + Localizer.forPackage(ManagedCache.class); + + private Map _main; // oid -> sm + private Map _conflicts = null; // conflict oid -> new sm + private Map _news = null; // tmp id -> new sm + private Collection _embeds = null; // embedded/non-persistent sms + private Collection _untracked = null; // hard refs to untracked sms + private BrokerImpl broker; + + /** + * Constructor; supply primary cache map. + */ + ManagedCache(BrokerImpl broker) { + this.broker = broker; + _main = broker.newManagedObjectCache(); + } + + /** + * Return the instance for the given oid, optionally allowing + * new instances. + */ + public StateManagerImpl getById(Object oid, boolean allowNew) { + if (oid == null) + return null; + + // check main cache for oid + StateManagerImpl sm = (StateManagerImpl) _main.get(oid); + StateManagerImpl sm2; + if (sm != null) { + // if it's a new instance, we know it's the only match, because + // other pers instances override new instances in _cache + if (sm.isNew()) + return (allowNew) ? sm : null; + if (!allowNew || !sm.isDeleted()) + return sm; + + // sm is deleted; check conflict cache + if (_conflicts != null) { + sm2 = (StateManagerImpl) _conflicts.get(oid); + if (sm2 != null) + return sm2; + } + } + + // at this point sm is null or deleted; check the new cache for + // any matches. this allows us to match app id objects to new + // instances without permanant oids + if (allowNew && _news != null && !_news.isEmpty()) { + sm2 = (StateManagerImpl) _news.get(oid); + if (sm2 != null) + return sm2; + } + return sm; + } + + /** + * Call this method when a new state manager initializes itself. + */ + public void add(StateManagerImpl sm) { + if (!sm.isIntercepting()) { + if (_untracked == null) + _untracked = new HashSet(); + _untracked.add(sm); + } + + if (!sm.isPersistent() || sm.isEmbedded()) { + if (_embeds == null) + _embeds = new ReferenceHashSet(ReferenceHashSet.WEAK); + _embeds.add(sm); + return; + } + + // initializing new instance; put in new cache because won't have + // permanent oid yet + if (sm.isNew()) { + if (_news == null) + _news = new HashMap(); + _news.put(sm.getId(), sm); + return; + } + + // initializing persistent instance; put in main cache + StateManagerImpl orig = (StateManagerImpl) _main.put + (sm.getObjectId(), sm); + if (orig != null) { + _main.put(sm.getObjectId(), orig); + throw new UserException(_loc.get("dup-load", sm.getObjectId(), + Exceptions.toString(orig.getManagedInstance()))) + .setFailedObject(sm.getManagedInstance()); + } + } + + /** + * Remove the given state manager from the cache when it transitions + * to transient. + */ + public void remove(Object id, StateManagerImpl sm) { + // if it has a permanent oid, remove from main / conflict cache, + // else remove from embedded/nontrans cache, and if not there + // remove from new cache + Object orig; + if (sm.getObjectId() != null) { + orig = _main.remove(id); + if (orig != sm) { + if (orig != null) + _main.put(id, orig); // put back + if (_conflicts != null) { + orig = _conflicts.remove(id); + if (orig != null && orig != sm) + _conflicts.put(id, orig); // put back + } + } + } else if ((_embeds == null || !_embeds.remove(sm)) + && _news != null) { + orig = _news.remove(id); + if (orig != null && orig != sm) + _news.put(id, orig); // put back + } + + if (_untracked != null) + _untracked.remove(sm); + } + + /** + * An embedded or nonpersistent managed instance has been persisted. + */ + public void persist(StateManagerImpl sm) { + if (_embeds != null) + _embeds.remove(sm); + } + + /** + * A new instance has just been assigned a permanent oid. + */ + public void assignObjectId(Object id, StateManagerImpl sm) { + // if assigning oid, remove from new cache and put in primary; may + // not be in new cache if another new instance had same id + StateManagerImpl orig = null; + if (_news != null) { + orig = (StateManagerImpl) _news.remove(id); + if (orig != null && orig != sm) + _news.put(id, orig); // put back + } + + // put in main cache, but make sure we don't replace another + // instance with the same oid + orig = (StateManagerImpl) _main.put(sm.getObjectId(), sm); + if (orig != null) { + _main.put(sm.getObjectId(), orig); + if (!orig.isDeleted()) + throw new UserException(_loc.get("dup-oid-assign", + sm.getObjectId(), + Exceptions.toString(sm.getManagedInstance()))) + .setFailedObject(sm.getManagedInstance()); + + // same oid as deleted instance; put in conflict cache + if (_conflicts == null) + _conflicts = new HashMap(); + _conflicts.put(sm.getObjectId(), sm); + } + } + + /** + * A new instance has committed; recache under permanent oid. + */ + public void commitNew(Object id, StateManagerImpl sm) { + // if the id didn't change, the instance was already assigned an + // id, but it could have been in conflict cache + StateManagerImpl orig; + if (sm.getObjectId() == id) { + orig = (_conflicts == null) ? null + : (StateManagerImpl) _conflicts.remove(id); + if (orig == sm) { + orig = (StateManagerImpl) _main.put(id, sm); + if (orig != null && !orig.isDeleted()) { + _main.put(sm.getObjectId(), orig); + throw new UserException(_loc.get("dup-oid-assign", + sm.getObjectId(), Exceptions.toString( + sm.getManagedInstance()))) + .setFailedObject(sm.getManagedInstance()) + .setFatal(true); + } + } + return; + } + + // oid changed, so it must previously have been a new instance + // without an assigned oid. remove it from the new cache; ok if + // we end up removing another instance with same id + if (_news != null) + _news.remove(id); + + // and put into main cache now that id is asssigned + orig = (StateManagerImpl) _main.put(sm.getObjectId(), sm); + if (orig != null && orig != sm && !orig.isDeleted()) { + // put back orig and throw error + _main.put(sm.getObjectId(), orig); + throw new UserException(_loc.get("dup-oid-assign", + sm.getObjectId(), Exceptions.toString(sm.getManagedInstance()))) + .setFailedObject(sm.getManagedInstance()).setFatal(true); + } + } + + /** + * Return a copy of all cached persistent objects. + */ + public Collection copy() { + // proxies not included here because the state manager is always + // present in other caches too + + int size = _main.size(); + if (_conflicts != null) + size += _conflicts.size(); + if (_news != null) + size += _news.size(); + if (_embeds != null) + size += _embeds.size(); + if (size == 0) + return Collections.EMPTY_LIST; + + List copy = new ArrayList(size); + for (Iterator itr = _main.values().iterator(); itr.hasNext();) + copy.add(itr.next()); + if (_conflicts != null && !_conflicts.isEmpty()) + for (Iterator itr = _conflicts.values().iterator(); + itr.hasNext();) + copy.add(itr.next()); + if (_news != null && !_news.isEmpty()) + for (Iterator itr = _news.values().iterator(); itr.hasNext();) + copy.add(itr.next()); + if (_embeds != null && !_embeds.isEmpty()) + for (Iterator itr = _embeds.iterator(); itr.hasNext();) + copy.add(itr.next()); + return copy; + } + + /** + * Clear the cache. + */ + public void clear() { + _main = broker.newManagedObjectCache(); + if (_conflicts != null) + _conflicts = null; + if (_news != null) + _news = null; + if (_embeds != null) + _embeds = null; + if (_untracked != null) + _untracked = null; + } + + /** + * Clear new instances without permanent oids. + */ + public void clearNew() { + if (_news != null) + _news = null; + } + + void dirtyCheck() { + if (_untracked == null) + return; + + for (Iterator iter = _untracked.iterator(); iter.hasNext(); ) + ((StateManagerImpl) iter.next()).dirtyCheck(); + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ObjectIdStateManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ObjectIdStateManager.java index 88f713aca..99cdcaab6 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ObjectIdStateManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ObjectIdStateManager.java @@ -309,8 +309,8 @@ public class ObjectIdStateManager return _owner; } - public ValueMetaData getOwnerMetaData() { - return _vmd; + public int getOwnerIndex() { + return _vmd.getFieldMetaData().getIndex(); } public boolean isEmbedded() { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java index 676594bd6..3effd27e0 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.kernel; +import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -30,7 +31,7 @@ import java.util.Map; * @author Steve Kim * @since 0.3.4 */ -public class OpenJPASavepoint { +public class OpenJPASavepoint implements Serializable { private final Broker _broker; private final String _name; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPAStateManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPAStateManager.java index 001b1cb1a..a6f44dc43 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPAStateManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPAStateManager.java @@ -102,9 +102,11 @@ public interface OpenJPAStateManager public OpenJPAStateManager getOwner(); /** - * Return the owning value. + * Return the owning value's field index + * + * @since 1.1.0 */ - public ValueMetaData getOwnerMetaData(); + public int getOwnerIndex(); /** * Return true if this instance has an owner, meaning it is an embedded diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java index cea42eb95..9a8b0f45b 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java @@ -18,13 +18,16 @@ */ package org.apache.openjpa.kernel; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.util.BitSet; import java.util.Collection; import java.util.Date; import java.util.Map; import org.apache.openjpa.enhance.PersistenceCapable; -import org.apache.openjpa.enhance.Reflection; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.util.ProxyManager; @@ -35,13 +38,14 @@ import org.apache.openjpa.util.ProxyManager; * @author Abe White */ public class SaveFieldManager - extends ClearFieldManager { + extends ClearFieldManager + implements Serializable { private final StateManagerImpl _sm; private final BitSet _unloaded; private BitSet _saved = null; private int[] _copyField = null; - private PersistenceCapable _state = null; + private transient PersistenceCapable _state = null; // used to track field value during store/fetch cycle private Object _field = null; @@ -140,7 +144,7 @@ public class SaveFieldManager if (_copyField == null) _copyField = new int[1]; _copyField[0] = field; - _state.pcCopyFields(_sm.getPersistenceCapable(), _copyField); + getState().pcCopyFields(_sm.getPersistenceCapable(), _copyField); return false; } @@ -164,7 +168,7 @@ public class SaveFieldManager if (_copyField == null) _copyField = new int[1]; _copyField[0] = field; - _sm.getPersistenceCapable().pcCopyFields(_state, _copyField); + _sm.getPersistenceCapable().pcCopyFields(getState(), _copyField); return false; } @@ -177,12 +181,12 @@ public class SaveFieldManager // if the field is not available, assume that it has changed. if (_saved == null || !_saved.get(field)) return false; - if (!(_state.pcGetStateManager() instanceof StateManagerImpl)) + if (!(getState().pcGetStateManager() instanceof StateManagerImpl)) return false; - StateManagerImpl sm = (StateManagerImpl) _state.pcGetStateManager(); + StateManagerImpl sm = (StateManagerImpl) getState().pcGetStateManager(); SingleFieldManager single = new SingleFieldManager(sm, sm.getBroker()); - sm.provideField(_state, single, field); + sm.provideField(getState(), single, field); Object old = single.fetchObjectField(field); return current == old || current != null && current.equals(old); } @@ -227,4 +231,15 @@ public class SaveFieldManager _saved.clear(field); } } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + _sm.writePC(oos, _state); + } + + private void readObject(ObjectInputStream ois) + throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + _state = _sm.readPC(ois); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SavepointFieldManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SavepointFieldManager.java index 6980b455a..b68890f6b 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SavepointFieldManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SavepointFieldManager.java @@ -18,6 +18,10 @@ */ package org.apache.openjpa.kernel; +import java.io.Serializable; +import java.io.ObjectOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.BitSet; import java.util.Collection; import java.util.Date; @@ -37,7 +41,8 @@ import org.apache.openjpa.util.ProxyManager; * @since 0.3.4 */ class SavepointFieldManager - extends ClearFieldManager { + extends ClearFieldManager + implements Serializable { private static final Localizer _loc = Localizer.forPackage (SavepointFieldManager.class); @@ -47,7 +52,7 @@ class SavepointFieldManager private final BitSet _dirty; private final BitSet _flush; private final PCState _state; - private PersistenceCapable _copy; + private transient PersistenceCapable _copy; private final Object _version; private final Object _loadVersion; @@ -227,4 +232,15 @@ class SavepointFieldManager if (curVal != null && _field == null) throw new InternalException(_loc.get("no-savepoint-copy", fmd)); } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + _sm.writePC(oos, _copy); + } + + private void readObject(ObjectInputStream ois) + throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + _copy = _sm.readPC(ois); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java index 0563ab4fb..d303c0b45 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java @@ -20,6 +20,7 @@ package org.apache.openjpa.kernel; import java.io.IOException; import java.io.ObjectOutput; +import java.io.Serializable; import java.sql.Timestamp; import java.util.Arrays; import java.util.Calendar; @@ -50,7 +51,8 @@ import org.apache.openjpa.util.UserException; * @author Abe White */ class SingleFieldManager - extends TransferFieldManager { + extends TransferFieldManager + implements Serializable { private static final Localizer _loc = Localizer.forPackage (SingleFieldManager.class); @@ -235,7 +237,7 @@ class SingleFieldManager StateManagerImpl sm = _broker.getStateManagerImpl(obj, false); if (sm != null && sm.getOwner() == _sm - && sm.getOwnerMetaData() == vmd) + && sm.getOwnerIndex() == vmd.getFieldMetaData().getIndex()) sm.release(true); } @@ -380,7 +382,8 @@ class SingleFieldManager // delete if unknowned or this isn't an embedded field or if owned by us StateManagerImpl sm = _broker.getStateManagerImpl(obj, false); if (sm != null && (sm.getOwner() == null || !vmd.isEmbeddedPC() - || (sm.getOwner() == _sm && sm.getOwnerMetaData() == vmd))) + || (sm.getOwner() == _sm + && sm.getOwnerIndex() == vmd.getFieldMetaData().getIndex()))) _broker.delete(sm.getManagedInstance(), sm, call); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java index e0ea66031..c11fa18f6 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java @@ -19,7 +19,11 @@ package org.apache.openjpa.kernel; import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -77,7 +81,7 @@ import serp.util.Numbers; * @author Abe White */ public class StateManagerImpl - implements OpenJPAStateManager { + implements OpenJPAStateManager, Serializable { public static final int LOAD_FGS = 0; public static final int LOAD_ALL = 1; @@ -105,8 +109,8 @@ public class StateManagerImpl (StateManagerImpl.class); // information about the instance - private PersistenceCapable _pc = null; - private ClassMetaData _meta = null; + private transient PersistenceCapable _pc = null; + private transient ClassMetaData _meta = null; private BitSet _loaded = null; private BitSet _dirty = null; private BitSet _flush = null; @@ -121,7 +125,7 @@ public class StateManagerImpl private Object _oid = null; // the managing persistence manager and lifecycle state - private final BrokerImpl _broker; + private transient BrokerImpl _broker; // this is serialized specially private PCState _state = PCState.TRANSIENT; // the current and last loaded version indicators, and the lock object @@ -142,7 +146,7 @@ public class StateManagerImpl // information about the owner of this instance, if it is embedded private StateManagerImpl _owner = null; - private ValueMetaData _ownerMeta = null; + private int _ownerIndex = -1; /** * Constructor; supply id, type metadata, and owning persistence manager. @@ -163,7 +167,7 @@ public class StateManagerImpl */ void setOwner(StateManagerImpl owner, ValueMetaData ownerMeta) { _owner = owner; - _ownerMeta = ownerMeta; + _ownerIndex = ownerMeta.getFieldMetaData().getIndex(); } /** @@ -371,7 +375,7 @@ public class StateManagerImpl // care of checking if the DFG is loaded, making sure version info // is loaded, etc int lockLevel = calculateLockLevel(active, forWrite, fetch); - boolean ret = loadFields(fields, fetch, lockLevel, sdata, forWrite); + boolean ret = loadFields(fields, fetch, lockLevel, sdata); obtainLocks(active, forWrite, lockLevel, fetch, sdata); return ret; } @@ -395,8 +399,8 @@ public class StateManagerImpl return _owner; } - public ValueMetaData getOwnerMetaData() { - return _ownerMeta; + public int getOwnerIndex() { + return _ownerIndex; } public boolean isEmbedded() { @@ -594,9 +598,9 @@ public class StateManagerImpl // Throw exception if field already has a value assigned. // @GeneratedValue overrides POJO initial values and setter methods - if (!isDefaultValue(field) && !fmd.isValueGenerated()) + if (!fmd.isValueGenerated() && !isDefaultValue(field)) throw new InvalidStateException(_loc.get( - "existing-value-override-excep", fmd.getFullName(false))); + "existing-value-override-excep", fmd.getFullName(false))); // for primary key fields, assign the object id and recache so that // to the user, so it looks like the oid always matches the pk fields @@ -1318,7 +1322,16 @@ public class StateManagerImpl // Implementation of StateManager interface //////////////////////////////////////////// + /** + * @return whether or not unloaded fields should be closed. + */ public boolean serializing() { + // if the broker is in the midst of a serialization, then no special + // handling should be performed on the instance, and no subsequent + // load should happen + if (_broker.isSerializing()) + return false; + try { if (_meta.isDetachable()) return DetachManager.preSerialize(this); @@ -1510,8 +1523,7 @@ public class StateManagerImpl if (isEmbedded()) { // notify owner of change - _owner.dirty(_ownerMeta.getFieldMetaData().getIndex(), - Boolean.TRUE, loadFetchGroup); + _owner.dirty(_ownerIndex, Boolean.TRUE, loadFetchGroup); } // is this a direct mutation of an sco field? @@ -2862,7 +2874,7 @@ public class StateManagerImpl * Return true if any data is loaded, false otherwise. */ boolean loadFields(BitSet fields, FetchConfiguration fetch, int lockLevel, - Object sdata, boolean forWrite) { + Object sdata) { // can't load version field from store if (fields != null) { FieldMetaData vfield = _meta.getVersionField(); @@ -2956,7 +2968,7 @@ public class StateManagerImpl // call this method even if there are no unloaded fields; loadFields // takes care of things like loading version info and setting PC flags try { - loadFields(fields, fetch, lockLevel, null, forWrite); + loadFields(fields, fetch, lockLevel, null); } finally { if (lfgAdded) fetch.removeFetchGroup(lfg); @@ -3154,4 +3166,66 @@ public class StateManagerImpl // manager lock and broker lock being obtained in different orders _broker.unlock (); } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.writeObject(_broker); + oos.defaultWriteObject(); + oos.writeObject(_meta.getDescribedType()); + writePC(oos, _pc); + } + + /** + * Write pc to oos, handling internal-form + * serialization. pc must be of the same type that this + * state manager manages. + * + * @since 1.1.0 + */ + void writePC(ObjectOutputStream oos, PersistenceCapable pc) + throws IOException { + if (!Serializable.class.isAssignableFrom(_meta.getDescribedType())) + throw new NotSerializableException( + _meta.getDescribedType().getName()); + + oos.writeObject(pc); + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + _broker = (BrokerImpl) in.readObject(); + in.defaultReadObject(); + + // we need to store the class before the pc instance so that we can + // create _meta before calling readPC(), which relies on _meta being + // non-null when reconstituting ReflectingPC instances. Sadly, this + // penalizes the serialization footprint of non-ReflectingPC SMs also. + Class managedType = (Class) in.readObject(); + _meta = _broker.getConfiguration().getMetaDataRepositoryInstance() + .getMetaData(managedType, null, true); + + _pc = readPC(in); + } + + /** + * Converts the deserialized o to a {@link PersistenceCapable} + * instance appropriate for storing in _pc. + * + * @since 1.1.0 + */ + PersistenceCapable readPC(ObjectInputStream in) + throws ClassNotFoundException, IOException { + Object o = in.readObject(); + + if (o == null) + return null; + + PersistenceCapable pc; + if (!(o instanceof PersistenceCapable)) + pc = ImplHelper.toPersistenceCapable(o, this); + else + pc = (PersistenceCapable) o; + + pc.pcReplaceStateManager(this); + return pc; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneralException.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneralException.java index 938847f44..d56d783b7 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneralException.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneralException.java @@ -47,6 +47,10 @@ public class GeneralException super(msg, cause); } + public GeneralException(String msg, Throwable cause) { + super(msg, cause); + } + public int getType() { return GENERAL; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java index 3a416da3d..74acea0c9 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java @@ -304,6 +304,11 @@ public class ImplHelper { } } + public static void registerPersistenceCapable( + ReflectingPersistenceCapable pc) { + _unenhancedInstanceMap.put(pc.getManagedInstance(), pc); + } + /** * @return the user-visible representation of o. * @since 1.0.0 diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties index 206825fd7..1cc80eecb 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties @@ -390,3 +390,9 @@ multi-threaded-access: Multiple concurrent threads attempted to access a \ openjpa.Multithreaded property to true to override the default behavior. no-saved-fields: No state snapshot is available for "{0}", but this instance \ uses state-comparison for dirty detection. +cant-serialize-flushed-broker: Serialization not allowed once a broker has \ + been flushed. +cant-serialize-pessimistic-broker: Serialization not allowed for brokers with \ + an active datastore (pessimistic) transaction. +cant-serialize-connected-broker: Serialization not allowed for brokers with \ + an active connection to the database. \ No newline at end of file diff --git a/openjpa-kernel/src/test/java/org/apache/openjpa/enhance/TestPCSubclassNameConversion.java b/openjpa-kernel/src/test/java/org/apache/openjpa/enhance/TestPCSubclassNameConversion.java new file mode 100644 index 000000000..f337f2628 --- /dev/null +++ b/openjpa-kernel/src/test/java/org/apache/openjpa/enhance/TestPCSubclassNameConversion.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.enhance; + +import junit.framework.TestCase; + +public class TestPCSubclassNameConversion + extends TestCase { + + public void testPCSubclassNameConversion() { + String name = PCEnhancer.toPCSubclassName(Object.class); + assertTrue(PCEnhancer.isPCSubclassName(name)); + assertEquals(Object.class.getName(), + PCEnhancer.toManagedTypeName(name)); + } +} \ No newline at end of file diff --git a/openjpa-lib/src/main/java/org/apache/openjpa/lib/conf/ConfigurationImpl.java b/openjpa-lib/src/main/java/org/apache/openjpa/lib/conf/ConfigurationImpl.java index 3e8bfa9c3..4816be519 100644 --- a/openjpa-lib/src/main/java/org/apache/openjpa/lib/conf/ConfigurationImpl.java +++ b/openjpa-lib/src/main/java/org/apache/openjpa/lib/conf/ConfigurationImpl.java @@ -648,13 +648,15 @@ public class ConfigurationImpl Configurations.removeProperty("properties", remaining); // now warn if there are any remaining properties that there - // is an unhandled prop + // is an unhandled prop, and remove the unknown properties Map.Entry entry; for (Iterator itr = remaining.entrySet().iterator(); itr.hasNext();) { entry = (Map.Entry) itr.next(); - if (entry.getKey() != null) - warnInvalidProperty((String) entry.getKey()); - ser &= entry.getValue() instanceof Serializable; + Object key = entry.getKey(); + if (key != null) { + warnInvalidProperty((String) key); + map.remove(key); + } } // cache properties diff --git a/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/ReferenceHashSet.java b/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/ReferenceHashSet.java index 20c449a83..9c5642b72 100644 --- a/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/ReferenceHashSet.java +++ b/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/ReferenceHashSet.java @@ -49,7 +49,11 @@ public class ReferenceHashSet implements Set, Serializable { */ public static final int WEAK = 2; - private static final Object DUMMY_VAL = new Object(); + private static final Object DUMMY_VAL = new Serializable() { + public String toString() { + return ReferenceHashSet.class.getName() + ".DUMMY_VAL"; + } + }; private final Set _set; diff --git a/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/concurrent/AbstractConcurrentEventManager.java b/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/concurrent/AbstractConcurrentEventManager.java index 9e807cb48..05f4cc17b 100644 --- a/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/concurrent/AbstractConcurrentEventManager.java +++ b/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/concurrent/AbstractConcurrentEventManager.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.lib.util.concurrent; +import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -35,7 +36,8 @@ import org.apache.openjpa.lib.util.EventManager; * * @author Abe White */ -public abstract class AbstractConcurrentEventManager implements EventManager { +public abstract class AbstractConcurrentEventManager + implements EventManager, Serializable { private static Exception[] EMPTY_EXCEPTIONS = new Exception[0]; diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/AbstractUnenhancedClassTest.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/AbstractUnenhancedClassTest.java index 3eebdb702..028f34ffd 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/AbstractUnenhancedClassTest.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/AbstractUnenhancedClassTest.java @@ -102,6 +102,7 @@ public abstract class AbstractUnenhancedClassTest PersistenceCapable pc = PCRegistry.newInstance( getUnenhancedClass(), null, false); assertNotNull(pc); + assertEquals(pc.getClass(), PCRegistry.getPCType(getUnenhancedClass())); } public void testClearingOnSubtypeInstance() { diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/AbstractBrokerSerializationTest.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/AbstractBrokerSerializationTest.java new file mode 100644 index 000000000..7d8d44b02 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/AbstractBrokerSerializationTest.java @@ -0,0 +1,446 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.EntityManager; + +import org.apache.openjpa.event.AbstractLifecycleListener; +import org.apache.openjpa.event.AbstractTransactionListener; +import org.apache.openjpa.event.LifecycleEvent; +import org.apache.openjpa.event.TransactionEvent; +import org.apache.openjpa.persistence.InvalidStateException; +import org.apache.openjpa.persistence.JPAFacadeHelper; +import org.apache.openjpa.persistence.OpenJPAEntityManager; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory; +import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan; +import org.apache.openjpa.persistence.jdbc.JoinSyntax; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/* + * To test: + * - managed transactions + * - converting non-enhanced classes to enhanced subclasses + * (maybe an ugly ThreadLocal, maybe through PCData?) + */ +public abstract class AbstractBrokerSerializationTest + extends SingleEMFTestCase { + + private static LifeListener deserializedLifeListener; + private static int testGlobalRefreshCount = 0; + + private static TxListener deserializedTxListener; + private static int testGlobalBeginCount = 0; + + + private Object id; + + public void setUp() { + testGlobalRefreshCount = 0; + deserializedLifeListener = null; + testGlobalBeginCount = 0; + deserializedTxListener = null; + + setUp(getManagedType(), getSecondaryType(), CLEAR_TABLES, + "openjpa.EntityManagerFactoryPool", "true"); + + T e = newManagedInstance(); + OpenJPAEntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + em.persist(e); + em.getTransaction().commit(); + id = em.getObjectId(e); + em.close(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + testGlobalRefreshCount = 0; + deserializedLifeListener = null; + testGlobalBeginCount = 0; + deserializedTxListener = null; + } + + public void testEmptyBrokerSerialization() { + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAEntityManager em2 = deserializeEM(serialize(em)); + + assertTrue(em != em2); + assertTrue( + JPAFacadeHelper.toBroker(em) != JPAFacadeHelper.toBroker(em2)); + assertSame(em.getEntityManagerFactory(), em2.getEntityManagerFactory()); + + assertSame(em2, JPAFacadeHelper.toBroker(em2) + .getUserObject(JPAFacadeHelper.EM_KEY)); + + em.close(); + assertTrue(em2.isOpen()); + em2.close(); + } + + public void testNontransactionalBrokerSerialization() { + OpenJPAEntityManager em = emf.createEntityManager(); + T e = em.find(getManagedType(), id); + OpenJPAEntityManager em2 = deserializeEM(serialize(em)); + + assertFalse(em2.getTransaction().isActive()); + + assertFalse(em2.contains(e)); + assertEquals(1*graphSize(), em2.getManagedObjects().size()); + T e2 = em2.find(getManagedType(), id); + assertEquals(em.getObjectId(e), em2.getObjectId(e2)); + + em.close(); + em2.close(); + } + + public void testUnflushedOptimisticTxBrokerSerialization() { + OpenJPAEntityManager em = emf.createEntityManager(); + T e = em.find(getManagedType(), id); + OpenJPAEntityManager em2 = null; + OpenJPAEntityManager em3 = null; + try { + em.getTransaction().begin(); + modifyInstance(e); + T newe = newManagedInstance(); + em.persist(newe); + em2 = deserializeEM(serialize(em)); + + assertTrue(em2.getTransaction().isActive()); + + assertFalse(em2.contains(e)); + T e2 = em2.find(getManagedType(), id); + assertEquals(em.getObjectId(e), em2.getObjectId(e2)); + + assertEquals("modified", getModifiedValue(e2)); + + em.getTransaction().rollback(); + assertTrue(em2.getTransaction().isActive()); + em2.getTransaction().commit(); + + em3 = emf.createEntityManager(); + T e3 = em3.find(getManagedType(), id); + assertEquals(getModifiedValue(e2), getModifiedValue(e3)); + assertTrue(1 < ((Number) em3.createQuery("select count(o) from " + + getManagedType().getName() + " o").getSingleResult()) + .intValue()); + } finally { + close(em); + close(em2); + close(em3); + } + } + + public void testFlushedOptimisticTxBrokerSerialization() { + OpenJPAEntityManager em = emf.createEntityManager(); + T e = em.find(getManagedType(), id); + em.getTransaction().begin(); + modifyInstance(e); + em.flush(); + try { + serialize(em); + } catch (InvalidStateException ise) { + // expected + assertTrue(ise.getMessage().contains("flushed")); + } finally { + em.getTransaction().rollback(); + em.close(); + } + } + + public void testConnectedOptimisticTxBrokerSerialization() { + Map m = new HashMap(); + m.put("openjpa.ConnectionRetainMode", "always"); + OpenJPAEntityManager em = emf.createEntityManager(m); + try { + serialize(em); + } catch (InvalidStateException ise) { + // expected + assertTrue(ise.getMessage().contains("connected")); + } finally { + em.close(); + } + } + + public void testEmptyPessimisticTxBrokerSerialization() { + Map m = new HashMap(); + m.put("openjpa.Optimistic", "false"); + OpenJPAEntityManager em = emf.createEntityManager(m); + em.getTransaction().begin(); + try { + serialize(em); + fail("should not be able to serialize"); + } catch (InvalidStateException ise) { + // expected + assertTrue(ise.getMessage().contains("datastore (pessimistic)")); + } finally { + em.getTransaction().rollback(); + em.close(); + } + } + + public void testNonEmptyPessimisticTxBrokerSerialization() { + Map m = new HashMap(); + m.put("openjpa.Optimistic", "false"); + OpenJPAEntityManager em = emf.createEntityManager(m); + T e = em.find(getManagedType(), id); + em.getTransaction().begin(); + try { + serialize(em); + fail("should not be able to serialize"); + } catch (InvalidStateException ise) { + // expected + assertTrue(ise.getMessage().contains("datastore (pessimistic)")); + } finally { + em.getTransaction().rollback(); + em.close(); + } + } + + public void testFetchConfigurationMutations() { + OpenJPAEntityManager em = emf.createEntityManager(); + JDBCFetchPlan plan = (JDBCFetchPlan) em.getFetchPlan(); + + assertNotEquals(17, plan.getLockTimeout()); + assertNotEquals(JoinSyntax.TRADITIONAL, plan.getJoinSyntax()); + + plan.setLockTimeout(17); + plan.setJoinSyntax(JoinSyntax.TRADITIONAL); + + OpenJPAEntityManager em2 = deserializeEM(serialize(em)); + JDBCFetchPlan plan2 = (JDBCFetchPlan) em2.getFetchPlan(); + assertEquals(17, plan2.getLockTimeout()); + assertEquals(JoinSyntax.TRADITIONAL, plan2.getJoinSyntax()); + } + + public void testInMemorySavepointsWithNewInstances() { + emf.close(); + OpenJPAEntityManagerFactory emf = createEMF( + getManagedType(), getSecondaryType(), + "openjpa.EntityManagerFactoryPool", "true", + "openjpa.SavepointManager", "in-mem"); + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAEntityManager em2 = null; + try { + em.getTransaction().begin(); + T t = newManagedInstance(); + Object orig = getModifiedValue(t); + em.persist(t); + Object id = em.getObjectId(t); + em.setSavepoint("foo"); + modifyInstance(t); + assertNotEquals(orig, getModifiedValue(t)); + + em2 = deserializeEM(serialize(em)); + T t2 = em2.find(getManagedType(), id); + assertNotEquals(orig, getModifiedValue(t2)); + + em.rollbackToSavepoint("foo"); + assertEquals(orig, getModifiedValue(t)); + + em2.rollbackToSavepoint("foo"); + assertEquals(orig, getModifiedValue(t2)); + } finally { + close(em); + close(em2); + } + } + + public void testInMemorySavepointsWithModifiedInstances() { + emf.close(); + OpenJPAEntityManagerFactory emf = createEMF( + getManagedType(), getSecondaryType(), + "openjpa.EntityManagerFactoryPool", "true", + "openjpa.SavepointManager", "in-mem"); + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAEntityManager em2 = null; + try { + em.getTransaction().begin(); + T t = em.find(getManagedType(), id); + Object orig = getModifiedValue(t); + em.setSavepoint("foo"); + modifyInstance(t); + assertNotEquals(orig, getModifiedValue(t)); + + em2 = deserializeEM(serialize(em)); + T t2 = em2.find(getManagedType(), id); + assertNotEquals(orig, getModifiedValue(t2)); + + em.rollbackToSavepoint("foo"); + assertEquals(orig, getModifiedValue(t)); + + em2.rollbackToSavepoint("foo"); + assertEquals(orig, getModifiedValue(t2)); + } finally { + close(em); + close(em2); + } + } + + public void testEventManagers() { + TxListener txListener = new TxListener(); + emf.addTransactionListener(txListener); + LifeListener lifeListener = new LifeListener(); + emf.addLifecycleListener(lifeListener, null); + + OpenJPAEntityManager em = emf.createEntityManager(); + T t = em.find(getManagedType(), id); + assertEquals(0, lifeListener.refreshCount); + em.refresh(t); + assertEquals(1*graphSize(), lifeListener.refreshCount); + em.getTransaction().begin(); + em.getTransaction().commit(); + em.getTransaction().begin(); + em.getTransaction().commit(); + assertEquals(2, txListener.beginCount); + + OpenJPAEntityManager em2 = deserializeEM(serialize(em)); + assertNotNull(deserializedLifeListener); + assertEquals(1* graphSize(), + deserializedLifeListener.refreshCount); + assertNotSame(lifeListener, deserializedLifeListener); + T t2 = em2.find(getManagedType(), id); + em2.refresh(t2); + assertEquals(2* graphSize(), + deserializedLifeListener.refreshCount); + + // if this is 3*refreshMultiplier(), that means that there are + // extra registered listeners + assertEquals(2* graphSize(), testGlobalRefreshCount); + + + assertNotNull(deserializedTxListener); + assertEquals(2, deserializedTxListener.beginCount); + assertNotSame(txListener, deserializedTxListener); + em2.getTransaction().begin(); + em2.getTransaction().rollback(); + assertEquals(3, deserializedTxListener.beginCount); + + // if this is 4, that means that there are extra registered listeners + assertEquals(3, testGlobalBeginCount); + } + + byte[] serialize(Object o) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + oos.flush(); + return baos.toByteArray(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + OpenJPAEntityManager deserializeEM(byte[] bytes) { + return (OpenJPAEntityManager) deserialize(bytes); + } + + private Object deserialize(byte[] bytes) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bais); + return ois.readObject(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + void close(EntityManager em) { + if (em != null && em.isOpen() && em.getTransaction().isActive()) + em.getTransaction().rollback(); + if (em != null && em.isOpen()) + em.close(); + } + + protected abstract Class getManagedType(); + + protected abstract T newManagedInstance(); + + protected abstract void modifyInstance(T t); + + protected abstract Object getModifiedValue(T t); + + /** + * The number of instances in the graph created + * by {@link #newManagedInstance()} of type T. + */ + protected int graphSize() { + return 1; + } + + /** + * An additional type that must be available in this PC. May be null. + */ + protected Class getSecondaryType() { + return null; + } + + private static class TxListener + extends AbstractTransactionListener + implements Serializable { + + private int beginCount = 0; + + public TxListener() { + + } + + @Override + public void afterBegin(TransactionEvent event) { + beginCount++; + testGlobalBeginCount++; + } + + private void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException { + in.defaultReadObject(); + deserializedTxListener = this; + } + } + + private static class LifeListener + extends AbstractLifecycleListener + implements Serializable { + + private int refreshCount = 0; + + @Override + public void afterRefresh(LifecycleEvent event) { + refreshCount++; + testGlobalRefreshCount++; + } + + private void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException { + in.defaultReadObject(); + deserializedLifeListener = this; + } + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/AbstractUnenhancedRelationBrokerSerializationTest.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/AbstractUnenhancedRelationBrokerSerializationTest.java new file mode 100644 index 000000000..f8f0a817b --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/AbstractUnenhancedRelationBrokerSerializationTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.enhance.PersistenceCapable; +import org.apache.openjpa.enhance.UnenhancedSubtype; +import org.apache.openjpa.persistence.OpenJPAEntityManager; +import org.apache.openjpa.util.ImplHelper; + +public abstract class AbstractUnenhancedRelationBrokerSerializationTest + extends AbstractBrokerSerializationTest { + + public void testNewUnenhancedSMsRegisteredGlobally() { + OpenJPAEntityManager em = emf.createEntityManager(); + OpenJPAEntityManager em2 = null; + try { + em.getTransaction().begin(); + UnenhancedSubtype newe = (UnenhancedSubtype) newManagedInstance(); + em.persist(newe); + em2 = deserializeEM(serialize(em)); + + for (Object o : em2.getManagedObjects()) { + assertFalse(o instanceof PersistenceCapable); + assertNotNull(ImplHelper.toPersistenceCapable(o, + emf.getConfiguration())); + if (o instanceof UnenhancedSubtype) + assertNotNull(ImplHelper.toPersistenceCapable( + ((UnenhancedSubtype) o).getRelated(), + emf.getConfiguration())); + } + } finally { + close(em); + close(em2); + } + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestEnhancedInstanceBrokerSerialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestEnhancedInstanceBrokerSerialization.java new file mode 100644 index 000000000..ceb8f257d --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestEnhancedInstanceBrokerSerialization.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.enhance.PersistenceCapable; +import org.apache.openjpa.persistence.query.SimpleEntity; + +public class TestEnhancedInstanceBrokerSerialization + extends AbstractBrokerSerializationTest { + + @Override + public void setUp() { + assertTrue( + PersistenceCapable.class.isAssignableFrom(SimpleEntity.class)); + super.setUp(); + } + + protected Class getManagedType() { + return SimpleEntity.class; + } + + protected SimpleEntity newManagedInstance() { + SimpleEntity e = new SimpleEntity(); + e.setName("foo"); + e.setValue("bar"); + return e; + } + + protected void modifyInstance(SimpleEntity e) { + e.setValue("modified"); + } + + protected Object getModifiedValue(SimpleEntity e) { + return e.getValue(); + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestEntityManagerFactoryPool.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestEntityManagerFactoryPool.java new file mode 100644 index 000000000..f61a62c68 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestEntityManagerFactoryPool.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +public class TestEntityManagerFactoryPool + extends SingleEMFTestCase { + + public void setUp() { + setUp("openjpa.EntityManagerFactoryPool", Boolean.TRUE); + + emf.createEntityManager().close(); + } + + public void testBrokerFactoryPoolHit() { + Map m = new HashMap(); + // also tests string values for the property + m.put("openjpa.EntityManagerFactoryPool", "True"); + EntityManagerFactory emf = Persistence.createEntityManagerFactory( + "test", m); + assertSame(this.emf, emf); + } + + public void testBrokerFactoryPoolMiss() { + Map m = new HashMap(); + m.put("openjpa.EntityManagerFactoryPool", Boolean.TRUE); + EntityManagerFactory emf = Persistence.createEntityManagerFactory( + "second-persistence-unit", m); + assertNotSame(this.emf, emf); + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestInstanceGraphBrokerSerialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestInstanceGraphBrokerSerialization.java new file mode 100644 index 000000000..4fc26d412 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestInstanceGraphBrokerSerialization.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.persistence.query.ManyOneEntity; + +public class TestInstanceGraphBrokerSerialization + extends AbstractBrokerSerializationTest { + + protected Class getManagedType() { + return ManyOneEntity.class; + } + + protected ManyOneEntity newManagedInstance() { + ManyOneEntity e = new ManyOneEntity(); + e.setName("foo"); + ManyOneEntity rel = new ManyOneEntity(); + rel.setName("bar"); + e.setRel(rel); + return e; + } + + protected void modifyInstance(ManyOneEntity e) { + e.getRel().setName("modified"); + } + + protected Object getModifiedValue(ManyOneEntity e) { + return e.getRel().getName(); + } + + @Override + protected int graphSize() { + return 2; + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedFieldAccessInstanceBrokerSerialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedFieldAccessInstanceBrokerSerialization.java new file mode 100644 index 000000000..1918545d0 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedFieldAccessInstanceBrokerSerialization.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.enhance.UnenhancedFieldAccess; + +public class TestUnenhancedFieldAccessInstanceBrokerSerialization + extends AbstractBrokerSerializationTest { + + protected Class getManagedType() { + return UnenhancedFieldAccess.class; + } + + protected UnenhancedFieldAccess newManagedInstance() { + UnenhancedFieldAccess e = new UnenhancedFieldAccess(); + e.setStringField("foo"); + return e; + } + + protected void modifyInstance(UnenhancedFieldAccess e) { + e.setStringField("modified"); + } + + protected Object getModifiedValue(UnenhancedFieldAccess e) { + return e.getStringField(); + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedFieldAccessWithRelationInstanceBrokerSerialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedFieldAccessWithRelationInstanceBrokerSerialization.java new file mode 100644 index 000000000..02ac209d1 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedFieldAccessWithRelationInstanceBrokerSerialization.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.enhance.UnenhancedFieldAccess; +import org.apache.openjpa.enhance.UnenhancedFieldAccessSubclass; + +public class TestUnenhancedFieldAccessWithRelationInstanceBrokerSerialization + extends AbstractUnenhancedRelationBrokerSerializationTest + { + + protected Class getSecondaryType() { + return UnenhancedFieldAccess.class; + } + + protected Class getManagedType() { + return UnenhancedFieldAccessSubclass.class; + } + + protected UnenhancedFieldAccessSubclass newManagedInstance() { + UnenhancedFieldAccessSubclass e = new UnenhancedFieldAccessSubclass(); + e.setStringField("foo"); + UnenhancedFieldAccess related = new UnenhancedFieldAccess(); + related.setStringField("bar"); + e.setRelated(related); + return e; + } + + protected void modifyInstance(UnenhancedFieldAccessSubclass e) { + e.getRelated().setStringField("modified"); + } + + protected Object getModifiedValue(UnenhancedFieldAccessSubclass e) { + return e.getRelated().getStringField(); + } + + @Override + protected int graphSize() { + return 2; + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedPropertyAccessInstanceBrokerSerialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedPropertyAccessInstanceBrokerSerialization.java new file mode 100644 index 000000000..3c4994948 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedPropertyAccessInstanceBrokerSerialization.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.enhance.UnenhancedPropertyAccess; + +public class TestUnenhancedPropertyAccessInstanceBrokerSerialization + extends AbstractBrokerSerializationTest { + + protected Class getManagedType() { + return UnenhancedPropertyAccess.class; + } + + protected UnenhancedPropertyAccess newManagedInstance() { + UnenhancedPropertyAccess e = new UnenhancedPropertyAccess(); + e.setStringField("foo"); + return e; + } + + protected void modifyInstance(UnenhancedPropertyAccess e) { + e.setStringField("modified"); + } + + protected Object getModifiedValue(UnenhancedPropertyAccess e) { + return e.getStringField(); + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedPropertyAccessWithRelationInstanceBrokerSerialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedPropertyAccessWithRelationInstanceBrokerSerialization.java new file mode 100644 index 000000000..3f533f7d9 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/kernel/TestUnenhancedPropertyAccessWithRelationInstanceBrokerSerialization.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import org.apache.openjpa.enhance.UnenhancedPropertyAccess; +import org.apache.openjpa.enhance.UnenhancedPropertyAccessSubclass; + +public class TestUnenhancedPropertyAccessWithRelationInstanceBrokerSerialization + extends AbstractUnenhancedRelationBrokerSerializationTest + { + + protected Class getSecondaryType() { + return UnenhancedPropertyAccess.class; + } + + protected Class getManagedType() { + return UnenhancedPropertyAccessSubclass.class; + } + + protected UnenhancedPropertyAccessSubclass newManagedInstance() { + UnenhancedPropertyAccessSubclass e = + new UnenhancedPropertyAccessSubclass(); + e.setStringField("foo"); + UnenhancedPropertyAccess related = new UnenhancedPropertyAccess(); + related.setStringField("bar"); + e.setRelated(related); + return e; + } + + protected void modifyInstance(UnenhancedPropertyAccessSubclass e) { + e.getRelated().setStringField("modified"); + } + + protected Object getModifiedValue(UnenhancedPropertyAccessSubclass e) { + return e.getRelated().getStringField(); + } + + @Override + protected int graphSize() { + return 2; + } +} \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/ManyOneEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/ManyOneEntity.java index 9c19e509b..0eb18327f 100755 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/ManyOneEntity.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/ManyOneEntity.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.persistence.query; +import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -29,7 +30,7 @@ import javax.persistence.Version; @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) -public class ManyOneEntity { +public class ManyOneEntity implements Serializable { @Id @GeneratedValue diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/SimpleEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/SimpleEntity.java index 706801da5..ba15b9729 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/SimpleEntity.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/SimpleEntity.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.persistence.query; +import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; @@ -43,7 +44,7 @@ import javax.persistence.Table; @FieldResult(name = "value", column = "VALUE") })) @Entity(name = "simple") @Table(name = "SIMPLE_ENTITY") -public class SimpleEntity { +public class SimpleEntity implements Serializable { @Id @GeneratedValue diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java index 328c7dd9c..a06389927 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java @@ -18,6 +18,15 @@ */ package org.apache.openjpa.persistence; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; @@ -30,6 +39,9 @@ import javax.persistence.Query; import org.apache.commons.lang.StringUtils; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.ee.ManagedRuntime; +import org.apache.openjpa.enhance.PCEnhancer; +import org.apache.openjpa.enhance.PCRegistry; +import org.apache.openjpa.kernel.AbstractBrokerFactory; import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.kernel.DelegatingBroker; import org.apache.openjpa.kernel.FindCallbacks; @@ -40,8 +52,8 @@ import org.apache.openjpa.kernel.QueryFlushModes; import org.apache.openjpa.kernel.QueryLanguages; import org.apache.openjpa.kernel.Seq; import org.apache.openjpa.kernel.jpql.JPQLParser; -import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Closeable; +import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.QueryMetaData; @@ -59,30 +71,36 @@ import org.apache.openjpa.util.UserException; * @nojavadoc */ public class EntityManagerImpl - implements OpenJPAEntityManagerSPI, + implements OpenJPAEntityManagerSPI, Externalizable, FindCallbacks, OpCallbacks, Closeable, OpenJPAEntityTransaction { private static final Localizer _loc = Localizer.forPackage (EntityManagerImpl.class); - - private final DelegatingBroker _broker; - private final EntityManagerFactoryImpl _emf; - private FetchPlan _fetch = null; private static final Object[] EMPTY_OBJECTS = new Object[0]; + private DelegatingBroker _broker; + private EntityManagerFactoryImpl _emf; + private FetchPlan _fetch = null; + private RuntimeExceptionTranslator ret = PersistenceExceptions.getRollbackTranslator(this); + public EntityManagerImpl() { + // for Externalizable + } + /** * Constructor; supply factory and delegate. */ public EntityManagerImpl(EntityManagerFactoryImpl factory, Broker broker) { + initialize(factory, broker); + } + + private void initialize(EntityManagerFactoryImpl factory, Broker broker) { _emf = factory; - RuntimeExceptionTranslator translator = - PersistenceExceptions.getRollbackTranslator(this); - _broker = new DelegatingBroker(broker, translator); - _broker.setImplicitBehavior(this, translator); + _broker = new DelegatingBroker(broker, ret); + _broker.setImplicitBehavior(this, ret); } /** @@ -1180,4 +1198,144 @@ public class EntityManagerImpl return false; return _broker.equals(((EntityManagerImpl) other)._broker); } + + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException { + try { + ret = PersistenceExceptions.getRollbackTranslator(this); + + // this assumes that serialized Brokers are from something + // that extends AbstractBrokerFactory. + Object factoryKey = in.readObject(); + AbstractBrokerFactory factory = + AbstractBrokerFactory.getPooledFactoryForKey(factoryKey); + byte[] brokerBytes = (byte[]) in.readObject(); + ObjectInputStream innerIn = new BrokerBytesInputStream(brokerBytes, + factory.getConfiguration()); + + Broker broker = (Broker) innerIn.readObject(); + EntityManagerFactoryImpl emf = (EntityManagerFactoryImpl) + JPAFacadeHelper.toEntityManagerFactory( + broker.getBrokerFactory()); + broker.putUserObject(JPAFacadeHelper.EM_KEY, this); + initialize(emf, broker); + } catch (RuntimeException re) { + try { + re = ret.translate(re); + } catch (Exception e) { + // ignore + } + throw re; + } + } + + public void writeExternal(ObjectOutput out) throws IOException { + try { + // this requires that only AbstractBrokerFactory-sourced + // brokers can be serialized + Object factoryKey = ((AbstractBrokerFactory) _broker + .getBrokerFactory()).getPoolKey(); + out.writeObject(factoryKey); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream innerOut = new ObjectOutputStream(baos); + innerOut.writeObject(_broker.getDelegate()); + innerOut.flush(); + out.writeObject(baos.toByteArray()); + } catch (RuntimeException re) { + try { + re = ret.translate(re); + } catch (Exception e) { + // ignore + } + throw re; + } + } + + private static class BrokerBytesInputStream extends ObjectInputStream { + + private OpenJPAConfiguration conf; + + BrokerBytesInputStream(byte[] bytes, OpenJPAConfiguration conf) + throws IOException { + super(new ByteArrayInputStream(bytes)); + if (conf == null) + throw new IllegalArgumentException( + "Illegal null argument to ObjectInputStreamWithLoader"); + this.conf = conf; + } + + /** + * Make a primitive array class + */ + private Class primitiveType(char type) { + switch (type) { + case 'B': return byte.class; + case 'C': return char.class; + case 'D': return double.class; + case 'F': return float.class; + case 'I': return int.class; + case 'J': return long.class; + case 'S': return short.class; + case 'Z': return boolean.class; + default: return null; + } + } + + protected Class resolveClass(ObjectStreamClass classDesc) + throws IOException, ClassNotFoundException { + + String cname = classDesc.getName(); + if (cname.startsWith("[")) { + // An array + Class component; // component class + int dcount; // dimension + for (dcount=1; cname.charAt(dcount)=='['; dcount++) ; + if (cname.charAt(dcount) == 'L') { + component = lookupClass(cname.substring(dcount+1, + cname.length()-1)); + } else { + if (cname.length() != dcount+1) { + throw new ClassNotFoundException(cname);// malformed + } + component = primitiveType(cname.charAt(dcount)); + } + int dim[] = new int[dcount]; + for (int i=0; i