From 0071f7b0d8e98d9892283a4579acdab1ba4cc0b1 Mon Sep 17 00:00:00 2001 From: Patrick Linskey Date: Fri, 16 Nov 2007 01:09:26 +0000 Subject: [PATCH] OPENJPA-316 git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@595510 13f79535-47bb-0310-9956-ffa450edef68 --- .../enhance/ManagedClassSubclasser.java | 24 +++- .../openjpa/enhance/DynamicPCHelper.java | 30 ----- .../openjpa/enhance/RedefinitionHelper.java | 49 ++++++++ .../openjpa/kernel/StateManagerImpl.java | 24 +++- openjpa-persistence-jdbc/pom.xml | 1 + .../enhance/TestUnenhancedOneToMany.java | 118 ++++++++++++++++++ .../openjpa/enhance/UnenhancedMany.java | 69 ++++++++++ .../apache/openjpa/enhance/UnenhancedOne.java | 67 ++++++++++ 8 files changed, 346 insertions(+), 36 deletions(-) delete mode 100644 openjpa-kernel/src/main/java/org/apache/openjpa/enhance/DynamicPCHelper.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/TestUnenhancedOneToMany.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedMany.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedOne.java diff --git a/openjpa-kernel-5/src/main/java/org/apache/openjpa/enhance/ManagedClassSubclasser.java b/openjpa-kernel-5/src/main/java/org/apache/openjpa/enhance/ManagedClassSubclasser.java index d0365e201..bd79c85cf 100644 --- a/openjpa-kernel-5/src/main/java/org/apache/openjpa/enhance/ManagedClassSubclasser.java +++ b/openjpa-kernel-5/src/main/java/org/apache/openjpa/enhance/ManagedClassSubclasser.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.enhance; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -34,6 +35,7 @@ import org.apache.openjpa.lib.log.Log; import org.apache.openjpa.lib.util.BytecodeWriter; import org.apache.openjpa.lib.util.JavaVersions; import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.lib.util.Files; import org.apache.openjpa.lib.util.Localizer.Message; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; @@ -249,10 +251,14 @@ public class ManagedClassSubclasser { // but do set the metadata accordingly. if (enhancer.isAlreadyRedefined()) ints.add(bc.getType()); - else if (JavaVersions.VERSION >= 5) + else if (JavaVersions.VERSION >= 5) { map.put(bc.getType(), bc.toByteArray()); + debugBytecodes(bc); + } } else { if (!enhancer.isAlreadySubclassed()) { + debugBytecodes(bc); + // this is the new subclass ClassLoader loader = GeneratedClasses.getMostDerivedLoader( cls, PersistenceCapable.class); @@ -261,6 +267,22 @@ public class ManagedClassSubclasser { } } + private static void debugBytecodes(BCClass bc) throws IOException { + // Write the bytecodes to disk for debugging purposes. + if ("true".equals(System.getProperty( + ManagedClassSubclasser.class.getName() + ".dumpBytecodes"))) + { + File tmp = new File(System.getProperty("java.io.tmpdir")); + File dir = new File(tmp, "openjpa"); + dir = new File(dir, "pcsubclasses"); + dir.mkdirs(); + dir = Files.getPackageFile(dir, bc.getPackageName(), true); + File f = new File(dir, bc.getClassName() + ".class"); + System.err.println("Writing to " + f); + bc.write(f); + } + } + private static void setIntercepting(OpenJPAConfiguration conf, ClassLoader envLoader, Class cls) { ClassMetaData meta = conf.getMetaDataRepositoryInstance() diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/DynamicPCHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/DynamicPCHelper.java deleted file mode 100644 index ed413dcd2..000000000 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/DynamicPCHelper.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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; - -/** - * Helper methods for dynamically-redefined managed types. - * - * @since 1.0.0 - */ -public class DynamicPCHelper { - public static boolean isDetached(Object o) { - throw new UnsupportedOperationException(); - } -} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/RedefinitionHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/RedefinitionHelper.java index ecf91ea3c..85ecedbf1 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/RedefinitionHelper.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/RedefinitionHelper.java @@ -18,8 +18,15 @@ */ package org.apache.openjpa.enhance; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.lang.reflect.Method; +import java.lang.reflect.Field; + import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.StateManagerImpl; +import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.util.ImplHelper; /** @@ -185,4 +192,46 @@ public class RedefinitionHelper { sm.settingObjectField(pc, idx, cur, next, OpenJPAStateManager.SET_USER); } + + /** + * Create a container instance that will delegate back to the state + * manager to emulate lazy loading. This is used by PC subclasses for + * unenhanced types that could not be redefined, and thus do not have + * field-interception capabilities. Do this for all collection and + * map field types, even if they are in the dfg, in case the fetch + * groups are reset at runtime. + * + * @since 1.1.0 + */ + public static void assignLazyLoadProxies(StateManagerImpl sm) { + FieldMetaData[] fmds = sm.getMetaData().getFields(); + for (int i = 0; i < fmds.length; i++) { + switch (fmds[i].getTypeCode()) { + case JavaTypes.COLLECTION: + case JavaTypes.MAP: + PersistenceCapable pc = sm.getPersistenceCapable(); + Field field = (Field) fmds[i].getBackingMember(); + Reflection.set(pc, field, + newLazyLoadingProxy(fmds[i].getDeclaredType(), i, sm)); + break; + } + } + } + + private static Object newLazyLoadingProxy(Class type, final int idx, + final StateManagerImpl sm) { + InvocationHandler handler = new InvocationHandler() { + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + // this will replace the field in the instance, so the dynamic + // proxy should only be called the first time a + // lazy-load-proxied field is used in normal usage. + Object delegate = sm.fetch(idx); + return method.invoke(delegate, args); + } + }; + return Proxy.newProxyInstance(type.getClassLoader(), + new Class[] { type }, handler); + } } 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 6d7486bb2..e0ea66031 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 @@ -21,16 +21,25 @@ package org.apache.openjpa.kernel; import java.io.IOException; import java.io.ObjectOutput; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.TimeZone; import org.apache.commons.lang.StringUtils; import org.apache.openjpa.conf.OpenJPAConfiguration; +import org.apache.openjpa.enhance.DynamicPersistenceCapable; import org.apache.openjpa.enhance.FieldManager; +import org.apache.openjpa.enhance.ManagedInstanceProvider; import org.apache.openjpa.enhance.PCRegistry; import org.apache.openjpa.enhance.PersistenceCapable; +import org.apache.openjpa.enhance.RedefinitionHelper; import org.apache.openjpa.enhance.StateManager; -import org.apache.openjpa.enhance.ManagedInstanceProvider; -import org.apache.openjpa.enhance.DynamicPersistenceCapable; import org.apache.openjpa.event.LifecycleEvent; import org.apache.openjpa.event.LifecycleEventManager; import org.apache.openjpa.lib.util.Localizer; @@ -43,6 +52,7 @@ import org.apache.openjpa.meta.ValueMetaData; import org.apache.openjpa.meta.ValueStrategies; import org.apache.openjpa.util.ApplicationIds; import org.apache.openjpa.util.Exceptions; +import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InvalidStateException; import org.apache.openjpa.util.ObjectNotFoundException; @@ -50,7 +60,6 @@ import org.apache.openjpa.util.OpenJPAId; import org.apache.openjpa.util.ProxyManager; import org.apache.openjpa.util.RuntimeExceptionTranslator; import org.apache.openjpa.util.UserException; -import org.apache.openjpa.util.ImplHelper; import serp.util.Numbers; /** @@ -306,8 +315,11 @@ public class StateManagerImpl // if this is a non-tracking PC, add a hard ref to the appropriate data // sets and give it an opportunity to make a state snapshot. - if (!isIntercepting()) + if (!isIntercepting()) { saveFields(true); + if (!isNew()) + RedefinitionHelper.assignLazyLoadProxies(this); + } } /** @@ -315,6 +327,8 @@ public class StateManagerImpl * from {@link ClassMetaData#isIntercepting()} in that it checks for * property access + subclassing in addition to the redefinition / * enhancement checks. + * + * @since 1.0.0 */ public boolean isIntercepting() { if (getMetaData().isIntercepting()) diff --git a/openjpa-persistence-jdbc/pom.xml b/openjpa-persistence-jdbc/pom.xml index f11ca2dda..b41043b0b 100644 --- a/openjpa-persistence-jdbc/pom.xml +++ b/openjpa-persistence-jdbc/pom.xml @@ -398,6 +398,7 @@ + diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/TestUnenhancedOneToMany.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/TestUnenhancedOneToMany.java new file mode 100644 index 000000000..9db55d11d --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/TestUnenhancedOneToMany.java @@ -0,0 +1,118 @@ +/* + * 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 java.lang.reflect.Proxy; +import java.util.Collection; + +import org.apache.openjpa.persistence.test.SingleEMTestCase; + +public class TestUnenhancedOneToMany extends SingleEMTestCase { + + public void setUp() { + setUp(UnenhancedOne.class, UnenhancedMany.class, CLEAR_TABLES); + } + + public void testOneToMany() throws Exception { + assertFalse(PersistenceCapable.class.isAssignableFrom( + UnenhancedOne.class)); + assertFalse(PersistenceCapable.class.isAssignableFrom( + UnenhancedMany.class)); + + em.getTransaction().begin(); + + UnenhancedOne one = new UnenhancedOne(1000); + + UnenhancedMany manyA = new UnenhancedMany(1); + one.getMany().add(manyA); + manyA.setOne(one); + + UnenhancedMany manyB = new UnenhancedMany(2); + one.getMany().add(manyB); + manyB.setOne(one); + + UnenhancedMany manyC = new UnenhancedMany(3); + one.getMany().add(manyC); + manyC.setOne(one); + + // em should not know about our entities + assertFalse(em.contains(one)); + assertFalse(em.contains(manyA)); + assertFalse(em.contains(manyB)); + assertFalse(em.contains(manyC)); + + // persist the entity + em.persist(one); + em.persist(manyA); + em.persist(manyB); + em.persist(manyC); + em.flush(); + + // em should now be aware of our entity + assertTrue(em.contains(one)); + assertTrue(em.contains(manyA)); + assertTrue(em.contains(manyB)); + assertTrue(em.contains(manyC)); + + em.getTransaction().commit(); + + // recreate entity manager to avoid caching + one = null; + manyA = null; + manyB = null; + manyC = null; + em.close(); + em = emf.createEntityManager(); + em.getTransaction().begin(); + + // reload one + one = em.find(UnenhancedOne.class, 1000); + assertNotNull("one is null", one); + + // verify one.getMany(); ensure that it's a dynamic proxy before + // it is accessed + assertTrue(Proxy.isProxyClass(one.many.getClass())); + assertNotNull("one.getMany() is null", one.getMany()); + Collection many = one.getMany(); + assertEquals(3, many.size()); + + // reload the many + manyA = em.find(UnenhancedMany.class, 1); + assertNotNull("manyA is null", manyA); + manyB = em.find(UnenhancedMany.class, 2); + assertNotNull("manyB is null", manyA); + manyC = em.find(UnenhancedMany.class, 3); + assertNotNull("manyc is null", manyA); + + // verify many.getOne() + assertNotNull("manyA.getOne() is null", manyA.getOne()); + assertEquals(one, manyA.getOne()); + assertNotNull("manyB.getOne() is null", manyB.getOne()); + assertEquals(one, manyB.getOne()); + assertNotNull("manyC.getOne() is null", manyC.getOne()); + assertEquals(one, manyC.getOne()); + + // verify collection contains each many + assertTrue(many.contains(manyA)); + assertTrue(many.contains(manyB)); + assertTrue(many.contains(manyC)); + + em.getTransaction().commit(); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedMany.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedMany.java new file mode 100644 index 000000000..9b2b052a0 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedMany.java @@ -0,0 +1,69 @@ +/** + * + * 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 javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import java.io.Serializable; + +@Entity +public class UnenhancedMany implements Serializable, Cloneable { + private static final long serialVersionUID = 4041356744771116705L; + + @Id + private int id; + + @ManyToOne + private UnenhancedOne one; + + public UnenhancedMany() { + } + + public UnenhancedMany(int id) { + this.id = id; + } + + public long getId() { + return id; + } + + public UnenhancedOne getOne() { + return one; + } + + public void setOne(UnenhancedOne one) { + this.one = one; + } + + public boolean equals(Object o) { + if (o == this) return true; + if (o == null) return false; + if (!getClass().isAssignableFrom(o.getClass())) return false; + + return id == ((UnenhancedMany) o).id; + } + + public int hashCode() { + return id; + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedOne.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedOne.java new file mode 100644 index 000000000..a96444522 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/enhance/UnenhancedOne.java @@ -0,0 +1,67 @@ +/** + * + * 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 javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; + +@Entity +public class UnenhancedOne implements Serializable, Cloneable { + private static final long serialVersionUID = -5834998517804641711L; + + @Id + private int id; + + @OneToMany + Collection many = new HashSet(); + + public UnenhancedOne() { + } + + public UnenhancedOne(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public Collection getMany() { + return many; + } + + public boolean equals(Object o) { + if (o == this) return true; + if (o == null) return false; + if (!getClass().isAssignableFrom(o.getClass())) return false; + + return id == ((UnenhancedOne) o).id; + } + + public int hashCode() { + return id; + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } +}