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 c6915ba678..cbfa1f8ae3 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 @@ -3094,7 +3094,7 @@ public OpenJPAStateManager embed(Object obj, Object id, // embedded copy mapping so that getStateManagerImpl can find // the SM when the owner's enhanced field provides the raw record if (obj != null && meta.isRecord()) { - ImplHelper._unenhancedInstanceMap.put(obj, copy); + ImplHelper.registerUnenhancedInstance(obj, copy); } return sm; 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 fbac498907..4b9e827a8c 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 @@ -61,8 +61,15 @@ public class ImplHelper { private static final Map _assignableTypes = new ConcurrentReferenceHashMap(ReferenceStrength.WEAK, ReferenceStrength.HARD); - // map of all new unenhanced instances active in this classloader - public static final Map _unenhancedInstanceMap = + /** + * Map of all new unenhanced instances active in this classloader, keyed + * by object identity. Keys are held weakly, values strongly. + *
+ * Not public: register through
+ * {@link #registerUnenhancedInstance(Object, PersistenceCapable)} and
+ * resolve through {@link #toPersistenceCapable(Object, Object)}.
+ */
+ static final Map _unenhancedInstanceMap =
new ConcurrentReferenceHashMap(ReferenceStrength.WEAK, ReferenceStrength.HARD) {
@Override
@@ -313,14 +320,43 @@ else if (ctx instanceof StateManager
return null;
pc = new ReflectingPersistenceCapable(o, conf);
- _unenhancedInstanceMap.put(o, pc);
+ registerUnenhancedInstance(o, pc);
return pc;
}
}
+ /**
+ * Registers the {@link PersistenceCapable} instance that manages the state
+ * of the given raw, unenhanced instance, so that subsequent calls to
+ * {@link #toPersistenceCapable(Object, Object)} for that instance resolve
+ * to pc instead of creating a new
+ * {@link org.apache.openjpa.enhance.ReflectingPersistenceCapable}.
+ *
+ * Instances are keyed by identity rather than by {@link Object#equals},
+ * so value-based types such as records can be used as keys. Any mapping
+ * already present for the instance is replaced. pc is not
+ * required to report instance as its managed instance; for a
+ * record embeddable it is the embedded copy carrying the state manager.
+ *
+ * @param instance the raw, unenhanced managed instance
+ * @param pc the persistence-capable instance to resolve it to
+ * @since 4.2.0
+ */
+ public static void registerUnenhancedInstance(Object instance,
+ PersistenceCapable pc) {
+ _unenhancedInstanceMap.put(instance, pc);
+ }
+
+ /**
+ * Re-registers a {@link ReflectingPersistenceCapable} against the
+ * instance it manages, for example after deserialization.
+ *
+ * @param pc the persistence-capable instance to register
+ * @see #registerUnenhancedInstance(Object, PersistenceCapable)
+ */
public static void registerPersistenceCapable(
ReflectingPersistenceCapable pc) {
- _unenhancedInstanceMap.put(pc.getManagedInstance(), pc);
+ registerUnenhancedInstance(pc.getManagedInstance(), pc);
}
/**
diff --git a/openjpa-kernel/src/test/java/org/apache/openjpa/util/TestImplHelperUnenhancedInstance.java b/openjpa-kernel/src/test/java/org/apache/openjpa/util/TestImplHelperUnenhancedInstance.java
new file mode 100644
index 0000000000..17a8c26244
--- /dev/null
+++ b/openjpa-kernel/src/test/java/org/apache/openjpa/util/TestImplHelperUnenhancedInstance.java
@@ -0,0 +1,102 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.apache.openjpa.enhance.PersistenceCapable;
+import org.junit.Test;
+
+/**
+ * Pins the registry contract of
+ * {@link ImplHelper#registerUnenhancedInstance(Object, PersistenceCapable)}.
+ *
+ * The registrations are read back through the package private registry itself: + * resolving them through {@link ImplHelper#toPersistenceCapable(Object, Object)} + * would need a configuration and manageable metadata, which is beyond a unit + * test here. + */ +public class TestImplHelperUnenhancedInstance { + + record Point(int x, int y) { + } + + /** + * Records are the reason this registry exists, and two equal records must + * not share one registration. + */ + @Test + public void testKeysByIdentityNotEquality() { + Point p1 = new Point(3, 4); + Point p2 = new Point(3, 4); + assertEquals(p1, p2); + + PersistenceCapable pc1 = newPersistenceCapable(); + PersistenceCapable pc2 = newPersistenceCapable(); + ImplHelper.registerUnenhancedInstance(p1, pc1); + ImplHelper.registerUnenhancedInstance(p2, pc2); + + assertSame(pc1, ImplHelper._unenhancedInstanceMap.get(p1)); + assertSame(pc2, ImplHelper._unenhancedInstanceMap.get(p2)); + assertNull(ImplHelper._unenhancedInstanceMap.get(new Point(3, 4))); + } + + @Test + public void testLastRegistrationWins() { + Point p = new Point(5, 6); + PersistenceCapable pcA = newPersistenceCapable(); + PersistenceCapable pcB = newPersistenceCapable(); + + ImplHelper.registerUnenhancedInstance(p, pcA); + ImplHelper.registerUnenhancedInstance(p, pcB); + assertSame(pcB, ImplHelper._unenhancedInstanceMap.get(p)); + } + + /** + * A do-nothing {@link PersistenceCapable} stub; no mock framework is + * available on this module's test classpath. + */ + private static PersistenceCapable newPersistenceCapable() { + InvocationHandler handler = new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + switch (method.getName()) { + case "equals": + return proxy == args[0]; + case "hashCode": + return System.identityHashCode(proxy); + case "toString": + return "PersistenceCapable stub"; + default: + return null; + } + } + }; + return (PersistenceCapable) Proxy.newProxyInstance( + PersistenceCapable.class.getClassLoader(), + new Class>[]{ PersistenceCapable.class }, handler); + } +}