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