Skip to content

Commit d2169b4

Browse files
authored
Merge pull request #621 from apache/5.0-FIX-CAY-2667-generic-inheritance
CAY-2667 Fix Issues with Generic Vertical Inheritance
2 parents 927b609 + 208199e commit d2169b4

File tree

10 files changed

+172
-38
lines changed

10 files changed

+172
-38
lines changed

cayenne/src/main/java/org/apache/cayenne/access/DataContextObjectCreator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ protected void injectInitialValue(Object obj) {
209209

210210
ObjEntity entity;
211211
try {
212-
entity = context.getEntityResolver().getObjEntity(object.getClass());
212+
entity = context.getEntityResolver().getObjEntity(object.getObjectId().getEntityName());
213213
} catch (CayenneRuntimeException ex) {
214214
// ObjEntity cannot be fetched, ignored
215215
entity = null;

cayenne/src/main/java/org/apache/cayenne/reflect/ArcProperty.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
package org.apache.cayenne.reflect;
2121

22-
import org.apache.cayenne.map.DbRelationship;
2322
import org.apache.cayenne.map.ObjRelationship;
2423

2524
/**
@@ -55,7 +54,7 @@ public interface ArcProperty extends PropertyDescriptor {
5554
* Returns a ClassDescriptor for the type of graph nodes pointed to by this
5655
* arc property. Note that considering that a target object may be a
5756
* subclass of the class handled by the descriptor, users of this method may
58-
* need to call {@link ClassDescriptor#getSubclassDescriptor(Class)} before
57+
* need to call {@link ClassDescriptor#getSubclassDescriptor(String)} before
5958
* using the descriptor to access objects.
6059
*/
6160
ClassDescriptor getTargetDescriptor();

cayenne/src/main/java/org/apache/cayenne/reflect/ClassDescriptor.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,25 @@ public interface ClassDescriptor {
8888
ClassDescriptor getSuperclassDescriptor();
8989

9090
/**
91-
* Returns the most "specialized" descriptor for a given class. This method assumes
92-
* that the following is true:
91+
* Returns the most "specialized" descriptor for a given class.
92+
* This method assumes that the following is true:
9393
*
9494
* <pre>
9595
* this.getObjectClass().isAssignableFrom(objectClass)
9696
* </pre>
97+
* @deprecated since 5.0, will throw UnsupportedOperationException on invocation,
98+
* use {@link #getSubclassDescriptor(String)}
9799
*/
98-
ClassDescriptor getSubclassDescriptor(Class<?> objectClass);
100+
@Deprecated(since = "5.0", forRemoval = true)
101+
default ClassDescriptor getSubclassDescriptor(Class<?> unused) {
102+
throw new UnsupportedOperationException("This method is deprecated, use getSubclassDescriptor(entityName) instead");
103+
}
104+
105+
/**
106+
* Returns the most "specialized" descriptor for a given entity name.
107+
* @since 5.0
108+
*/
109+
ClassDescriptor getSubclassDescriptor(String entityName);
99110

100111
/**
101112
* Creates a new instance of a class described by this object.

cayenne/src/main/java/org/apache/cayenne/reflect/LazyClassDescriptorDecorator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ public PropertyDescriptor getProperty(String propertyName) {
147147
return descriptor.getProperty(propertyName);
148148
}
149149

150-
public ClassDescriptor getSubclassDescriptor(Class<?> objectClass) {
150+
@Override
151+
public ClassDescriptor getSubclassDescriptor(String entityName) {
151152
checkDescriptorInitialized();
152-
return descriptor.getSubclassDescriptor(objectClass);
153+
return descriptor.getSubclassDescriptor(entityName);
153154
}
154155

155156
public ClassDescriptor getSuperclassDescriptor() {

cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@
4646
*/
4747
public class PersistentDescriptor implements ClassDescriptor {
4848

49-
static final Integer TRANSIENT_STATE = PersistenceState.TRANSIENT;
5049
static final Integer HOLLOW_STATE = PersistenceState.HOLLOW;
51-
static final Integer COMMITTED_STATE = PersistenceState.COMMITTED;
5250

5351
protected ClassDescriptor superclassDescriptor;
5452

@@ -85,7 +83,7 @@ public PersistentDescriptor() {
8583
this.subclassDescriptors = new HashMap<>();
8684

8785
// must be a set as duplicate addition attempts are expected...
88-
this.rootDbEntities = new HashSet<DbEntity>(1);
86+
this.rootDbEntities = new HashSet<>(1);
8987
}
9088

9189
public void setDiscriminatorColumns(Collection<ObjAttribute> columns) {
@@ -213,14 +211,11 @@ public void removeDeclaredProperty(String propertyName) {
213211
/**
214212
* Adds a subclass descriptor that maps to a given class name.
215213
*/
216-
public void addSubclassDescriptor(String className, ClassDescriptor subclassDescriptor) {
217-
// note that 'className' should be used instead of
218-
// "subclassDescriptor.getEntity().getClassName()", as this method is
219-
// called in
220-
// the early phases of descriptor initialization and we do not want to
221-
// trigger
222-
// subclassDescriptor resolution just yet to prevent stack overflow.
223-
subclassDescriptors.put(className, subclassDescriptor);
214+
public void addSubclassDescriptor(String entityName, ClassDescriptor subclassDescriptor) {
215+
// NOTE: 'entityName' should be used instead of "subclassDescriptor.getEntity().getName()",
216+
// as this method is called in the early phases of descriptor initialization, and we do not want to
217+
// trigger subclassDescriptor resolution just yet to prevent stack overflow.
218+
subclassDescriptors.put(entityName, subclassDescriptor);
224219
}
225220

226221
public ObjEntity getEntity() {
@@ -259,31 +254,21 @@ void setObjectClass(Class<?> objectClass) {
259254
this.objectClass = objectClass;
260255
}
261256

262-
public ClassDescriptor getSubclassDescriptor(Class<?> objectClass) {
263-
if (objectClass == null) {
257+
public ClassDescriptor getSubclassDescriptor(String entityName) {
258+
if (entityName == null) {
264259
throw new IllegalArgumentException("Null objectClass");
265260
}
266261

267262
if (subclassDescriptors.isEmpty()) {
268263
return this;
269264
}
270265

271-
ClassDescriptor subclassDescriptor = subclassDescriptors.get(objectClass.getName());
272-
273-
// ascend via the class hierarchy (only doing it if there are multiple
274-
// choices)
275-
if (subclassDescriptor == null) {
276-
Class<?> currentClass = objectClass;
277-
while (subclassDescriptor == null && (currentClass = currentClass.getSuperclass()) != null) {
278-
subclassDescriptor = subclassDescriptors.get(currentClass.getName());
279-
}
280-
}
281-
266+
ClassDescriptor subclassDescriptor = subclassDescriptors.get(entityName);
282267
return subclassDescriptor != null ? subclassDescriptor : this;
283268
}
284269

285270
public Collection<ObjAttribute> getDiscriminatorColumns() {
286-
return allDiscriminatorColumns != null ? allDiscriminatorColumns : Collections.<ObjAttribute> emptyList();
271+
return allDiscriminatorColumns != null ? allDiscriminatorColumns : Collections.emptyList();
287272
}
288273

289274
public Collection<AttributeProperty> getIdProperties() {

cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ public ClassDescriptor getDescriptor(String entityName) {
6565
protected ClassDescriptor getDescriptor(ObjEntity entity, Class<?> entityClass) {
6666
String superEntityName = entity.getSuperEntityName();
6767

68-
ClassDescriptor superDescriptor = (superEntityName != null) ? descriptorMap.getDescriptor(superEntityName)
68+
ClassDescriptor superDescriptor = (superEntityName != null)
69+
? descriptorMap.getDescriptor(superEntityName)
6970
: null;
7071

7172
PersistentDescriptor descriptor = createDescriptor();
@@ -175,7 +176,7 @@ protected void indexSubclassDescriptors(PersistentDescriptor descriptor, EntityI
175176

176177
for (EntityInheritanceTree child : inheritanceTree.getChildren()) {
177178
ObjEntity childEntity = child.getEntity();
178-
descriptor.addSubclassDescriptor(childEntity.getClassName(),
179+
descriptor.addSubclassDescriptor(childEntity.getName(),
179180
descriptorMap.getDescriptor(childEntity.getName()));
180181

181182
indexSubclassDescriptors(descriptor, child);
@@ -200,8 +201,7 @@ private void appendDeclaredRootDbEntity(PersistentDescriptor descriptor, ObjEnti
200201
DbEntity dbEntity = entity.getDbEntity();
201202
if (dbEntity != null) {
202203
// descriptor takes care of weeding off duplicates, which are likely
203-
// in cases
204-
// of non-horizontal inheritance
204+
// in cases of non-horizontal inheritance
205205
descriptor.addRootDbEntity(dbEntity);
206206
}
207207
}

cayenne/src/main/java/org/apache/cayenne/util/DeepMergeOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private <T extends Persistent> T merge(
8383
final T target = shallowMergeOperation.merge(peerInParentContext);
8484
seen.put(id, target);
8585

86-
descriptor = descriptor.getSubclassDescriptor(peerInParentContext.getClass());
86+
descriptor = descriptor.getSubclassDescriptor(id.getEntityName());
8787
descriptor.visitProperties(new PropertyVisitor() {
8888

8989
public boolean visitToOne(ToOneProperty property) {

cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.apache.cayenne.Cayenne;
2222
import org.apache.cayenne.ObjectContext;
23+
import org.apache.cayenne.Persistent;
2324
import org.apache.cayenne.di.Inject;
2425
import org.apache.cayenne.query.ColumnSelect;
2526
import org.apache.cayenne.query.EJBQLQuery;
@@ -1234,4 +1235,37 @@ public void testColumnSelectVerticalInheritance_Sub1Sub1() throws SQLException {
12341235
assertEquals("mDA", result.getSub1Name());
12351236
assertEquals("3DQa", result.getSub1Sub1Name());
12361237
}
1238+
1239+
@Test
1240+
public void testInsertTwoGenericVerticalInheritanceObjects() {
1241+
// Generic DataObjects play nicer with a DataContext
1242+
final DataContext dataContext = (DataContext) context;
1243+
1244+
final Persistent girlEmma = dataContext.newObject("GenGirl");
1245+
final Persistent boyLuke = dataContext.newObject("GenBoy");
1246+
1247+
assertEquals("Girl is type G", girlEmma.readProperty("type"), "G");
1248+
assertEquals("Boy is type B", boyLuke.readProperty("type"), "B");
1249+
1250+
girlEmma.writeProperty("reference", "g1");
1251+
girlEmma.writeProperty("name", "Emma");
1252+
girlEmma.writeProperty("toyDolls", 5);
1253+
1254+
boyLuke.writeProperty("reference", "b1");
1255+
boyLuke.writeProperty("name", "Luke");
1256+
boyLuke.writeProperty("toyTrucks", 12);
1257+
1258+
context.commitChanges();
1259+
1260+
assertEquals(2, ObjectSelect.query(Persistent.class, "GenStudent").selectCount(context));
1261+
1262+
final List<Persistent> students = ObjectSelect.query(Persistent.class, "GenStudent").select(context);
1263+
assertTrue(students.contains(girlEmma));
1264+
assertTrue(students.contains(boyLuke));
1265+
1266+
final List<Persistent> girls = ObjectSelect.query(Persistent.class, "GenGirl").select(context);
1267+
assertEquals(1, girls.size());
1268+
final List<Persistent> boys = ObjectSelect.query(Persistent.class, "GenBoy").select(context);
1269+
assertEquals(1, boys.size());
1270+
}
12371271
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
****************************************************************/
19+
package org.apache.cayenne.reflect.generic;
20+
21+
import org.apache.cayenne.GenericPersistentObject;
22+
import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
23+
import org.apache.cayenne.di.Inject;
24+
import org.apache.cayenne.map.EntityResolver;
25+
import org.apache.cayenne.reflect.ClassDescriptor;
26+
import org.apache.cayenne.reflect.SingletonFaultFactory;
27+
import org.apache.cayenne.unit.di.runtime.CayenneProjects;
28+
import org.apache.cayenne.unit.di.runtime.RuntimeCase;
29+
import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime;
30+
import org.junit.Test;
31+
32+
import static org.junit.Assert.*;
33+
import static org.mockito.Mockito.mock;
34+
35+
@UseCayenneRuntime(CayenneProjects.INHERITANCE_VERTICAL_PROJECT)
36+
public class PersistentObjectDescriptorFactory_VerticalInheritanceIT extends RuntimeCase {
37+
38+
@Inject
39+
private EntityResolver resolver;
40+
41+
@Test
42+
public void testVisitProperties_IterationOrder() {
43+
44+
PersistentObjectDescriptorFactory factory = new PersistentObjectDescriptorFactory(
45+
resolver.getClassDescriptorMap(),
46+
new SingletonFaultFactory(),
47+
new DefaultValueComparisonStrategyFactory(mock(ValueObjectTypeRegistry.class))
48+
);
49+
50+
ClassDescriptor genStudent = factory.getDescriptor("GenStudent");
51+
assertNotNull(genStudent);
52+
ClassDescriptor genBoy = genStudent.getSubclassDescriptor("GenBoy");
53+
assertNotNull(genBoy);
54+
ClassDescriptor genGirl = genStudent.getSubclassDescriptor("GenGirl");
55+
assertNotNull(genGirl);
56+
57+
assertNotSame(genStudent, genBoy);
58+
assertNotSame(genStudent, genGirl);
59+
assertNotSame(genBoy, genGirl);
60+
61+
assertEquals(GenericPersistentObject.class, genBoy.getObjectClass());
62+
assertEquals(GenericPersistentObject.class, genGirl.getObjectClass());
63+
assertEquals(GenericPersistentObject.class, genStudent.getObjectClass());
64+
}
65+
}

cayenne/src/test/resources/inheritance-vertical.map.xml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@
8383
<db-attribute name="SUB1_NAME" type="VARCHAR" length="100"/>
8484
<db-attribute name="SUB1_PRICE" type="DOUBLE"/>
8585
</db-entity>
86+
<db-entity name="GEN_STUDENT">
87+
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
88+
<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="50"/>
89+
<db-attribute name="REFERENCE" type="VARCHAR" isMandatory="true" length="10"/>
90+
<db-attribute name="TYPE" type="CHAR" isMandatory="true" length="1"/>
91+
</db-entity>
92+
<db-entity name="GEN_BOY">
93+
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
94+
<db-attribute name="TOY_TRUCKS" type="SMALLINT" length="4"/>
95+
</db-entity>
96+
<db-entity name="GEN_GIRL">
97+
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
98+
<db-attribute name="TOY_DOLLS" type="SMALLINT" length="4"/>
99+
</db-entity>
86100
<db-entity name="IV_SUB1_SUB1">
87101
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
88102
<db-attribute name="SUB1_SUB1_NAME" type="VARCHAR" length="100"/>
@@ -176,6 +190,19 @@
176190
<qualifier><![CDATA[discriminator = "IvSub3"]]></qualifier>
177191
<pre-persist method-name="onPrePersist"/>
178192
</obj-entity>
193+
<obj-entity name="GenStudent" abstract="true" dbEntityName="GEN_STUDENT">
194+
<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
195+
<obj-attribute name="reference" type="java.lang.String" db-attribute-path="REFERENCE"/>
196+
<obj-attribute name="type" type="java.lang.String" db-attribute-path="TYPE"/>
197+
</obj-entity>
198+
<obj-entity name="GenBoy" superEntityName="GenStudent">
199+
<qualifier><![CDATA[type = "B"]]></qualifier>
200+
<obj-attribute name="toyTrucks" type="java.lang.Short" db-attribute-path="boy.TOY_TRUCKS"/>
201+
</obj-entity>
202+
<obj-entity name="GenGirl" superEntityName="GenStudent">
203+
<qualifier><![CDATA[type = "G"]]></qualifier>
204+
<obj-attribute name="toyDolls" type="java.lang.Short" db-attribute-path="girl.TOY_DOLLS"/>
205+
</obj-entity>
179206
<db-relationship name="sub1" source="IV1_ROOT" target="IV1_SUB1" toDependentPK="true">
180207
<db-attribute-pair source="ID" target="ID"/>
181208
</db-relationship>
@@ -290,6 +317,18 @@
290317
<db-relationship name="ivRoot1" source="IV_SUB3" target="IV_ROOT">
291318
<db-attribute-pair source="IV_ROOT_ID" target="ID"/>
292319
</db-relationship>
320+
<db-relationship name="student" source="GEN_BOY" target="GEN_STUDENT">
321+
<db-attribute-pair source="ID" target="ID"/>
322+
</db-relationship>
323+
<db-relationship name="student" source="GEN_GIRL" target="GEN_STUDENT">
324+
<db-attribute-pair source="ID" target="ID"/>
325+
</db-relationship>
326+
<db-relationship name="boy" source="GEN_STUDENT" target="GEN_BOY" toDependentPK="true">
327+
<db-attribute-pair source="ID" target="ID"/>
328+
</db-relationship>
329+
<db-relationship name="girl" source="GEN_STUDENT" target="GEN_GIRL" toDependentPK="true">
330+
<db-attribute-pair source="ID" target="ID"/>
331+
</db-relationship>
293332
<obj-relationship name="x" source="Iv2Sub1" target="Iv2X" deleteRule="Nullify" db-relationship-path="sub1.x"/>
294333
<obj-relationship name="relatedConcrete" source="IvAbstract" target="IvConcrete" deleteRule="Nullify" db-relationship-path="relatedConcrete.abstract"/>
295334
<obj-relationship name="others" source="IvBase" target="IvOther" deleteRule="Deny" db-relationship-path="others"/>

0 commit comments

Comments
 (0)