You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2024/07/09 15:01:35 UTC
(cayenne) branch 5.0-FIX-CAY-2667-generic-inheritance created (now d57f6e002)
This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a change to branch 5.0-FIX-CAY-2667-generic-inheritance
in repository https://gitbox.apache.org/repos/asf/cayenne.git
at d57f6e002 CAY-2667 Fix Issues with Generic Vertical Inheritance
This branch includes the following new commits:
new d57f6e002 CAY-2667 Fix Issues with Generic Vertical Inheritance
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(cayenne) 01/01: CAY-2667 Fix Issues with Generic Vertical Inheritance
Posted by nt...@apache.org.
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 <st...@gmail.com>
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"/>