Skip to content

Commit

Permalink
CAY-2667 Fix Issues with Generic Vertical Inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
stariy95 committed Jul 9, 2024
1 parent fc376af commit d57f6e0
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ protected void injectInitialValue(Object obj) {

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

package org.apache.cayenne.reflect;

import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.ObjRelationship;

/**
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ public PropertyDescriptor getProperty(String propertyName) {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@
*/
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;

Expand Down Expand Up @@ -85,7 +83,7 @@ public PersistentDescriptor() {
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) {
Expand Down Expand Up @@ -213,14 +211,11 @@ public void removeDeclaredProperty(String propertyName) {
/**
* 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() {
Expand Down Expand Up @@ -259,31 +254,21 @@ void setObjectClass(Class<?> objectClass) {
this.objectClass = objectClass;
}

public ClassDescriptor getSubclassDescriptor(Class<?> objectClass) {
if (objectClass == null) {
public ClassDescriptor getSubclassDescriptor(String entityName) {
if (entityName == null) {
throw new IllegalArgumentException("Null objectClass");
}

if (subclassDescriptors.isEmpty()) {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public ClassDescriptor getDescriptor(String entityName) {
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();
Expand Down Expand Up @@ -175,7 +176,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);
Expand All @@ -200,8 +201,7 @@ private void appendDeclaredRootDbEntity(PersistentDescriptor descriptor, ObjEnti
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private <T extends Persistent> T merge(
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@

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;
Expand All @@ -37,6 +41,7 @@
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;
Expand Down Expand Up @@ -1234,4 +1239,49 @@ public void testColumnSelectVerticalInheritance_Sub1Sub1() throws SQLException {
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());
}
}
39 changes: 39 additions & 0 deletions cayenne/src/test/resources/inheritance-vertical.map.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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"/>
Expand Down

0 comments on commit d57f6e0

Please sign in to comment.