From 38f4b531b17db165fae6d401bdcc9661e437f0c5 Mon Sep 17 00:00:00 2001 From: Jurriaan Pruys Date: Mon, 10 Jun 2013 10:40:36 +0200 Subject: [PATCH 1/6] Test to demonstrate circular serialization issues. Deserialization of objects with mybatis proxies fails when having circular references. --- .../submitted/serializecircular/Child.java | 45 +++++++++++ .../serializecircular/ChildMapper.java | 25 ++++++ .../serializecircular/ChildMapper.xml | 57 ++++++++++++++ .../submitted/serializecircular/CreateDB.sql | 20 +++++ .../MapperConfigWithAggressiveLazyLoading.xml | 2 + ...pperConfigWithoutAggressiveLazyLoading.xml | 2 + .../submitted/serializecircular/Parent.java | 46 +++++++++++ .../serializecircular/ParentMapper.java | 22 ++++++ .../serializecircular/ParentMapper.xml | 47 ++++++++++++ .../SerializeCircularTest.java | 76 +++++++++++++------ .../serializecircular/UtilityTester.java | 9 +-- 11 files changed, 320 insertions(+), 31 deletions(-) create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java new file mode 100644 index 00000000000..33cc47dc607 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + +import java.io.Serializable; + +public class Child implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private Parent parent; + private Person person; + + public Integer getId() { + return id; + } + public void setId(Integer aId) { + id = aId; + } + public Parent getParent() { + return parent; + } + public void setParent(Parent aParent) { + parent = aParent; + } + public Person getPerson() { + return person; + } + public void setPerson(Person aPerson) { + person = aPerson; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java new file mode 100644 index 00000000000..eb751b990b2 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + +import java.util.List; + + +public interface ChildMapper { + + public List getByParentId(Integer aParentId); + public Child getById(Integer aChildId); +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml new file mode 100644 index 00000000000..c45c6fca0a7 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + Child.id, + Child.nr_parent_id, + Child.nr_person_id + + + Child + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql index f483ed918f2..de93b9f32bd 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql @@ -14,18 +14,38 @@ -- limitations under the License. -- +-- parent +drop table if exists parent; +create table parent (id int, nr_person_id int); + +insert into parent (id, nr_person_id) +values (1, 1); + +-- child +drop table if exists child; +create table child (id int, nr_parent_id int, nr_person_id int); + +insert into child (id, nr_parent_id, nr_person_id) +values (1, 1, 1); + +insert into child (id, nr_parent_id, nr_person_id) +values (2, 1, 1); + +-- person drop table if exists person; create table person (id int, nr_department int); insert into person (id, nr_department) values (1, 1); +-- productattribute drop table if exists productattribute; create table productattribute (nr_id int); insert into productattribute(nr_id) values (1); +-- department drop table if exists department; create table department (nr_id int,nr_attribute int,person int); diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml index b74c24337ab..a7fadf06b41 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml @@ -42,6 +42,8 @@ + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml index 5cebe10268e..7019d5312aa 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml @@ -42,6 +42,8 @@ + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java new file mode 100644 index 00000000000..6b81eb30328 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + +import java.io.Serializable; +import java.util.List; + +public class Parent implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private List children; + private Person person; + + public Integer getId() { + return id; + } + public void setId(Integer aId) { + id = aId; + } + public List getChildren() { + return children; + } + public void setChildren(List aChildren) { + children = aChildren; + } + public Person getPerson() { + return person; + } + public void setPerson(Person aPerson) { + person = aPerson; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java new file mode 100644 index 00000000000..ae89357db37 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + + +public interface ParentMapper { + + public Parent getById(Integer anId); +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml new file mode 100644 index 00000000000..f99fdd29eba --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + Parent.id, + Parent.nr_person_id + + + Parent + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java index 642db16494c..eec8260a206 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java @@ -15,24 +15,52 @@ */ package org.apache.ibatis.submitted.serializecircular; -import java.io.IOException; import java.io.Reader; import java.sql.Connection; -import java.sql.SQLException; import org.apache.ibatis.io.Resources; import org.apache.ibatis.jdbc.ScriptRunner; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; -import org.junit.Ignore; import org.junit.Test; -//@Ignore("see issue #614") +// see issue #614 public class SerializeCircularTest { @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() + public void serializeAndDeserializeListWithoutAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeListWithAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + private void serializeAndDeserializeList(SqlSession sqlSession) + throws Exception { + ParentMapper parentMapper = sqlSession.getMapper(ParentMapper.class); + Parent parent = parentMapper.getById(1); + Child child = parent.getChildren().get(0); + + UtilityTester.serializeAndDeserializeObject(child); + } + + @Test + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); try { @@ -41,9 +69,9 @@ public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloa sqlSession.close(); } } - + @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); try { @@ -52,10 +80,10 @@ public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadin sqlSession.close(); } } - -// @Ignore("See http://code.google.com/p/mybatis/issues/detail?id=614") + + //See http://code.google.com/p/mybatis/issues/detail?id=614 @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); try { @@ -65,9 +93,9 @@ public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPre sqlSession.close(); } } - + @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); try { @@ -76,11 +104,11 @@ public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloa sqlSession.close(); } } - + private SqlSession createSessionWithoutAggressiveLazyLoading() throws Exception { return createSession(false); } - + private SqlSession createSessionWithAggressiveLazyLoading() throws Exception { return createSession(true); } @@ -94,32 +122,32 @@ private SqlSession createSession(boolean anAggressiveLazyLoading) throws Excepti return sqlSession; } - private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) { + private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) throws Exception { testSerialize(sqlSession, true); } - private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) { + private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) throws Exception { testSerialize(sqlSession, false); } - - private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) { + + private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) throws Exception { DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); Department department = departmentMapper.getById(1); if (aPreloadAttribute) { department.getAttribute(); } - + serializeAndDeserializeObject(department); - + // This call results in problems when deserializing department department.getPerson(); serializeAndDeserializeObject(department); } - - protected void serializeAndDeserializeObject(Object anObject) { + + protected void serializeAndDeserializeObject(Object anObject) throws Exception { UtilityTester.serializeAndDeserializeObject(anObject); } - + private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws Exception { Reader configReader = Resources.getResourceAsReader(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configReader); @@ -131,7 +159,7 @@ private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws return sqlSessionFactory; } - private static void initDb(Connection conn) throws IOException, SQLException { + private static void initDb(Connection conn) throws Exception { try { Reader scriptReader = Resources .getResourceAsReader("org/apache/ibatis/submitted/serializecircular/CreateDB.sql"); diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java index a029e74d7d9..47b398e8918 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java @@ -22,14 +22,9 @@ import java.io.ObjectOutputStream; public class UtilityTester { - - public static void serializeAndDeserializeObject(Object myObject){ - try { - deserialzeObject(serializeObject(myObject)); - } catch (IOException e) { - System.out.println("Exception: " + e.toString()); - } + public static void serializeAndDeserializeObject(Object myObject) throws Exception{ + deserialzeObject(serializeObject(myObject)); } private static byte[] serializeObject(Object myObject) throws IOException { From 736675fc3198aca6d6ad031f91a6bcc30af27ccf Mon Sep 17 00:00:00 2001 From: Franta Mejta Date: Fri, 7 Mar 2014 17:08:24 +0100 Subject: [PATCH 2/6] Deserializing cyclic object graphs #59 --- .../AbstractEnhancedDeserializationProxy.java | 24 +- .../loader/AbstractSerialStateHolder.java | 187 +++++++++++-- .../cglib/CglibDeserializationProxy.java | 53 ++++ .../loader/cglib/CglibProxyFactory.java | 40 +-- .../loader/cglib/CglibSerialStateHolder.java | 28 +- .../JavassistDeserializationProxy.java | 52 ++++ .../javassist/JavassistProxyFactory.java | 41 +-- .../javassist/JavassistSerialStateHolder.java | 38 ++- ...pperConfigWithoutAggressiveLazyLoading.xml | 3 +- .../SerializeCircularTest.java | 262 ++++++++++-------- .../serializecircular/UtilityTester.java | 75 ++--- 11 files changed, 548 insertions(+), 255 deletions(-) create mode 100644 src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java create mode 100644 src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java diff --git a/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java b/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java index 6c014978452..7ad43be4886 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java +++ b/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java @@ -16,6 +16,7 @@ package org.apache.ibatis.executor.loader; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; @@ -30,11 +31,11 @@ public abstract class AbstractEnhancedDeserializationProxy { protected static final String FINALIZE_METHOD = "finalize"; protected static final String WRITE_REPLACE_METHOD = "writeReplace"; - private Class type; - private Map unloadedProperties; - private ObjectFactory objectFactory; - private List> constructorArgTypes; - private List constructorArgs; + private final Class type; + private final Map unloadedProperties; + private final ObjectFactory objectFactory; + private final List> constructorArgTypes; + private final List constructorArgs; private final Object reloadingPropertyLock; private boolean reloadingProperty; @@ -49,6 +50,16 @@ protected AbstractEnhancedDeserializationProxy(Class type, Map type, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + this.type = type; + this.unloadedProperties = unloaded; + this.objectFactory = factory; + this.constructorArgTypes = Arrays.asList(ctorTypes); + this.constructorArgs = Arrays.asList(ctorArgs); + this.reloadingPropertyLock = new Object(); + this.reloadingProperty = false; + } + public final Object invoke(Object enhanced, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); try { @@ -61,7 +72,7 @@ public final Object invoke(Object enhanced, Method method, Object[] args) throws } PropertyCopier.copyBeanProperties(type, enhanced, original); - return this.newSerialStateHolder(original, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + return this.newSerialStateHolder(enhanced, original, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } else { synchronized (this.reloadingPropertyLock) { if (!FINALIZE_METHOD.equals(methodName) && PropertyNamer.isProperty(methodName) && !reloadingProperty) { @@ -94,6 +105,7 @@ public final Object invoke(Object enhanced, Method method, Object[] args) throws } protected abstract AbstractSerialStateHolder newSerialStateHolder( + Object enhanced, Object userBean, Map unloadedProperties, ObjectFactory objectFactory, diff --git a/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java b/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java index 4361a499123..d7faaf7dd11 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java +++ b/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java @@ -26,17 +26,39 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; - +import java.util.Set; import org.apache.ibatis.reflection.factory.ObjectFactory; public abstract class AbstractSerialStateHolder implements Externalizable { - private static final long serialVersionUID = 8940388717901644661L; + private static final long serialVersionUID = 20140307; private static final ThreadLocal stream = new ThreadLocal(); + private static final ThreadLocal> handlesWrite = new ThreadLocal>() { + + @Override + protected Map initialValue() { + return new IdentityHashMap(); + } + + }; + private static final ThreadLocal> handlesRead = new ThreadLocal>() { + + @Override + protected Map initialValue() { + return new IdentityHashMap(); + } + + }; + private Object enhanced; private byte[] userBeanBytes = new byte[0]; private Object userBean; private Map unloadedProperties; @@ -48,11 +70,13 @@ public AbstractSerialStateHolder() { } public AbstractSerialStateHolder( + final Object enhanced, final Object userBean, final Map unloadedProperties, final ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { + this.enhanced = enhanced; this.userBean = userBean; this.unloadedProperties = new HashMap(unloadedProperties); this.objectFactory = objectFactory; @@ -60,28 +84,68 @@ public AbstractSerialStateHolder( this.constructorArgs = constructorArgs.toArray(new Object[constructorArgs.size()]); } + private Map findFields(final Object source) { + final Map map = new IdentityHashMap(); + for (Class c = source.getClass(); c != null; c = c.getSuperclass()) { + for (Field f : c.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + if (Modifier.isTransient(f.getModifiers())) { + continue; + } + + f.setAccessible(true); + try { + map.put(f, f.get(source)); + } catch (final IllegalAccessException ex) { + // no-op + } + } + } + + return map; + } + @Override public final void writeExternal(final ObjectOutput out) throws IOException { boolean firstRound = false; final ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream os = stream.get(); + Map hand = handlesWrite.get(); if (os == null) { os = new ObjectOutputStream(baos); firstRound = true; stream.set(os); } - os.writeObject(this.userBean); - os.writeObject(this.unloadedProperties); - os.writeObject(this.objectFactory); - os.writeObject(this.constructorArgTypes); - os.writeObject(this.constructorArgs); + final Integer handle = hand.get(enhanced); + hand.put(enhanced, handle == null ? hand.size() : handle); + + os.writeObject(userBean.getClass()); + os.writeObject(hand.get(enhanced)); + + if (handle == null) { + os.writeObject(this.unloadedProperties); + os.writeObject(this.constructorArgTypes); + os.writeObject(this.constructorArgs); + os.writeObject(this.objectFactory); + + final Map fields = findFields(userBean); + os.writeObject(fields.size()); + for (final Map.Entry e : fields.entrySet()) { + os.writeObject(e.getKey().getDeclaringClass()); + os.writeObject(e.getKey().getName()); + os.writeObject(e.getValue()); + } + } final byte[] bytes = baos.toByteArray(); out.writeObject(bytes); if (firstRound) { stream.remove(); + handlesWrite.remove(); } } @@ -91,11 +155,55 @@ public final void readExternal(final ObjectInput in) throws IOException, ClassNo if (data.getClass().isArray()) { this.userBeanBytes = (byte[]) data; } else { - this.userBean = data; + if (data instanceof Class == false) { + throw new IOException("Invalid stream. Unexpected " + data); + } + this.userBean = this.readUserBean((Class) data, in); + } + } + + @SuppressWarnings({"UseSpecificCatch", "BroadCatchBlock", "TooBroadCatch", "ThrowableInitCause"}) + private Object readUserBean(final Class type, final ObjectInput in) throws IOException, ClassNotFoundException { + final Integer handle = (Integer) in.readObject(); + final Map hand = handlesRead.get(); + if (hand.containsKey(handle)) { + return hand.get(handle); } + + @SuppressWarnings("unchecked") + final Map desUnloaded = (Map) in.readObject(); + final Class[] desCtorTypes = (Class[]) in.readObject(); + final Object[] desCtorArgs = (Object[]) in.readObject(); + final ObjectFactory desObjectFactory = (ObjectFactory) in.readObject(); + + final Object bean = desObjectFactory.create(type, Arrays.asList(desCtorTypes), Arrays.asList(desCtorArgs)); + final Object ubean = desUnloaded.isEmpty() ? bean : newDeserializationProxy(bean, desCtorTypes, desCtorArgs, desObjectFactory, desUnloaded); + hand.put(handle, ubean); + + final Integer fieldsCount = (Integer) in.readObject(); + for (int i = 0; i < fieldsCount; ++i) { + final Class c = (Class) in.readObject(); + final String n = (String) in.readObject(); + try { + final Field f = c.getDeclaredField(n); + + Object v = in.readObject(); + if (v instanceof AbstractSerialStateHolder) { + v = newCyclicReferenceMarker(f.getType(), desCtorTypes, desCtorArgs, (AbstractSerialStateHolder) v); + assert f.getType().isInstance(v) : "Marker not instance of " + f.getType(); + assert v instanceof CyclicReferenceMarker : "Marker not instanceof " + CyclicReferenceMarker.class; + } + + f.setAccessible(true); + f.set(bean, v); + } catch (final Exception ex) { + throw (IOException) new IOException(ex.getLocalizedMessage()).initCause(ex); + } + } + + return ubean; } - @SuppressWarnings("unchecked") protected final Object readResolve() throws ObjectStreamException { /* Second run */ if (this.userBean != null && this.userBeanBytes.length == 0) { @@ -105,24 +213,63 @@ protected final Object readResolve() throws ObjectStreamException { /* First run */ try { final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(this.userBeanBytes)); - this.userBean = in.readObject(); - this.unloadedProperties = (Map) in.readObject(); - this.objectFactory = (ObjectFactory) in.readObject(); - this.constructorArgTypes = (Class[]) in.readObject(); - this.constructorArgs = (Object[]) in.readObject(); + final Class userBeanType = (Class) in.readObject(); + + this.userBean = this.readUserBean(userBeanType, in); + this.replaceCyclicReferenceMarkers(this.userBean, new HashSet()); + + return this.userBean; } catch (final IOException ex) { throw (ObjectStreamException) new StreamCorruptedException().initCause(ex); } catch (final ClassNotFoundException ex) { throw (ObjectStreamException) new InvalidClassException(ex.getLocalizedMessage()).initCause(ex); + } finally { + handlesRead.remove(); } + } - final Map arrayProps = new HashMap(this.unloadedProperties); - final List> arrayTypes = Arrays.asList(this.constructorArgTypes); - final List arrayValues = Arrays.asList(this.constructorArgs); + private void replaceCyclicReferenceMarkers(final Object o, final Set set) { + for (final Map.Entry e : findFields(o).entrySet()) { + final Object value = e.getValue(); + if (value == null) { + continue; + } - return this.createDeserializationProxy(userBean, arrayProps, objectFactory, arrayTypes, arrayValues); + if (value instanceof CyclicReferenceMarker) { + final Field f = e.getKey(); + f.setAccessible(true); + + final AbstractSerialStateHolder assh = ((CyclicReferenceMarker) e.getValue()).getSerialStateHolder(); + try { + f.set(o, assh.userBean); + } catch (final Exception ex) { + throw new IllegalStateException(ex); + } + } else if (value instanceof Collection) { + for (Object ce : ((Collection) value)) { + this.replaceCyclicReferenceMarkers(ce, set); + } + } else if (value instanceof Map) { + for (Map.Entry ce : ((Map) value).entrySet()) { + this.replaceCyclicReferenceMarkers(ce.getKey(), set); + this.replaceCyclicReferenceMarkers(ce.getValue(), set); + } + } else { + if (set.add(value)) { + this.replaceCyclicReferenceMarkers(value, set); + } + } + } } - protected abstract Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs); + protected abstract Object newDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, + ObjectFactory factory, Map unloaded); + + protected abstract Object newCyclicReferenceMarker(Class target, Class[] ctorTypes, Object[] ctorArgs, + AbstractSerialStateHolder ssh); + + protected static interface CyclicReferenceMarker { + + AbstractSerialStateHolder getSerialStateHolder(); + } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java new file mode 100644 index 00000000000..d9ba8dc4c43 --- /dev/null +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 MyBatis.org. + * + * Licensed 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.ibatis.executor.loader.cglib; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; +import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; +import org.apache.ibatis.executor.loader.ResultLoaderMap; +import org.apache.ibatis.reflection.factory.ObjectFactory; + +final class CglibDeserializationProxy extends AbstractEnhancedDeserializationProxy implements MethodInterceptor { + + private final Object target; + + public CglibDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + super(target.getClass(), ctorTypes, ctorArgs, factory, unloaded); + this.target = target; + } + + @Override + protected AbstractSerialStateHolder newSerialStateHolder(Object enhanced, Object userBean, Map unloadedProperties, + ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { + return new CglibSerialStateHolder(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + } + + @Override + public Object intercept(final Object enhanced, final Method method, final Object[] args, final MethodProxy methodProxy) throws Throwable { + final Object o = super.invoke(this.target, method, args); + if (o instanceof AbstractSerialStateHolder) { + return o; + } else { + return methodProxy.invoke(this.target, args); + } + } + +} diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java index 33ff90c2aa9..283ae15a40d 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java @@ -16,6 +16,7 @@ package org.apache.ibatis.executor.loader.cglib; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; @@ -26,8 +27,6 @@ import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; -import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; -import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ProxyFactory; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.loader.WriteReplaceInterface; @@ -58,8 +57,13 @@ public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configurati return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } + public Object createDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + final MethodInterceptor callback = new CglibDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); + return crateProxy(target.getClass(), callback, Arrays.asList(ctorTypes), Arrays.asList(ctorArgs)); + } + public Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + return this.createDeserializationProxy(target, constructorArgTypes.toArray(new Class[0]), constructorArgs.toArray(), objectFactory, unloadedProperties); } public void setProperties(Properties properties) { @@ -130,7 +134,7 @@ public Object intercept(Object enhanced, Method method, Object[] args, MethodPro } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { - return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); + return new CglibSerialStateHolder(enhanced, original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } @@ -154,32 +158,4 @@ public Object intercept(Object enhanced, Method method, Object[] args, MethodPro } } - private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor { - - private EnhancedDeserializationProxyImpl(Class type, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - - public static Object createProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - final Class type = target.getClass(); - EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); - PropertyCopier.copyBeanProperties(type, target, enhanced); - return enhanced; - } - - @Override - public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { - final Object o = super.invoke(enhanced, method, args); - return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invokeSuper(o, args); - } - - @Override - protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java index 009f1cab2ac..907dab1236e 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java @@ -17,7 +17,8 @@ import java.util.List; import java.util.Map; - +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.FixedValue; import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.reflection.factory.ObjectFactory; @@ -30,17 +31,34 @@ public CglibSerialStateHolder() { } public CglibSerialStateHolder( + final Object enhanced, final Object userBean, final Map unloadedProperties, final ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - super(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + super(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } @Override - protected Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new CglibProxyFactory().createDeserializationProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + protected Object newDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + return new CglibProxyFactory().createDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); } + + @Override + protected Object newCyclicReferenceMarker(final Class target, final Class[] ctorTypes, final Object[] ctorArgs, final AbstractSerialStateHolder ssh) { + final Enhancer enhancer = new Enhancer(); + enhancer.setCallback(new FixedValue() { + + @Override + public Object loadObject() throws Exception { + return ssh; + } + }); + enhancer.setSuperclass(target); + enhancer.setInterfaces(new Class[]{CyclicReferenceMarker.class}); + + return enhancer.create(ctorTypes, ctorArgs); + } + } diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java new file mode 100644 index 00000000000..599a7f2fc5b --- /dev/null +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 MyBatis.org. + * + * Licensed 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.ibatis.executor.loader.javassist; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import javassist.util.proxy.MethodHandler; +import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; +import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; +import org.apache.ibatis.executor.loader.ResultLoaderMap; +import org.apache.ibatis.reflection.factory.ObjectFactory; + +final class JavassistDeserializationProxy extends AbstractEnhancedDeserializationProxy implements MethodHandler { + + private final Object target; + + public JavassistDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + super(target.getClass(), ctorTypes, ctorArgs, factory, unloaded); + this.target = target; + } + + @Override + protected AbstractSerialStateHolder newSerialStateHolder(Object enhanced, Object userBean, Map unloadedProperties, + ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { + return new JavassistSerialStateHolder(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + } + + @Override + public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { + final Object o = super.invoke(this.target, thisMethod, args); + if (o instanceof AbstractSerialStateHolder) { + return o; + } else { + return thisMethod.invoke(this.target, args); + } + } + +} diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java index 5ac7e3ee018..136e99edc6f 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java @@ -16,6 +16,7 @@ package org.apache.ibatis.executor.loader.javassist; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; @@ -26,8 +27,6 @@ import javassist.util.proxy.ProxyFactory; import org.apache.ibatis.executor.ExecutorException; -import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; -import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.loader.WriteReplaceInterface; import org.apache.ibatis.io.Resources; @@ -57,8 +56,13 @@ public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configurati return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } + public Object createDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + final MethodHandler callback = new JavassistDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); + return crateProxy(target.getClass(), callback, Arrays.asList(ctorTypes), Arrays.asList(ctorArgs)); + } + public Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + return createDeserializationProxy(target, constructorArgTypes.toArray(new Class[0]), constructorArgs.toArray(), objectFactory, unloadedProperties); } public void setProperties(Properties properties) { @@ -132,7 +136,7 @@ public Object invoke(Object enhanced, Method method, Method methodProxy, Object[ } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { - return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); + return new JavassistSerialStateHolder(enhanced, original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } @@ -155,33 +159,4 @@ public Object invoke(Object enhanced, Method method, Method methodProxy, Object[ } } } - - private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodHandler { - - private EnhancedDeserializationProxyImpl(Class type, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - - public static Object createProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - final Class type = target.getClass(); - EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); - PropertyCopier.copyBeanProperties(type, target, enhanced); - return enhanced; - } - - @Override - public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { - final Object o = super.invoke(enhanced, method, args); - return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invoke(o, args); - } - - @Override - protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new JavassistSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java index 8d43dfb3296..3a3cb5afdd5 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java @@ -15,9 +15,12 @@ */ package org.apache.ibatis.executor.loader.javassist; +import java.lang.reflect.Method; import java.util.List; import java.util.Map; - +import javassist.util.proxy.MethodHandler; +import javassist.util.proxy.Proxy; +import javassist.util.proxy.ProxyFactory; import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.reflection.factory.ObjectFactory; @@ -30,17 +33,42 @@ public JavassistSerialStateHolder() { } public JavassistSerialStateHolder( + final Object enhanced, final Object userBean, final Map unloadedProperties, final ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - super(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + super(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } @Override - protected Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new JavassistProxyFactory().createDeserializationProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + protected Object newDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + return new JavassistProxyFactory().createDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); } + + @Override + protected Object newCyclicReferenceMarker(final Class target, final Class[] ctorTypes, final Object[] ctorArgs, final AbstractSerialStateHolder ssh) { + final ProxyFactory enhancer = new ProxyFactory(); + enhancer.setSuperclass(target); + enhancer.setInterfaces(new Class[]{CyclicReferenceMarker.class}); + + final Proxy enhanced; + try { + enhanced = (Proxy) enhancer.create(ctorTypes, ctorArgs); + } catch (final Exception ex) { + throw new IllegalStateException(ex); + } + + enhanced.setHandler(new MethodHandler() { + + @Override + public Object invoke(Object o, Method method, Method method1, Object[] os) throws Throwable { + return ssh; + } + }); + + return enhanced; + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml index 7019d5312aa..559877d4607 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml @@ -24,8 +24,9 @@ - + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java index eec8260a206..a41e258a188 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java @@ -20,133 +20,149 @@ import org.apache.ibatis.io.Resources; import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.Assert; import org.junit.Test; // see issue #614 public class SerializeCircularTest { - @Test - public void serializeAndDeserializeListWithoutAggressiveLazyLoading() - throws Exception { - SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); - try { - serializeAndDeserializeList(sqlSession); - } finally { - sqlSession.close(); - } - } - - @Test - public void serializeAndDeserializeListWithAggressiveLazyLoading() - throws Exception { - SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); - try { - serializeAndDeserializeList(sqlSession); - } finally { - sqlSession.close(); - } - } - - private void serializeAndDeserializeList(SqlSession sqlSession) - throws Exception { - ParentMapper parentMapper = sqlSession.getMapper(ParentMapper.class); - Parent parent = parentMapper.getById(1); - Child child = parent.getChildren().get(0); - - UtilityTester.serializeAndDeserializeObject(child); - } - - @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); - try { - testSerializeWithoutPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); - try { - testSerializeWithPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - //See http://code.google.com/p/mybatis/issues/detail?id=614 - @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); - try { - //expected problem with deserializing - testSerializeWithoutPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); - try { - testSerializeWithPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - private SqlSession createSessionWithoutAggressiveLazyLoading() throws Exception { - return createSession(false); - } - - private SqlSession createSessionWithAggressiveLazyLoading() throws Exception { - return createSession(true); - } - - private SqlSession createSession(boolean anAggressiveLazyLoading) throws Exception { - String xmlConfig = anAggressiveLazyLoading ? - "org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml": - "org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml"; - SqlSessionFactory sqlSessionFactory = getSqlSessionFactoryXmlConfig(xmlConfig); - SqlSession sqlSession = sqlSessionFactory.openSession(); - return sqlSession; - } - - private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) throws Exception { - testSerialize(sqlSession, true); - } - - private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) throws Exception { - testSerialize(sqlSession, false); - } - - private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) throws Exception { - DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); - Department department = departmentMapper.getById(1); - if (aPreloadAttribute) { - department.getAttribute(); - } - - serializeAndDeserializeObject(department); - - // This call results in problems when deserializing department - department.getPerson(); - serializeAndDeserializeObject(department); - } - - protected void serializeAndDeserializeObject(Object anObject) throws Exception { - UtilityTester.serializeAndDeserializeObject(anObject); - } + @Test + public void serializeAndDeserializeListWithoutAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + cfg = sqlSession.getConfiguration(); + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeListWithAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + private void serializeAndDeserializeList(SqlSession sqlSession) + throws Exception { + ParentMapper parentMapper = sqlSession.getMapper(ParentMapper.class); + Parent parent = parentMapper.getById(1); + Child child = parent.getChildren().get(0); + + Child deserialized = UtilityTester.serializeAndDeserializeObject(child); + Assert.assertNotNull(deserialized); + Assert.assertNotSame(child, deserialized); + Assert.assertEquals(child.getId(), deserialized.getId()); + Assert.assertNotNull(deserialized.getParent()); + Assert.assertEquals(child.getParent().getId(), deserialized.getParent().getId()); + Assert.assertSame(deserialized, deserialized.getParent().getChildren().get(0)); + + Person dp1 = child.getPerson(); + Assert.assertNotNull(dp1); + + Person dp2 = deserialized.getPerson(); + Assert.assertNotNull(dp2); + + } + + @Test + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + testSerializeWithoutPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + testSerializeWithPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + //See http://code.google.com/p/mybatis/issues/detail?id=614 + @Test + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + //expected problem with deserializing + testSerializeWithoutPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + testSerializeWithPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + private SqlSession createSessionWithoutAggressiveLazyLoading() throws Exception { + return createSession(false); + } + + private SqlSession createSessionWithAggressiveLazyLoading() throws Exception { + return createSession(true); + } + + private SqlSession createSession(boolean anAggressiveLazyLoading) throws Exception { + String xmlConfig = anAggressiveLazyLoading + ? "org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml" + : "org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml"; + SqlSessionFactory sqlSessionFactory = getSqlSessionFactoryXmlConfig(xmlConfig); + SqlSession sqlSession = sqlSessionFactory.openSession(); + return sqlSession; + } + + private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) throws Exception { + testSerialize(sqlSession, true); + } + + private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) throws Exception { + testSerialize(sqlSession, false); + } + + private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) throws Exception { + DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); + Department department = departmentMapper.getById(1); + if (aPreloadAttribute) { + department.getAttribute(); + } + + serializeAndDeserializeObject(department); + + // This call results in problems when deserializing department + department.getPerson(); + serializeAndDeserializeObject(department); + } + + protected void serializeAndDeserializeObject(Object anObject) throws Exception { + UtilityTester.serializeAndDeserializeObject(anObject); + } private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws Exception { Reader configReader = Resources.getResourceAsReader(resource); @@ -159,10 +175,16 @@ private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws return sqlSessionFactory; } + private static Configuration cfg; + + public static Configuration getConfiguration() { + return cfg; + } + private static void initDb(Connection conn) throws Exception { try { Reader scriptReader = Resources - .getResourceAsReader("org/apache/ibatis/submitted/serializecircular/CreateDB.sql"); + .getResourceAsReader("org/apache/ibatis/submitted/serializecircular/CreateDB.sql"); ScriptRunner runner = new ScriptRunner(conn); runner.setLogWriter(null); runner.setErrorLogWriter(null); diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java index 47b398e8918..12c5d08d434 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java @@ -23,38 +23,47 @@ public class UtilityTester { - public static void serializeAndDeserializeObject(Object myObject) throws Exception{ - deserialzeObject(serializeObject(myObject)); - } - - private static byte[] serializeObject(Object myObject) throws IOException { - try { - ByteArrayOutputStream myByteArrayOutputStream = new ByteArrayOutputStream(); - - // Serialize to a byte array - ObjectOutputStream myObjectOutputStream = new ObjectOutputStream(myByteArrayOutputStream) ; - myObjectOutputStream.writeObject(myObject); - myObjectOutputStream.close(); - - // Get the bytes of the serialized object - byte[] myResult = myByteArrayOutputStream.toByteArray(); - return myResult; - } catch (Exception anException) { - throw new RuntimeException("Problem serializing: " + anException.toString(), anException); - } - } - - private static Object deserialzeObject(byte[] aSerializedObject) { - try { - // Deserialize from a byte array - ObjectInputStream myObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(aSerializedObject)); - Object myResult = myObjectInputStream.readObject(); - myObjectInputStream.close(); - - return myResult; - } catch (Exception anException) { - throw new RuntimeException("Problem deserializing", anException); - } - } + public static T serializeAndDeserializeObject(Object myObject) throws Exception { + return deserialzeObject(serializeObject(myObject)); + } + private static byte[] serializeObject(Object myObject) throws IOException { + try { + ByteArrayOutputStream myByteArrayOutputStream = new ByteArrayOutputStream(); + + // Serialize to a byte array + ObjectOutputStream myObjectOutputStream = new ObjectOutputStream(myByteArrayOutputStream); + myObjectOutputStream.writeObject(myObject); + myObjectOutputStream.close(); + + // Get the bytes of the serialized object + byte[] myResult = myByteArrayOutputStream.toByteArray(); + return myResult; + } catch (final Exception ex) { + throw rethrow(ex); + } + } + + private static T deserialzeObject(byte[] aSerializedObject) { + try { + // Deserialize from a byte array + ObjectInputStream myObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(aSerializedObject)); + Object myResult = myObjectInputStream.readObject(); + myObjectInputStream.close(); + + return (T) myResult; + } catch (final Exception ex) { + throw rethrow(ex); + } + } + + @SuppressWarnings("unchecked") + private static RuntimeException rethrow(final Throwable t) { + UtilityTester.rethrow0(t); + return null; + } + @SuppressWarnings("unchecked") + private static RuntimeException rethrow0(final Throwable t) throws T { + throw (T) t; + } } From aa6a40d7215224be916c11655a4dab67ae0ce298 Mon Sep 17 00:00:00 2001 From: Jurriaan Pruys Date: Wed, 18 Jun 2014 14:46:50 +0200 Subject: [PATCH 3/6] Revert shouldSerizalizeADeserlizaliedProxy type-test, because there are no unloaded properties in the proxy. This means that the proxy can deserialize the wrapped Object. --- .../java/org/apache/ibatis/executor/loader/CglibProxyTest.java | 2 +- .../org/apache/ibatis/executor/loader/JavassistProxyTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java b/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java index 96b4ea6ffda..4f9a21c8bd3 100644 --- a/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java +++ b/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java @@ -65,7 +65,7 @@ public void shouldSerizalizeADeserlizaliedProxy() throws Exception { Object proxy = ((CglibProxyFactory)proxyFactory).createDeserializationProxy(author, new HashMap(), new DefaultObjectFactory(), new ArrayList>(), new ArrayList()); Author author2 = (Author) deserialize(serialize((Serializable) proxy)); assertEquals(author, author2); - assertFalse(author.getClass().equals(author2.getClass())); + assertTrue("author.class (" + author.getClass() + ") should be the same as author2.class (" + author2.getClass() + "), since the proxy does not have unloaded properties; it does not need to be proxied.", author.getClass().equals(author2.getClass())); } } diff --git a/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java b/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java index 90f94baf680..833fe359aaf 100644 --- a/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java +++ b/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java @@ -68,7 +68,8 @@ public void shouldSerizalizeADeserlizaliedProxy() throws Exception { Object proxy = ((JavassistProxyFactory)proxyFactory).createDeserializationProxy(author, new HashMap (), new DefaultObjectFactory(), new ArrayList>(), new ArrayList()); Author author2 = (Author) deserialize(serialize((Serializable) proxy)); assertEquals(author, author2); - assertFalse(author.getClass().equals(author2.getClass())); + assertTrue("author.class (" + author.getClass() + ") should be the same as author2.class (" + author2.getClass() + "), since the proxy does not have unloaded properties; it does not need to be proxied.", + author.getClass().equals(author2.getClass())); } } From 150c9cb5b650248ea56cc5356247761a40a3d1df Mon Sep 17 00:00:00 2001 From: Jurriaan Pruys Date: Mon, 10 Jun 2013 10:40:36 +0200 Subject: [PATCH 4/6] Test to demonstrate circular serialization issues. Deserialization of objects with mybatis proxies fails when having circular references. --- .../submitted/serializecircular/Child.java | 45 +++++++++++ .../serializecircular/ChildMapper.java | 25 ++++++ .../serializecircular/ChildMapper.xml | 57 ++++++++++++++ .../submitted/serializecircular/CreateDB.sql | 20 +++++ .../MapperConfigWithAggressiveLazyLoading.xml | 2 + ...pperConfigWithoutAggressiveLazyLoading.xml | 2 + .../submitted/serializecircular/Parent.java | 46 +++++++++++ .../serializecircular/ParentMapper.java | 22 ++++++ .../serializecircular/ParentMapper.xml | 47 ++++++++++++ .../SerializeCircularTest.java | 76 +++++++++++++------ .../serializecircular/UtilityTester.java | 9 +-- 11 files changed, 320 insertions(+), 31 deletions(-) create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java create mode 100644 src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java new file mode 100644 index 00000000000..33cc47dc607 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/Child.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + +import java.io.Serializable; + +public class Child implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private Parent parent; + private Person person; + + public Integer getId() { + return id; + } + public void setId(Integer aId) { + id = aId; + } + public Parent getParent() { + return parent; + } + public void setParent(Parent aParent) { + parent = aParent; + } + public Person getPerson() { + return person; + } + public void setPerson(Person aPerson) { + person = aPerson; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java new file mode 100644 index 00000000000..eb751b990b2 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + +import java.util.List; + + +public interface ChildMapper { + + public List getByParentId(Integer aParentId); + public Child getById(Integer aChildId); +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml new file mode 100644 index 00000000000..c45c6fca0a7 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ChildMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + Child.id, + Child.nr_parent_id, + Child.nr_person_id + + + Child + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql index f483ed918f2..de93b9f32bd 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/CreateDB.sql @@ -14,18 +14,38 @@ -- limitations under the License. -- +-- parent +drop table if exists parent; +create table parent (id int, nr_person_id int); + +insert into parent (id, nr_person_id) +values (1, 1); + +-- child +drop table if exists child; +create table child (id int, nr_parent_id int, nr_person_id int); + +insert into child (id, nr_parent_id, nr_person_id) +values (1, 1, 1); + +insert into child (id, nr_parent_id, nr_person_id) +values (2, 1, 1); + +-- person drop table if exists person; create table person (id int, nr_department int); insert into person (id, nr_department) values (1, 1); +-- productattribute drop table if exists productattribute; create table productattribute (nr_id int); insert into productattribute(nr_id) values (1); +-- department drop table if exists department; create table department (nr_id int,nr_attribute int,person int); diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml index b74c24337ab..a7fadf06b41 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml @@ -42,6 +42,8 @@ + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml index 5cebe10268e..7019d5312aa 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml @@ -42,6 +42,8 @@ + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java new file mode 100644 index 00000000000..6b81eb30328 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/Parent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + +import java.io.Serializable; +import java.util.List; + +public class Parent implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private List children; + private Person person; + + public Integer getId() { + return id; + } + public void setId(Integer aId) { + id = aId; + } + public List getChildren() { + return children; + } + public void setChildren(List aChildren) { + children = aChildren; + } + public Person getPerson() { + return person; + } + public void setPerson(Person aPerson) { + person = aPerson; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java new file mode 100644 index 00000000000..ae89357db37 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ibatis.submitted.serializecircular; + + +public interface ParentMapper { + + public Parent getById(Integer anId); +} diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml new file mode 100644 index 00000000000..f99fdd29eba --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/ParentMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + Parent.id, + Parent.nr_person_id + + + Parent + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java index 642db16494c..eec8260a206 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java @@ -15,24 +15,52 @@ */ package org.apache.ibatis.submitted.serializecircular; -import java.io.IOException; import java.io.Reader; import java.sql.Connection; -import java.sql.SQLException; import org.apache.ibatis.io.Resources; import org.apache.ibatis.jdbc.ScriptRunner; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; -import org.junit.Ignore; import org.junit.Test; -//@Ignore("see issue #614") +// see issue #614 public class SerializeCircularTest { @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() + public void serializeAndDeserializeListWithoutAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeListWithAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + private void serializeAndDeserializeList(SqlSession sqlSession) + throws Exception { + ParentMapper parentMapper = sqlSession.getMapper(ParentMapper.class); + Parent parent = parentMapper.getById(1); + Child child = parent.getChildren().get(0); + + UtilityTester.serializeAndDeserializeObject(child); + } + + @Test + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); try { @@ -41,9 +69,9 @@ public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloa sqlSession.close(); } } - + @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); try { @@ -52,10 +80,10 @@ public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadin sqlSession.close(); } } - -// @Ignore("See http://code.google.com/p/mybatis/issues/detail?id=614") + + //See http://code.google.com/p/mybatis/issues/detail?id=614 @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); try { @@ -65,9 +93,9 @@ public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPre sqlSession.close(); } } - + @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() throws Exception { SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); try { @@ -76,11 +104,11 @@ public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloa sqlSession.close(); } } - + private SqlSession createSessionWithoutAggressiveLazyLoading() throws Exception { return createSession(false); } - + private SqlSession createSessionWithAggressiveLazyLoading() throws Exception { return createSession(true); } @@ -94,32 +122,32 @@ private SqlSession createSession(boolean anAggressiveLazyLoading) throws Excepti return sqlSession; } - private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) { + private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) throws Exception { testSerialize(sqlSession, true); } - private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) { + private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) throws Exception { testSerialize(sqlSession, false); } - - private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) { + + private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) throws Exception { DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); Department department = departmentMapper.getById(1); if (aPreloadAttribute) { department.getAttribute(); } - + serializeAndDeserializeObject(department); - + // This call results in problems when deserializing department department.getPerson(); serializeAndDeserializeObject(department); } - - protected void serializeAndDeserializeObject(Object anObject) { + + protected void serializeAndDeserializeObject(Object anObject) throws Exception { UtilityTester.serializeAndDeserializeObject(anObject); } - + private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws Exception { Reader configReader = Resources.getResourceAsReader(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configReader); @@ -131,7 +159,7 @@ private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws return sqlSessionFactory; } - private static void initDb(Connection conn) throws IOException, SQLException { + private static void initDb(Connection conn) throws Exception { try { Reader scriptReader = Resources .getResourceAsReader("org/apache/ibatis/submitted/serializecircular/CreateDB.sql"); diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java index a029e74d7d9..47b398e8918 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java @@ -22,14 +22,9 @@ import java.io.ObjectOutputStream; public class UtilityTester { - - public static void serializeAndDeserializeObject(Object myObject){ - try { - deserialzeObject(serializeObject(myObject)); - } catch (IOException e) { - System.out.println("Exception: " + e.toString()); - } + public static void serializeAndDeserializeObject(Object myObject) throws Exception{ + deserialzeObject(serializeObject(myObject)); } private static byte[] serializeObject(Object myObject) throws IOException { From bda215f4cea76cc4a10c1ac5bc96e8af79a71e09 Mon Sep 17 00:00:00 2001 From: Franta Mejta Date: Fri, 7 Mar 2014 17:08:24 +0100 Subject: [PATCH 5/6] Deserializing cyclic object graphs #59 --- .../AbstractEnhancedDeserializationProxy.java | 24 +- .../loader/AbstractSerialStateHolder.java | 187 +++++++++++-- .../cglib/CglibDeserializationProxy.java | 53 ++++ .../loader/cglib/CglibProxyFactory.java | 40 +-- .../loader/cglib/CglibSerialStateHolder.java | 28 +- .../JavassistDeserializationProxy.java | 52 ++++ .../javassist/JavassistProxyFactory.java | 41 +-- .../javassist/JavassistSerialStateHolder.java | 38 ++- ...pperConfigWithoutAggressiveLazyLoading.xml | 3 +- .../SerializeCircularTest.java | 262 ++++++++++-------- .../serializecircular/UtilityTester.java | 75 ++--- 11 files changed, 548 insertions(+), 255 deletions(-) create mode 100644 src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java create mode 100644 src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java diff --git a/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java b/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java index 71c10386d1b..90b7c9b71e7 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java +++ b/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java @@ -16,6 +16,7 @@ package org.apache.ibatis.executor.loader; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; @@ -33,11 +34,11 @@ public abstract class AbstractEnhancedDeserializationProxy { protected static final String FINALIZE_METHOD = "finalize"; protected static final String WRITE_REPLACE_METHOD = "writeReplace"; - private Class type; - private Map unloadedProperties; - private ObjectFactory objectFactory; - private List> constructorArgTypes; - private List constructorArgs; + private final Class type; + private final Map unloadedProperties; + private final ObjectFactory objectFactory; + private final List> constructorArgTypes; + private final List constructorArgs; private final Object reloadingPropertyLock; private boolean reloadingProperty; @@ -52,6 +53,16 @@ protected AbstractEnhancedDeserializationProxy(Class type, Map type, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + this.type = type; + this.unloadedProperties = unloaded; + this.objectFactory = factory; + this.constructorArgTypes = Arrays.asList(ctorTypes); + this.constructorArgs = Arrays.asList(ctorArgs); + this.reloadingPropertyLock = new Object(); + this.reloadingProperty = false; + } + public final Object invoke(Object enhanced, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); try { @@ -64,7 +75,7 @@ public final Object invoke(Object enhanced, Method method, Object[] args) throws } PropertyCopier.copyBeanProperties(type, enhanced, original); - return this.newSerialStateHolder(original, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + return this.newSerialStateHolder(enhanced, original, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } else { synchronized (this.reloadingPropertyLock) { if (!FINALIZE_METHOD.equals(methodName) && PropertyNamer.isProperty(methodName) && !reloadingProperty) { @@ -97,6 +108,7 @@ public final Object invoke(Object enhanced, Method method, Object[] args) throws } protected abstract AbstractSerialStateHolder newSerialStateHolder( + Object enhanced, Object userBean, Map unloadedProperties, ObjectFactory objectFactory, diff --git a/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java b/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java index 45729347d9e..efeb279b18c 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java +++ b/src/main/java/org/apache/ibatis/executor/loader/AbstractSerialStateHolder.java @@ -26,11 +26,16 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; - +import java.util.Set; import org.apache.ibatis.reflection.factory.ObjectFactory; /** @@ -39,8 +44,25 @@ */ public abstract class AbstractSerialStateHolder implements Externalizable { - private static final long serialVersionUID = 8940388717901644661L; + private static final long serialVersionUID = 20140307; private static final ThreadLocal stream = new ThreadLocal(); + private static final ThreadLocal> handlesWrite = new ThreadLocal>() { + + @Override + protected Map initialValue() { + return new IdentityHashMap(); + } + + }; + private static final ThreadLocal> handlesRead = new ThreadLocal>() { + + @Override + protected Map initialValue() { + return new IdentityHashMap(); + } + + }; + private Object enhanced; private byte[] userBeanBytes = new byte[0]; private Object userBean; private Map unloadedProperties; @@ -52,11 +74,13 @@ public AbstractSerialStateHolder() { } public AbstractSerialStateHolder( + final Object enhanced, final Object userBean, final Map unloadedProperties, final ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { + this.enhanced = enhanced; this.userBean = userBean; this.unloadedProperties = new HashMap(unloadedProperties); this.objectFactory = objectFactory; @@ -64,28 +88,68 @@ public AbstractSerialStateHolder( this.constructorArgs = constructorArgs.toArray(new Object[constructorArgs.size()]); } + private Map findFields(final Object source) { + final Map map = new IdentityHashMap(); + for (Class c = source.getClass(); c != null; c = c.getSuperclass()) { + for (Field f : c.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + if (Modifier.isTransient(f.getModifiers())) { + continue; + } + + f.setAccessible(true); + try { + map.put(f, f.get(source)); + } catch (final IllegalAccessException ex) { + // no-op + } + } + } + + return map; + } + @Override public final void writeExternal(final ObjectOutput out) throws IOException { boolean firstRound = false; final ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream os = stream.get(); + Map hand = handlesWrite.get(); if (os == null) { os = new ObjectOutputStream(baos); firstRound = true; stream.set(os); } - os.writeObject(this.userBean); - os.writeObject(this.unloadedProperties); - os.writeObject(this.objectFactory); - os.writeObject(this.constructorArgTypes); - os.writeObject(this.constructorArgs); + final Integer handle = hand.get(enhanced); + hand.put(enhanced, handle == null ? hand.size() : handle); + + os.writeObject(userBean.getClass()); + os.writeObject(hand.get(enhanced)); + + if (handle == null) { + os.writeObject(this.unloadedProperties); + os.writeObject(this.constructorArgTypes); + os.writeObject(this.constructorArgs); + os.writeObject(this.objectFactory); + + final Map fields = findFields(userBean); + os.writeObject(fields.size()); + for (final Map.Entry e : fields.entrySet()) { + os.writeObject(e.getKey().getDeclaringClass()); + os.writeObject(e.getKey().getName()); + os.writeObject(e.getValue()); + } + } final byte[] bytes = baos.toByteArray(); out.writeObject(bytes); if (firstRound) { stream.remove(); + handlesWrite.remove(); } } @@ -95,11 +159,55 @@ public final void readExternal(final ObjectInput in) throws IOException, ClassNo if (data.getClass().isArray()) { this.userBeanBytes = (byte[]) data; } else { - this.userBean = data; + if (data instanceof Class == false) { + throw new IOException("Invalid stream. Unexpected " + data); + } + this.userBean = this.readUserBean((Class) data, in); + } + } + + @SuppressWarnings({"UseSpecificCatch", "BroadCatchBlock", "TooBroadCatch", "ThrowableInitCause"}) + private Object readUserBean(final Class type, final ObjectInput in) throws IOException, ClassNotFoundException { + final Integer handle = (Integer) in.readObject(); + final Map hand = handlesRead.get(); + if (hand.containsKey(handle)) { + return hand.get(handle); } + + @SuppressWarnings("unchecked") + final Map desUnloaded = (Map) in.readObject(); + final Class[] desCtorTypes = (Class[]) in.readObject(); + final Object[] desCtorArgs = (Object[]) in.readObject(); + final ObjectFactory desObjectFactory = (ObjectFactory) in.readObject(); + + final Object bean = desObjectFactory.create(type, Arrays.asList(desCtorTypes), Arrays.asList(desCtorArgs)); + final Object ubean = desUnloaded.isEmpty() ? bean : newDeserializationProxy(bean, desCtorTypes, desCtorArgs, desObjectFactory, desUnloaded); + hand.put(handle, ubean); + + final Integer fieldsCount = (Integer) in.readObject(); + for (int i = 0; i < fieldsCount; ++i) { + final Class c = (Class) in.readObject(); + final String n = (String) in.readObject(); + try { + final Field f = c.getDeclaredField(n); + + Object v = in.readObject(); + if (v instanceof AbstractSerialStateHolder) { + v = newCyclicReferenceMarker(f.getType(), desCtorTypes, desCtorArgs, (AbstractSerialStateHolder) v); + assert f.getType().isInstance(v) : "Marker not instance of " + f.getType(); + assert v instanceof CyclicReferenceMarker : "Marker not instanceof " + CyclicReferenceMarker.class; + } + + f.setAccessible(true); + f.set(bean, v); + } catch (final Exception ex) { + throw (IOException) new IOException(ex.getLocalizedMessage()).initCause(ex); + } + } + + return ubean; } - @SuppressWarnings("unchecked") protected final Object readResolve() throws ObjectStreamException { /* Second run */ if (this.userBean != null && this.userBeanBytes.length == 0) { @@ -109,24 +217,63 @@ protected final Object readResolve() throws ObjectStreamException { /* First run */ try { final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(this.userBeanBytes)); - this.userBean = in.readObject(); - this.unloadedProperties = (Map) in.readObject(); - this.objectFactory = (ObjectFactory) in.readObject(); - this.constructorArgTypes = (Class[]) in.readObject(); - this.constructorArgs = (Object[]) in.readObject(); + final Class userBeanType = (Class) in.readObject(); + + this.userBean = this.readUserBean(userBeanType, in); + this.replaceCyclicReferenceMarkers(this.userBean, new HashSet()); + + return this.userBean; } catch (final IOException ex) { throw (ObjectStreamException) new StreamCorruptedException().initCause(ex); } catch (final ClassNotFoundException ex) { throw (ObjectStreamException) new InvalidClassException(ex.getLocalizedMessage()).initCause(ex); + } finally { + handlesRead.remove(); } + } - final Map arrayProps = new HashMap(this.unloadedProperties); - final List> arrayTypes = Arrays.asList(this.constructorArgTypes); - final List arrayValues = Arrays.asList(this.constructorArgs); + private void replaceCyclicReferenceMarkers(final Object o, final Set set) { + for (final Map.Entry e : findFields(o).entrySet()) { + final Object value = e.getValue(); + if (value == null) { + continue; + } - return this.createDeserializationProxy(userBean, arrayProps, objectFactory, arrayTypes, arrayValues); + if (value instanceof CyclicReferenceMarker) { + final Field f = e.getKey(); + f.setAccessible(true); + + final AbstractSerialStateHolder assh = ((CyclicReferenceMarker) e.getValue()).getSerialStateHolder(); + try { + f.set(o, assh.userBean); + } catch (final Exception ex) { + throw new IllegalStateException(ex); + } + } else if (value instanceof Collection) { + for (Object ce : ((Collection) value)) { + this.replaceCyclicReferenceMarkers(ce, set); + } + } else if (value instanceof Map) { + for (Map.Entry ce : ((Map) value).entrySet()) { + this.replaceCyclicReferenceMarkers(ce.getKey(), set); + this.replaceCyclicReferenceMarkers(ce.getValue(), set); + } + } else { + if (set.add(value)) { + this.replaceCyclicReferenceMarkers(value, set); + } + } + } } - protected abstract Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs); + protected abstract Object newDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, + ObjectFactory factory, Map unloaded); + + protected abstract Object newCyclicReferenceMarker(Class target, Class[] ctorTypes, Object[] ctorArgs, + AbstractSerialStateHolder ssh); + + protected static interface CyclicReferenceMarker { + + AbstractSerialStateHolder getSerialStateHolder(); + } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java new file mode 100644 index 00000000000..d9ba8dc4c43 --- /dev/null +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibDeserializationProxy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 MyBatis.org. + * + * Licensed 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.ibatis.executor.loader.cglib; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; +import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; +import org.apache.ibatis.executor.loader.ResultLoaderMap; +import org.apache.ibatis.reflection.factory.ObjectFactory; + +final class CglibDeserializationProxy extends AbstractEnhancedDeserializationProxy implements MethodInterceptor { + + private final Object target; + + public CglibDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + super(target.getClass(), ctorTypes, ctorArgs, factory, unloaded); + this.target = target; + } + + @Override + protected AbstractSerialStateHolder newSerialStateHolder(Object enhanced, Object userBean, Map unloadedProperties, + ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { + return new CglibSerialStateHolder(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + } + + @Override + public Object intercept(final Object enhanced, final Method method, final Object[] args, final MethodProxy methodProxy) throws Throwable { + final Object o = super.invoke(this.target, method, args); + if (o instanceof AbstractSerialStateHolder) { + return o; + } else { + return methodProxy.invoke(this.target, args); + } + } + +} diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java index 371253d7591..b9f101254e7 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java @@ -16,6 +16,7 @@ package org.apache.ibatis.executor.loader.cglib; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; @@ -26,8 +27,6 @@ import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; -import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; -import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ProxyFactory; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.loader.WriteReplaceInterface; @@ -61,8 +60,13 @@ public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configurati return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } + public Object createDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + final MethodInterceptor callback = new CglibDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); + return crateProxy(target.getClass(), callback, Arrays.asList(ctorTypes), Arrays.asList(ctorArgs)); + } + public Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + return this.createDeserializationProxy(target, constructorArgTypes.toArray(new Class[0]), constructorArgs.toArray(), objectFactory, unloadedProperties); } public void setProperties(Properties properties) { @@ -133,7 +137,7 @@ public Object intercept(Object enhanced, Method method, Object[] args, MethodPro } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { - return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); + return new CglibSerialStateHolder(enhanced, original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } @@ -157,32 +161,4 @@ public Object intercept(Object enhanced, Method method, Object[] args, MethodPro } } - private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor { - - private EnhancedDeserializationProxyImpl(Class type, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - - public static Object createProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - final Class type = target.getClass(); - EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); - PropertyCopier.copyBeanProperties(type, target, enhanced); - return enhanced; - } - - @Override - public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { - final Object o = super.invoke(enhanced, method, args); - return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invokeSuper(o, args); - } - - @Override - protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java index 141e9df0549..5ea9f15eafa 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibSerialStateHolder.java @@ -17,7 +17,8 @@ import java.util.List; import java.util.Map; - +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.FixedValue; import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.reflection.factory.ObjectFactory; @@ -33,17 +34,34 @@ public CglibSerialStateHolder() { } public CglibSerialStateHolder( + final Object enhanced, final Object userBean, final Map unloadedProperties, final ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - super(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + super(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } @Override - protected Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new CglibProxyFactory().createDeserializationProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + protected Object newDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + return new CglibProxyFactory().createDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); } + + @Override + protected Object newCyclicReferenceMarker(final Class target, final Class[] ctorTypes, final Object[] ctorArgs, final AbstractSerialStateHolder ssh) { + final Enhancer enhancer = new Enhancer(); + enhancer.setCallback(new FixedValue() { + + @Override + public Object loadObject() throws Exception { + return ssh; + } + }); + enhancer.setSuperclass(target); + enhancer.setInterfaces(new Class[]{CyclicReferenceMarker.class}); + + return enhancer.create(ctorTypes, ctorArgs); + } + } diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java new file mode 100644 index 00000000000..599a7f2fc5b --- /dev/null +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistDeserializationProxy.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 MyBatis.org. + * + * Licensed 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.ibatis.executor.loader.javassist; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import javassist.util.proxy.MethodHandler; +import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; +import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; +import org.apache.ibatis.executor.loader.ResultLoaderMap; +import org.apache.ibatis.reflection.factory.ObjectFactory; + +final class JavassistDeserializationProxy extends AbstractEnhancedDeserializationProxy implements MethodHandler { + + private final Object target; + + public JavassistDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + super(target.getClass(), ctorTypes, ctorArgs, factory, unloaded); + this.target = target; + } + + @Override + protected AbstractSerialStateHolder newSerialStateHolder(Object enhanced, Object userBean, Map unloadedProperties, + ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { + return new JavassistSerialStateHolder(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + } + + @Override + public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { + final Object o = super.invoke(this.target, thisMethod, args); + if (o instanceof AbstractSerialStateHolder) { + return o; + } else { + return thisMethod.invoke(this.target, args); + } + } + +} diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java index 40492a99901..97f797ebc55 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java @@ -16,6 +16,7 @@ package org.apache.ibatis.executor.loader.javassist; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; @@ -26,8 +27,6 @@ import javassist.util.proxy.ProxyFactory; import org.apache.ibatis.executor.ExecutorException; -import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; -import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.loader.WriteReplaceInterface; import org.apache.ibatis.io.Resources; @@ -60,8 +59,13 @@ public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configurati return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } + public Object createDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + final MethodHandler callback = new JavassistDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); + return crateProxy(target.getClass(), callback, Arrays.asList(ctorTypes), Arrays.asList(ctorArgs)); + } + public Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + return createDeserializationProxy(target, constructorArgTypes.toArray(new Class[0]), constructorArgs.toArray(), objectFactory, unloadedProperties); } public void setProperties(Properties properties) { @@ -135,7 +139,7 @@ public Object invoke(Object enhanced, Method method, Method methodProxy, Object[ } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { - return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); + return new JavassistSerialStateHolder(enhanced, original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } @@ -158,33 +162,4 @@ public Object invoke(Object enhanced, Method method, Method methodProxy, Object[ } } } - - private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodHandler { - - private EnhancedDeserializationProxyImpl(Class type, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - - public static Object createProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - final Class type = target.getClass(); - EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); - PropertyCopier.copyBeanProperties(type, target, enhanced); - return enhanced; - } - - @Override - public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { - final Object o = super.invoke(enhanced, method, args); - return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invoke(o, args); - } - - @Override - protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new JavassistSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); - } - } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java index 7b14bca6214..2aa8a8167a8 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistSerialStateHolder.java @@ -15,9 +15,12 @@ */ package org.apache.ibatis.executor.loader.javassist; +import java.lang.reflect.Method; import java.util.List; import java.util.Map; - +import javassist.util.proxy.MethodHandler; +import javassist.util.proxy.Proxy; +import javassist.util.proxy.ProxyFactory; import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.reflection.factory.ObjectFactory; @@ -33,17 +36,42 @@ public JavassistSerialStateHolder() { } public JavassistSerialStateHolder( + final Object enhanced, final Object userBean, final Map unloadedProperties, final ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { - super(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + super(enhanced, userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } @Override - protected Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, - List> constructorArgTypes, List constructorArgs) { - return new JavassistProxyFactory().createDeserializationProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); + protected Object newDeserializationProxy(Object target, Class[] ctorTypes, Object[] ctorArgs, ObjectFactory factory, Map unloaded) { + return new JavassistProxyFactory().createDeserializationProxy(target, ctorTypes, ctorArgs, factory, unloaded); } + + @Override + protected Object newCyclicReferenceMarker(final Class target, final Class[] ctorTypes, final Object[] ctorArgs, final AbstractSerialStateHolder ssh) { + final ProxyFactory enhancer = new ProxyFactory(); + enhancer.setSuperclass(target); + enhancer.setInterfaces(new Class[]{CyclicReferenceMarker.class}); + + final Proxy enhanced; + try { + enhanced = (Proxy) enhancer.create(ctorTypes, ctorArgs); + } catch (final Exception ex) { + throw new IllegalStateException(ex); + } + + enhanced.setHandler(new MethodHandler() { + + @Override + public Object invoke(Object o, Method method, Method method1, Object[] os) throws Throwable { + return ssh; + } + }); + + return enhanced; + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml index 7019d5312aa..559877d4607 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml @@ -24,8 +24,9 @@ - + + diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java index eec8260a206..a41e258a188 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/SerializeCircularTest.java @@ -20,133 +20,149 @@ import org.apache.ibatis.io.Resources; import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.Assert; import org.junit.Test; // see issue #614 public class SerializeCircularTest { - @Test - public void serializeAndDeserializeListWithoutAggressiveLazyLoading() - throws Exception { - SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); - try { - serializeAndDeserializeList(sqlSession); - } finally { - sqlSession.close(); - } - } - - @Test - public void serializeAndDeserializeListWithAggressiveLazyLoading() - throws Exception { - SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); - try { - serializeAndDeserializeList(sqlSession); - } finally { - sqlSession.close(); - } - } - - private void serializeAndDeserializeList(SqlSession sqlSession) - throws Exception { - ParentMapper parentMapper = sqlSession.getMapper(ParentMapper.class); - Parent parent = parentMapper.getById(1); - Child child = parent.getChildren().get(0); - - UtilityTester.serializeAndDeserializeObject(child); - } - - @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); - try { - testSerializeWithoutPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - @Test - public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); - try { - testSerializeWithPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - //See http://code.google.com/p/mybatis/issues/detail?id=614 - @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); - try { - //expected problem with deserializing - testSerializeWithoutPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - @Test - public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() - throws Exception { - SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); - try { - testSerializeWithPreloadingAttribute(sqlSession); - } finally { - sqlSession.close(); - } - } - - private SqlSession createSessionWithoutAggressiveLazyLoading() throws Exception { - return createSession(false); - } - - private SqlSession createSessionWithAggressiveLazyLoading() throws Exception { - return createSession(true); - } - - private SqlSession createSession(boolean anAggressiveLazyLoading) throws Exception { - String xmlConfig = anAggressiveLazyLoading ? - "org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml": - "org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml"; - SqlSessionFactory sqlSessionFactory = getSqlSessionFactoryXmlConfig(xmlConfig); - SqlSession sqlSession = sqlSessionFactory.openSession(); - return sqlSession; - } - - private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) throws Exception { - testSerialize(sqlSession, true); - } - - private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) throws Exception { - testSerialize(sqlSession, false); - } - - private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) throws Exception { - DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); - Department department = departmentMapper.getById(1); - if (aPreloadAttribute) { - department.getAttribute(); - } - - serializeAndDeserializeObject(department); - - // This call results in problems when deserializing department - department.getPerson(); - serializeAndDeserializeObject(department); - } - - protected void serializeAndDeserializeObject(Object anObject) throws Exception { - UtilityTester.serializeAndDeserializeObject(anObject); - } + @Test + public void serializeAndDeserializeListWithoutAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + cfg = sqlSession.getConfiguration(); + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeListWithAggressiveLazyLoading() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + serializeAndDeserializeList(sqlSession); + } finally { + sqlSession.close(); + } + } + + private void serializeAndDeserializeList(SqlSession sqlSession) + throws Exception { + ParentMapper parentMapper = sqlSession.getMapper(ParentMapper.class); + Parent parent = parentMapper.getById(1); + Child child = parent.getChildren().get(0); + + Child deserialized = UtilityTester.serializeAndDeserializeObject(child); + Assert.assertNotNull(deserialized); + Assert.assertNotSame(child, deserialized); + Assert.assertEquals(child.getId(), deserialized.getId()); + Assert.assertNotNull(deserialized.getParent()); + Assert.assertEquals(child.getParent().getId(), deserialized.getParent().getId()); + Assert.assertSame(deserialized, deserialized.getParent().getChildren().get(0)); + + Person dp1 = child.getPerson(); + Assert.assertNotNull(dp1); + + Person dp2 = deserialized.getPerson(); + Assert.assertNotNull(dp2); + + } + + @Test + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithoutPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + testSerializeWithoutPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeObjectsWithAggressiveLazyLoadingWithPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithAggressiveLazyLoading(); + try { + testSerializeWithPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + //See http://code.google.com/p/mybatis/issues/detail?id=614 + @Test + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithoutPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + //expected problem with deserializing + testSerializeWithoutPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + @Test + public void serializeAndDeserializeObjectsWithoutAggressiveLazyLoadingWithPreloadingAttribute() + throws Exception { + SqlSession sqlSession = createSessionWithoutAggressiveLazyLoading(); + try { + testSerializeWithPreloadingAttribute(sqlSession); + } finally { + sqlSession.close(); + } + } + + private SqlSession createSessionWithoutAggressiveLazyLoading() throws Exception { + return createSession(false); + } + + private SqlSession createSessionWithAggressiveLazyLoading() throws Exception { + return createSession(true); + } + + private SqlSession createSession(boolean anAggressiveLazyLoading) throws Exception { + String xmlConfig = anAggressiveLazyLoading + ? "org/apache/ibatis/submitted/serializecircular/MapperConfigWithAggressiveLazyLoading.xml" + : "org/apache/ibatis/submitted/serializecircular/MapperConfigWithoutAggressiveLazyLoading.xml"; + SqlSessionFactory sqlSessionFactory = getSqlSessionFactoryXmlConfig(xmlConfig); + SqlSession sqlSession = sqlSessionFactory.openSession(); + return sqlSession; + } + + private void testSerializeWithPreloadingAttribute(SqlSession sqlSession) throws Exception { + testSerialize(sqlSession, true); + } + + private void testSerializeWithoutPreloadingAttribute(SqlSession sqlSession) throws Exception { + testSerialize(sqlSession, false); + } + + private void testSerialize(SqlSession sqlSession, boolean aPreloadAttribute) throws Exception { + DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); + Department department = departmentMapper.getById(1); + if (aPreloadAttribute) { + department.getAttribute(); + } + + serializeAndDeserializeObject(department); + + // This call results in problems when deserializing department + department.getPerson(); + serializeAndDeserializeObject(department); + } + + protected void serializeAndDeserializeObject(Object anObject) throws Exception { + UtilityTester.serializeAndDeserializeObject(anObject); + } private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws Exception { Reader configReader = Resources.getResourceAsReader(resource); @@ -159,10 +175,16 @@ private SqlSessionFactory getSqlSessionFactoryXmlConfig(String resource) throws return sqlSessionFactory; } + private static Configuration cfg; + + public static Configuration getConfiguration() { + return cfg; + } + private static void initDb(Connection conn) throws Exception { try { Reader scriptReader = Resources - .getResourceAsReader("org/apache/ibatis/submitted/serializecircular/CreateDB.sql"); + .getResourceAsReader("org/apache/ibatis/submitted/serializecircular/CreateDB.sql"); ScriptRunner runner = new ScriptRunner(conn); runner.setLogWriter(null); runner.setErrorLogWriter(null); diff --git a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java index 47b398e8918..12c5d08d434 100644 --- a/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java +++ b/src/test/java/org/apache/ibatis/submitted/serializecircular/UtilityTester.java @@ -23,38 +23,47 @@ public class UtilityTester { - public static void serializeAndDeserializeObject(Object myObject) throws Exception{ - deserialzeObject(serializeObject(myObject)); - } - - private static byte[] serializeObject(Object myObject) throws IOException { - try { - ByteArrayOutputStream myByteArrayOutputStream = new ByteArrayOutputStream(); - - // Serialize to a byte array - ObjectOutputStream myObjectOutputStream = new ObjectOutputStream(myByteArrayOutputStream) ; - myObjectOutputStream.writeObject(myObject); - myObjectOutputStream.close(); - - // Get the bytes of the serialized object - byte[] myResult = myByteArrayOutputStream.toByteArray(); - return myResult; - } catch (Exception anException) { - throw new RuntimeException("Problem serializing: " + anException.toString(), anException); - } - } - - private static Object deserialzeObject(byte[] aSerializedObject) { - try { - // Deserialize from a byte array - ObjectInputStream myObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(aSerializedObject)); - Object myResult = myObjectInputStream.readObject(); - myObjectInputStream.close(); - - return myResult; - } catch (Exception anException) { - throw new RuntimeException("Problem deserializing", anException); - } - } + public static T serializeAndDeserializeObject(Object myObject) throws Exception { + return deserialzeObject(serializeObject(myObject)); + } + private static byte[] serializeObject(Object myObject) throws IOException { + try { + ByteArrayOutputStream myByteArrayOutputStream = new ByteArrayOutputStream(); + + // Serialize to a byte array + ObjectOutputStream myObjectOutputStream = new ObjectOutputStream(myByteArrayOutputStream); + myObjectOutputStream.writeObject(myObject); + myObjectOutputStream.close(); + + // Get the bytes of the serialized object + byte[] myResult = myByteArrayOutputStream.toByteArray(); + return myResult; + } catch (final Exception ex) { + throw rethrow(ex); + } + } + + private static T deserialzeObject(byte[] aSerializedObject) { + try { + // Deserialize from a byte array + ObjectInputStream myObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(aSerializedObject)); + Object myResult = myObjectInputStream.readObject(); + myObjectInputStream.close(); + + return (T) myResult; + } catch (final Exception ex) { + throw rethrow(ex); + } + } + + @SuppressWarnings("unchecked") + private static RuntimeException rethrow(final Throwable t) { + UtilityTester.rethrow0(t); + return null; + } + @SuppressWarnings("unchecked") + private static RuntimeException rethrow0(final Throwable t) throws T { + throw (T) t; + } } From a3bcceda026366db9c05922abccf4ad2c9fbce97 Mon Sep 17 00:00:00 2001 From: Jurriaan Pruys Date: Wed, 18 Jun 2014 14:46:50 +0200 Subject: [PATCH 6/6] Revert shouldSerizalizeADeserlizaliedProxy type-test, because there are no unloaded properties in the proxy. This means that the proxy can deserialize the wrapped Object. --- .../java/org/apache/ibatis/executor/loader/CglibProxyTest.java | 2 +- .../org/apache/ibatis/executor/loader/JavassistProxyTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java b/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java index fc27208d43e..971971b7398 100644 --- a/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java +++ b/src/test/java/org/apache/ibatis/executor/loader/CglibProxyTest.java @@ -67,7 +67,7 @@ public void shouldSerizalizeADeserlizaliedProxy() throws Exception { Object proxy = ((CglibProxyFactory)proxyFactory).createDeserializationProxy(author, new HashMap(), new DefaultObjectFactory(), new ArrayList>(), new ArrayList()); Author author2 = (Author) deserialize(serialize((Serializable) proxy)); assertEquals(author, author2); - assertFalse(author.getClass().equals(author2.getClass())); + assertTrue("author.class (" + author.getClass() + ") should be the same as author2.class (" + author2.getClass() + "), since the proxy does not have unloaded properties; it does not need to be proxied.", author.getClass().equals(author2.getClass())); } } diff --git a/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java b/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java index f450e5fc4be..1643b843cfb 100644 --- a/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java +++ b/src/test/java/org/apache/ibatis/executor/loader/JavassistProxyTest.java @@ -67,7 +67,8 @@ public void shouldSerizalizeADeserlizaliedProxy() throws Exception { Object proxy = ((JavassistProxyFactory)proxyFactory).createDeserializationProxy(author, new HashMap (), new DefaultObjectFactory(), new ArrayList>(), new ArrayList()); Author author2 = (Author) deserialize(serialize((Serializable) proxy)); assertEquals(author, author2); - assertFalse(author.getClass().equals(author2.getClass())); + assertTrue("author.class (" + author.getClass() + ") should be the same as author2.class (" + author2.getClass() + "), since the proxy does not have unloaded properties; it does not need to be proxied.", + author.getClass().equals(author2.getClass())); } }