From d0d71f6ee456595406918ec72a6f0efbf43cd270 Mon Sep 17 00:00:00 2001 From: Matt Watson Date: Fri, 24 Jul 2020 11:55:29 -0700 Subject: [PATCH] CAY-2667 Fix Issues with Generic Vertical Inheritance --- .../java/org/apache/cayenne/BaseContext.java | 2 +- .../cayenne/reflect/ClassDescriptor.java | 8 +-- .../reflect/LazyClassDescriptorDecorator.java | 4 +- .../cayenne/reflect/PersistentDescriptor.java | 23 +++++---- .../reflect/PersistentDescriptorFactory.java | 2 +- .../cayenne/util/DeepMergeOperation.java | 2 +- .../cayenne/util/ObjectDetachOperation.java | 3 +- .../cayenne/access/VerticalInheritanceIT.java | 49 +++++++++++++++++++ .../resources/inheritance-vertical.map.xml | 39 +++++++++++++++ 9 files changed, 112 insertions(+), 20 deletions(-) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java b/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java index 981db73c77..3a2db3a577 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java @@ -637,7 +637,7 @@ protected void injectInitialValue(Object obj) { ObjEntity entity; try { - entity = getEntityResolver().getObjEntity(object.getClass()); + entity = getEntityResolver().getObjEntity(object); } catch (CayenneRuntimeException ex) { // ObjEntity cannot be fetched, ignored entity = null; diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java index 392e739237..31e485f582 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java @@ -87,14 +87,14 @@ public interface ClassDescriptor { ClassDescriptor getSuperclassDescriptor(); /** - * Returns the most "specialized" descriptor for a given class. This method assumes + * Returns the most "specialized" descriptor for a given ObjEntity. This method assumes * that the following is true: - * + * *
-     * this.getObjectClass().isAssignableFrom(objectClass)
+     * this.getObjectClass().isAssignableFrom(subEntity.getJavaClass())
      * 
*/ - ClassDescriptor getSubclassDescriptor(Class objectClass); + ClassDescriptor getSubclassDescriptor(ObjEntity subEntity); /** * Creates a new instance of a class described by this object. diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java index 821334e8a6..1f08faba78 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java @@ -146,9 +146,9 @@ public PropertyDescriptor getProperty(String propertyName) { return descriptor.getProperty(propertyName); } - public ClassDescriptor getSubclassDescriptor(Class objectClass) { + public ClassDescriptor getSubclassDescriptor(ObjEntity subEntity) { checkDescriptorInitialized(); - return descriptor.getSubclassDescriptor(objectClass); + return descriptor.getSubclassDescriptor(subEntity); } public ClassDescriptor getSuperclassDescriptor() { diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java index 24d83aee88..dcb691afd4 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java @@ -209,16 +209,16 @@ public void removeDeclaredProperty(String propertyName) { } /** - * Adds a subclass descriptor that maps to a given class name. + * Adds a subclass descriptor that maps to a given entity name. */ - public void addSubclassDescriptor(String className, ClassDescriptor subclassDescriptor) { + public void addSubclassDescriptor(String entityName, ClassDescriptor subclassDescriptor) { // note that 'className' should be used instead of // "subclassDescriptor.getEntity().getClassName()", as this method is // called in // the early phases of descriptor initialization and we do not want to // trigger // subclassDescriptor resolution just yet to prevent stack overflow. - subclassDescriptors.put(className, subclassDescriptor); + subclassDescriptors.put(entityName, subclassDescriptor); } public ObjEntity getEntity() { @@ -257,23 +257,26 @@ void setObjectClass(Class objectClass) { this.objectClass = objectClass; } - public ClassDescriptor getSubclassDescriptor(Class objectClass) { - if (objectClass == null) { - throw new IllegalArgumentException("Null objectClass"); + public ClassDescriptor getSubclassDescriptor(ObjEntity subEntity) { + if (subEntity == null) { + throw new IllegalArgumentException("Null subEntity"); } if (subclassDescriptors.isEmpty()) { return this; } - ClassDescriptor subclassDescriptor = subclassDescriptors.get(objectClass.getName()); + ClassDescriptor subclassDescriptor = subclassDescriptors.get(subEntity.getName()); // ascend via the class hierarchy (only doing it if there are multiple // choices) if (subclassDescriptor == null) { - Class currentClass = objectClass; - while (subclassDescriptor == null && (currentClass = currentClass.getSuperclass()) != null) { - subclassDescriptor = subclassDescriptors.get(currentClass.getName()); + ObjEntity currentEntity = subEntity; + while (subclassDescriptor == null && (currentEntity = currentEntity.getSuperEntity()) != null) { + if(currentEntity == null){ + break; + } + subclassDescriptor = subclassDescriptors.get(currentEntity.getName()); } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java index 68f3f7a4db..2c2d74ef26 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java @@ -173,7 +173,7 @@ protected void indexSubclassDescriptors(PersistentDescriptor descriptor, EntityI for (EntityInheritanceTree child : inheritanceTree.getChildren()) { ObjEntity childEntity = child.getEntity(); - descriptor.addSubclassDescriptor(childEntity.getClassName(), + descriptor.addSubclassDescriptor(childEntity.getName(), descriptorMap.getDescriptor(childEntity.getName())); indexSubclassDescriptors(descriptor, child); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java b/cayenne-server/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java index fe5c781ac9..e14d74ea5c 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java @@ -81,7 +81,7 @@ private T merge( final T target = shallowMergeOperation.merge(peerInParentContext); seen.put(id, target); - descriptor = descriptor.getSubclassDescriptor(peerInParentContext.getClass()); + descriptor = descriptor.getSubclassDescriptor(entityResolver.getObjEntity(id.getEntityName())); descriptor.visitProperties(new PropertyVisitor() { public boolean visitToOne(ToOneProperty property) { diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectDetachOperation.java b/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectDetachOperation.java index f5a70641fd..0091c07c4f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectDetachOperation.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectDetachOperation.java @@ -19,6 +19,7 @@ package org.apache.cayenne.util; +import org.apache.cayenne.Cayenne; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.ObjectId; import org.apache.cayenne.Persistent; @@ -86,7 +87,7 @@ public Object detach( return seenTarget; } - descriptor = descriptor.getSubclassDescriptor(source.getClass()); + descriptor = descriptor.getSubclassDescriptor(Cayenne.getObjEntity(source)); // presumably id's entity name should be of the right subclass. final ClassDescriptor targetDescriptor = targetResolver.getClassDescriptor(id diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java index 1b49875a4d..26c5d41579 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java @@ -18,6 +18,7 @@ ****************************************************************/ package org.apache.cayenne.access; +import java.lang.reflect.Field; import java.sql.SQLException; import java.sql.Types; import java.util.Collections; @@ -25,6 +26,7 @@ import java.util.List; import java.util.Map; +import org.apache.cayenne.DataObject; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.configuration.server.ServerRuntime; import org.apache.cayenne.di.Inject; @@ -32,6 +34,9 @@ import org.apache.cayenne.query.ObjectSelect; import org.apache.cayenne.query.SelectById; import org.apache.cayenne.query.SelectQuery; +import org.apache.cayenne.reflect.ClassDescriptor; +import org.apache.cayenne.reflect.LazyClassDescriptorDecorator; +import org.apache.cayenne.reflect.PersistentDescriptor; import org.apache.cayenne.test.jdbc.DBHelper; import org.apache.cayenne.test.jdbc.TableHelper; import org.apache.cayenne.testdo.inheritance_vertical.Iv1Root; @@ -755,4 +760,48 @@ public void testCountEjbqlQuery() throws Exception { EJBQLQuery query3 = new EJBQLQuery("SELECT COUNT(a) FROM IvSub2 a"); assertEquals(Collections.singletonList(2L), context.performQuery(query3)); } + + @Test + public void testGenericVerticalInheritancePersistentDescriptor() throws NoSuchFieldException, IllegalAccessException { + final LazyClassDescriptorDecorator lazyStudentDescriptor = (LazyClassDescriptorDecorator) context.getEntityResolver().getClassDescriptor("GenStudent"); + final ClassDescriptor studentDescriptor = lazyStudentDescriptor.getDescriptor(); + + final Field subclassDescriptorsField = PersistentDescriptor.class.getDeclaredField("subclassDescriptors"); + subclassDescriptorsField.setAccessible(true); + final Map subclassDescriptors = (Map) subclassDescriptorsField.get(studentDescriptor); + assertEquals(2, subclassDescriptors.size()); + } + + @Test + public void testInsertTwoGenericVerticalInheritanceObjects() { + // Generic DataObjects play nicer with a DataContext + final DataContext dataContext = (DataContext) context; + + final DataObject girlEmma = (DataObject) dataContext.newObject("GenGirl"); + final DataObject boyLuke = (DataObject) dataContext.newObject("GenBoy"); + + assertEquals("Girl is type G", girlEmma.readProperty("type"), "G"); + assertEquals("Boy is type B", boyLuke.readProperty("type"), "B"); + + girlEmma.writeProperty("reference", "g1"); + girlEmma.writeProperty("name", "Emma"); + girlEmma.writeProperty("toyDolls", 5); + + boyLuke.writeProperty("reference", "b1"); + boyLuke.writeProperty("name", "Luke"); + boyLuke.writeProperty("toyTrucks", 12); + + context.commitChanges(); + + assertEquals(2, ObjectSelect.query(DataObject.class, "GenStudent").selectCount(context)); + + final List students = ObjectSelect.query(DataObject.class, "GenStudent").select(context); + assertTrue(students.contains(girlEmma)); + assertTrue(students.contains(boyLuke)); + + final List girls = ObjectSelect.query(DataObject.class, "GenGirl").select(context); + assertEquals(1, girls.size()); + final List boys = ObjectSelect.query(DataObject.class, "GenBoy").select(context); + assertEquals(1, boys.size()); + } } diff --git a/cayenne-server/src/test/resources/inheritance-vertical.map.xml b/cayenne-server/src/test/resources/inheritance-vertical.map.xml index 25b772a912..782c8ea083 100644 --- a/cayenne-server/src/test/resources/inheritance-vertical.map.xml +++ b/cayenne-server/src/test/resources/inheritance-vertical.map.xml @@ -81,6 +81,20 @@ + + + + + + + + + + + + + + @@ -149,6 +163,19 @@ + + + + + + + + + + + + + @@ -233,6 +260,18 @@ + + + + + + + + + + + +