Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAY-2667 Fix Issues with Generic Vertical Inheritance #432

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
*
* <pre>
* this.getObjectClass().isAssignableFrom(objectClass)
* this.getObjectClass().isAssignableFrom(subEntity.getJavaClass())
* </pre>
*/
ClassDescriptor getSubclassDescriptor(Class<?> objectClass);
ClassDescriptor getSubclassDescriptor(ObjEntity subEntity);

/**
* 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 @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,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(entityResolver.getObjEntity(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 @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,25 @@
****************************************************************/
package org.apache.cayenne.access;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collections;
import java.util.HashMap;
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;
import org.apache.cayenne.query.EJBQLQuery;
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;
Expand Down Expand Up @@ -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<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 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<DataObject> students = ObjectSelect.query(DataObject.class, "GenStudent").select(context);
assertTrue(students.contains(girlEmma));
assertTrue(students.contains(boyLuke));

final List<DataObject> girls = ObjectSelect.query(DataObject.class, "GenGirl").select(context);
assertEquals(1, girls.size());
final List<DataObject> boys = ObjectSelect.query(DataObject.class, "GenBoy").select(context);
assertEquals(1, boys.size());
}
}
39 changes: 39 additions & 0 deletions cayenne-server/src/test/resources/inheritance-vertical.map.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
<db-attribute name="IV_ROOT_ID" type="INTEGER" isMandatory="true"/>
</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>
<obj-entity name="Iv1Root" className="org.apache.cayenne.testdo.inheritance_vertical.Iv1Root" dbEntityName="IV1_ROOT">
<obj-attribute name="discriminator" type="java.lang.String" db-attribute-path="DISCRIMINATOR"/>
<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
Expand Down Expand Up @@ -149,6 +163,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 @@ -233,6 +260,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="children" source="IvConcrete" target="IvConcrete" deleteRule="Deny" db-relationship-path="children"/>
<obj-relationship name="parent" source="IvConcrete" target="IvConcrete" deleteRule="Nullify" db-relationship-path="parent"/>
Expand Down