This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch 5.0-FIX-CAY-2667-generic-inheritance in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit d57f6e0028a253800eb00de66737c5e466e52f54 Author: Nikita Timofeev <[email protected]> AuthorDate: Tue Jul 9 18:59:55 2024 +0400 CAY-2667 Fix Issues with Generic Vertical Inheritance --- .../cayenne/access/DataContextObjectCreator.java | 2 +- .../org/apache/cayenne/reflect/ArcProperty.java | 3 +- .../apache/cayenne/reflect/ClassDescriptor.java | 17 ++++++-- .../reflect/LazyClassDescriptorDecorator.java | 5 ++- .../cayenne/reflect/PersistentDescriptor.java | 35 +++++---------- .../reflect/PersistentDescriptorFactory.java | 8 ++-- .../apache/cayenne/util/DeepMergeOperation.java | 2 +- .../cayenne/access/VerticalInheritanceIT.java | 50 ++++++++++++++++++++++ .../test/resources/inheritance-vertical.map.xml | 39 +++++++++++++++++ 9 files changed, 123 insertions(+), 38 deletions(-) diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContextObjectCreator.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContextObjectCreator.java index de7a5ebfa..f7d9ec3cb 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/DataContextObjectCreator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextObjectCreator.java @@ -209,7 +209,7 @@ class DataContextObjectCreator { ObjEntity entity; try { - entity = context.getEntityResolver().getObjEntity(object.getClass()); + entity = context.getEntityResolver().getObjEntity(object.getObjectId().getEntityName()); } catch (CayenneRuntimeException ex) { // ObjEntity cannot be fetched, ignored entity = null; diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/ArcProperty.java b/cayenne/src/main/java/org/apache/cayenne/reflect/ArcProperty.java index 97e2499ff..0bab4ca7d 100644 --- a/cayenne/src/main/java/org/apache/cayenne/reflect/ArcProperty.java +++ b/cayenne/src/main/java/org/apache/cayenne/reflect/ArcProperty.java @@ -19,7 +19,6 @@ package org.apache.cayenne.reflect; -import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.ObjRelationship; /** @@ -55,7 +54,7 @@ public interface ArcProperty extends PropertyDescriptor { * Returns a ClassDescriptor for the type of graph nodes pointed to by this * arc property. Note that considering that a target object may be a * subclass of the class handled by the descriptor, users of this method may - * need to call {@link ClassDescriptor#getSubclassDescriptor(Class)} before + * need to call {@link ClassDescriptor#getSubclassDescriptor(String)} before * using the descriptor to access objects. */ ClassDescriptor getTargetDescriptor(); diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java index 13833f8e3..51b4ca5a0 100644 --- a/cayenne/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java +++ b/cayenne/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java @@ -88,14 +88,25 @@ public interface ClassDescriptor { ClassDescriptor getSuperclassDescriptor(); /** - * Returns the most "specialized" descriptor for a given class. This method assumes - * that the following is true: + * Returns the most "specialized" descriptor for a given class. + * This method assumes that the following is true: * * <pre> * this.getObjectClass().isAssignableFrom(objectClass) * </pre> + * @deprecated since 5.0, will throw UnsupportedOperationException on invocation, + * use {@link #getSubclassDescriptor(String)} */ - ClassDescriptor getSubclassDescriptor(Class<?> objectClass); + @Deprecated(since = "5.0", forRemoval = true) + default ClassDescriptor getSubclassDescriptor(Class<?> unused) { + throw new UnsupportedOperationException("This method is deprecated, use getSubclassDescriptor(entityName) instead"); + } + + /** + * Returns the most "specialized" descriptor for a given entity name. + * @since 5.0 + */ + ClassDescriptor getSubclassDescriptor(String entityName); /** * Creates a new instance of a class described by this object. diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java b/cayenne/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java index 6c7810f32..228394a5e 100644 --- a/cayenne/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java +++ b/cayenne/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java @@ -147,9 +147,10 @@ public class LazyClassDescriptorDecorator implements ClassDescriptor { return descriptor.getProperty(propertyName); } - public ClassDescriptor getSubclassDescriptor(Class<?> objectClass) { + @Override + public ClassDescriptor getSubclassDescriptor(String entityName) { checkDescriptorInitialized(); - return descriptor.getSubclassDescriptor(objectClass); + return descriptor.getSubclassDescriptor(entityName); } public ClassDescriptor getSuperclassDescriptor() { diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java index 8842c3e49..9f981765e 100644 --- a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java +++ b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java @@ -46,9 +46,7 @@ import java.util.Map.Entry; */ public class PersistentDescriptor implements ClassDescriptor { - static final Integer TRANSIENT_STATE = PersistenceState.TRANSIENT; static final Integer HOLLOW_STATE = PersistenceState.HOLLOW; - static final Integer COMMITTED_STATE = PersistenceState.COMMITTED; protected ClassDescriptor superclassDescriptor; @@ -85,7 +83,7 @@ public class PersistentDescriptor implements ClassDescriptor { this.subclassDescriptors = new HashMap<>(); // must be a set as duplicate addition attempts are expected... - this.rootDbEntities = new HashSet<DbEntity>(1); + this.rootDbEntities = new HashSet<>(1); } public void setDiscriminatorColumns(Collection<ObjAttribute> columns) { @@ -213,14 +211,11 @@ public class PersistentDescriptor implements ClassDescriptor { /** * Adds a subclass descriptor that maps to a given class name. */ - public void addSubclassDescriptor(String className, 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); + public void addSubclassDescriptor(String entityName, ClassDescriptor subclassDescriptor) { + // NOTE: 'entityName' should be used instead of "subclassDescriptor.getEntity().getName()", + // 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(entityName, subclassDescriptor); } public ObjEntity getEntity() { @@ -259,8 +254,8 @@ public class PersistentDescriptor implements ClassDescriptor { this.objectClass = objectClass; } - public ClassDescriptor getSubclassDescriptor(Class<?> objectClass) { - if (objectClass == null) { + public ClassDescriptor getSubclassDescriptor(String entityName) { + if (entityName == null) { throw new IllegalArgumentException("Null objectClass"); } @@ -268,22 +263,12 @@ public class PersistentDescriptor implements ClassDescriptor { return this; } - ClassDescriptor subclassDescriptor = subclassDescriptors.get(objectClass.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()); - } - } - + ClassDescriptor subclassDescriptor = subclassDescriptors.get(entityName); return subclassDescriptor != null ? subclassDescriptor : this; } public Collection<ObjAttribute> getDiscriminatorColumns() { - return allDiscriminatorColumns != null ? allDiscriminatorColumns : Collections.<ObjAttribute> emptyList(); + return allDiscriminatorColumns != null ? allDiscriminatorColumns : Collections.emptyList(); } public Collection<AttributeProperty> getIdProperties() { diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java index 3e22bf5b7..e573f4501 100644 --- a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java +++ b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java @@ -65,7 +65,8 @@ public abstract class PersistentDescriptorFactory implements ClassDescriptorFact protected ClassDescriptor getDescriptor(ObjEntity entity, Class<?> entityClass) { String superEntityName = entity.getSuperEntityName(); - ClassDescriptor superDescriptor = (superEntityName != null) ? descriptorMap.getDescriptor(superEntityName) + ClassDescriptor superDescriptor = (superEntityName != null) + ? descriptorMap.getDescriptor(superEntityName) : null; PersistentDescriptor descriptor = createDescriptor(); @@ -175,7 +176,7 @@ public abstract class PersistentDescriptorFactory implements ClassDescriptorFact for (EntityInheritanceTree child : inheritanceTree.getChildren()) { ObjEntity childEntity = child.getEntity(); - descriptor.addSubclassDescriptor(childEntity.getClassName(), + descriptor.addSubclassDescriptor(childEntity.getName(), descriptorMap.getDescriptor(childEntity.getName())); indexSubclassDescriptors(descriptor, child); @@ -200,8 +201,7 @@ public abstract class PersistentDescriptorFactory implements ClassDescriptorFact DbEntity dbEntity = entity.getDbEntity(); if (dbEntity != null) { // descriptor takes care of weeding off duplicates, which are likely - // in cases - // of non-horizontal inheritance + // in cases of non-horizontal inheritance descriptor.addRootDbEntity(dbEntity); } } diff --git a/cayenne/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java b/cayenne/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java index a3e1c24a9..414475baa 100644 --- a/cayenne/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java +++ b/cayenne/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java @@ -83,7 +83,7 @@ public class DeepMergeOperation { final T target = shallowMergeOperation.merge(peerInParentContext); seen.put(id, target); - descriptor = descriptor.getSubclassDescriptor(peerInParentContext.getClass()); + descriptor = descriptor.getSubclassDescriptor(id.getEntityName()); descriptor.visitProperties(new PropertyVisitor() { public boolean visitToOne(ToOneProperty property) { diff --git a/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java b/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java index 74f37a439..05013a767 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java @@ -20,11 +20,15 @@ package org.apache.cayenne.access; import org.apache.cayenne.Cayenne; import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.Persistent; import org.apache.cayenne.di.Inject; import org.apache.cayenne.query.ColumnSelect; import org.apache.cayenne.query.EJBQLQuery; import org.apache.cayenne.query.ObjectSelect; import org.apache.cayenne.query.SelectById; +import org.apache.cayenne.reflect.ClassDescriptor; +import org.apache.cayenne.reflect.LazyClassDescriptorDecorator; +import org.apache.cayenne.reflect.PersistentDescriptor; import org.apache.cayenne.runtime.CayenneRuntime; import org.apache.cayenne.test.jdbc.DBHelper; import org.apache.cayenne.test.jdbc.TableHelper; @@ -37,6 +41,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import java.lang.reflect.Field; import java.sql.SQLException; import java.sql.Types; import java.util.Collections; @@ -1234,4 +1239,49 @@ public class VerticalInheritanceIT extends RuntimeCase { assertEquals("mDA", result.getSub1Name()); assertEquals("3DQa", result.getSub1Sub1Name()); } + + @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); + @SuppressWarnings("unchecked") + final Map<String, ClassDescriptor> subclassDescriptors = (Map<String, ClassDescriptor>) subclassDescriptorsField.get(studentDescriptor); + assertEquals(2, subclassDescriptors.size()); + } + + @Test + public void testInsertTwoGenericVerticalInheritanceObjects() { + // Generic DataObjects play nicer with a DataContext + final DataContext dataContext = (DataContext) context; + + final Persistent girlEmma = dataContext.newObject("GenGirl"); + final Persistent boyLuke = 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(Persistent.class, "GenStudent").selectCount(context)); + + final List<Persistent> students = ObjectSelect.query(Persistent.class, "GenStudent").select(context); + assertTrue(students.contains(girlEmma)); + assertTrue(students.contains(boyLuke)); + + final List<Persistent> girls = ObjectSelect.query(Persistent.class, "GenGirl").select(context); + assertEquals(1, girls.size()); + final List<Persistent> boys = ObjectSelect.query(Persistent.class, "GenBoy").select(context); + assertEquals(1, boys.size()); + } } diff --git a/cayenne/src/test/resources/inheritance-vertical.map.xml b/cayenne/src/test/resources/inheritance-vertical.map.xml index 142f7ee13..f5b8509f0 100644 --- a/cayenne/src/test/resources/inheritance-vertical.map.xml +++ b/cayenne/src/test/resources/inheritance-vertical.map.xml @@ -83,6 +83,20 @@ <db-attribute name="SUB1_NAME" type="VARCHAR" length="100"/> <db-attribute name="SUB1_PRICE" type="DOUBLE"/> </db-entity> + <db-entity name="GEN_STUDENT"> + <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> + <db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="50"/> + <db-attribute name="REFERENCE" type="VARCHAR" isMandatory="true" length="10"/> + <db-attribute name="TYPE" type="CHAR" isMandatory="true" length="1"/> + </db-entity> + <db-entity name="GEN_BOY"> + <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> + <db-attribute name="TOY_TRUCKS" type="SMALLINT" length="4"/> + </db-entity> + <db-entity name="GEN_GIRL"> + <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> + <db-attribute name="TOY_DOLLS" type="SMALLINT" length="4"/> + </db-entity> <db-entity name="IV_SUB1_SUB1"> <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> <db-attribute name="SUB1_SUB1_NAME" type="VARCHAR" length="100"/> @@ -176,6 +190,19 @@ <qualifier><![CDATA[discriminator = "IvSub3"]]></qualifier> <pre-persist method-name="onPrePersist"/> </obj-entity> + <obj-entity name="GenStudent" abstract="true" dbEntityName="GEN_STUDENT"> + <obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/> + <obj-attribute name="reference" type="java.lang.String" db-attribute-path="REFERENCE"/> + <obj-attribute name="type" type="java.lang.String" db-attribute-path="TYPE"/> + </obj-entity> + <obj-entity name="GenBoy" superEntityName="GenStudent"> + <qualifier><![CDATA[type = "B"]]></qualifier> + <obj-attribute name="toyTrucks" type="java.lang.Short" db-attribute-path="boy.TOY_TRUCKS"/> + </obj-entity> + <obj-entity name="GenGirl" superEntityName="GenStudent"> + <qualifier><![CDATA[type = "G"]]></qualifier> + <obj-attribute name="toyDolls" type="java.lang.Short" db-attribute-path="girl.TOY_DOLLS"/> + </obj-entity> <db-relationship name="sub1" source="IV1_ROOT" target="IV1_SUB1" toDependentPK="true"> <db-attribute-pair source="ID" target="ID"/> </db-relationship> @@ -290,6 +317,18 @@ <db-relationship name="ivRoot1" source="IV_SUB3" target="IV_ROOT"> <db-attribute-pair source="IV_ROOT_ID" target="ID"/> </db-relationship> + <db-relationship name="student" source="GEN_BOY" target="GEN_STUDENT"> + <db-attribute-pair source="ID" target="ID"/> + </db-relationship> + <db-relationship name="student" source="GEN_GIRL" target="GEN_STUDENT"> + <db-attribute-pair source="ID" target="ID"/> + </db-relationship> + <db-relationship name="boy" source="GEN_STUDENT" target="GEN_BOY" toDependentPK="true"> + <db-attribute-pair source="ID" target="ID"/> + </db-relationship> + <db-relationship name="girl" source="GEN_STUDENT" target="GEN_GIRL" toDependentPK="true"> + <db-attribute-pair source="ID" target="ID"/> + </db-relationship> <obj-relationship name="x" source="Iv2Sub1" target="Iv2X" deleteRule="Nullify" db-relationship-path="sub1.x"/> <obj-relationship name="relatedConcrete" source="IvAbstract" target="IvConcrete" deleteRule="Nullify" db-relationship-path="relatedConcrete.abstract"/> <obj-relationship name="others" source="IvBase" target="IvOther" deleteRule="Deny" db-relationship-path="others"/>
