views:

177

answers:

2

I have a few domain model classes in my web app that have a hierarchical relationship to themselves. An example of one is the hierarchical category structure used to classify users postings.

There is some logic relating to the hierarchical nature of these classes that is common. So I tried to move the logic into a generic @MappedSuperclass annotated superclass.

Something like :

@MappedSuperclass
public abstract class HierarchicalBaseEntity<N extends HierarchicalBaseEntity<N>> extends BaseEntity {

@ManyToOne(optional=true)
@JoinColumn(name="parent")
private N parent;
private int depth;

public N getParent() { ...
public void setParent(N newParent) { ...

public boolean isRoot() { ...
public int getDepth() { ...

public boolean isDescendantOf(N ancestor) { ...
public static <N extends HierarchicalBaseEntity<N>> N getCommonAncestor(N a, N b) { ...
public static <N extends HierarchicalBaseEntity<N>> Collection<N> reduceToCommonAncestors(Collection<N> entities) { ...
}

The subclasses then extend HierarchicalBaseEntity giving themselves as the generic type N:

@Entity
public class CategoryBean extends HierarchicalBaseEntity<CategoryBean> {

In Java this all works out quite cleanly. But unfortunately EclipseLink doesn't seems to like the generic 'parent' field:

private N parent;

It gives the following exception:

Caused by: Exception [EclipseLink-7250] (Eclipse Persistence Services - 2.1.0.v20100614-r7608): org.eclipse.persistence.exceptions.ValidationException
Exception Description: [class net.timp.yaase.core.model.HierarchicalBaseEntity] uses a non-entity [class java.lang.String] as target entity in the relationship attribute [field parent].
at org.eclipse.persistence.exceptions.ValidationException.nonEntityTargetInRelationship(ValidationException.java:1341)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.getReferenceDescriptor(RelationshipAccessor.java:416)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:609)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:678)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:107)

Why is it complain about a non-entity String?

As a test I've tried removing the generics and just having the parent field defined as:

private HierarchicalBaseEntity parent;

Without generics, EclipseLink gave this exception:

Caused by: Exception [EclipseLink-7250] (Eclipse Persistence Services - 2.1.0.v20100614-r7608): org.eclipse.persistence.exceptions.ValidationException
Exception Description: [class net.timp.yaase.core.model.OnymBean] uses a non-entity [class net.timp.yaase.core.model.HierarchicalBaseEntity] as target entity in the relationship attribute [field parent].
at org.eclipse.persistence.exceptions.ValidationException.nonEntityTargetInRelationship(ValidationException.java:1341)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.getReferenceDescriptor(RelationshipAccessor.java:416)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:609)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:678)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:107)

True HierarchicalBaseEntity its not an Entity in either case, is a @MappedSuperclass.. but is there a way to do this with generics or otherwise? It seems you can't have a field in your @MappedSuperclass that references one of it's subclass.

+2  A: 

The issue is that when using Generics as field types for relationships EclipseLink can not know what the target type is until runtime when the actual instance is inspected. So the mapping would have to be dynamically created at runtime and this is not supported.

You could continue to use the Generic SuperClass but this would require moving the field to the Entities where they would have types defined then have abstract internal getters/setters for those fields that return Object that the generic methods would call casting to the generic type. Convoluted but it would allow for the Generic MappedSuperclass.

Gordon Yorke
Hi Gordon, Thanks for your answer. I'll try your suggestion of putting the fields in the subclasses.. I can see how it would work. I currently have a HierarchicalEntity interface and a static 'helper' class with the logic in. Works, but it is not as clean as I would like. I was wondering if @AssociationOverride could be used to redefine the association in some way..
Tim P
Unfortunately in this case the issue is with the attribute type. @AssociationOverrides are used to change the Database information but have no attributes to specify the target type.
Gordon Yorke
Hi Again Gordon, I can't find a cleaner solution than what you have suggested for implementing a superclass for the common logic with abstract getters. So you get my vote. BTW. Do you know if this is possible in any other JPA implemetations or is it part of the JPA spec?
Tim P
I do not know if any of the other providers support this but I doubt it. The specification does not specifically mention generic attribute types but readers are warned that overriding the mapping meta data is not portable.
Gordon Yorke
A: 

I think I'm late, but people looking for the answer to this problem (like me) should take a look at this: https://bugs.eclipse.org/bugs/show_bug.cgi?id=312132

narduk