Apache Cayenne > Index > Cayenne FAQ > Is Inheritance Supported > CompositeVerticalInheritance

Vertical inheritance can be partially-simulated using composite DataObject.

For example, if you have INTERNAL_CUSTOMER -> CUSTOMER -> PERSON as an inheritance chain, an InternalCustomer DataObject would contain a relationship to a Customer DataObject and have wrapper methods for all of Customer's methods. In the same way, Customer would be a composite object with Person.

Note that you cannot use the single-table inheritance attribute in the modeler to represent your vertical inheritance chain.

One way to set this up is to (mis)use the Cayenne 1.2 "java client class" and "java client superclass" modeler attributes (assuming you are not using Cayenne in a distributed client-server mode). Put the inheritance chain parent ObjEntity name in "java client superclass" (ie, "Customer"). Put the relationship getter method in "java client class" (ie, "getCustomer"). Use the following template to generate an __INTERNAL_CUSTOMER class. Change InternalCustomer to inherit from this class.

You also need to set modeler delete rules so that when a child is deleted, a parent object is automatically deleted.
Creating a composite object is handled by the template.

Note that query paths will not work using the flattened wrapper attributes. Also, I suspect that marking the client object HOLLOW (invalidating or refetching are examples of this) will not properly mark the parent(s) HOLLOW.

Here's an example template:

##Terminology:
##	Base class - super superclass of entity, ie, org.objectstyle.cayenne.CayenneDataObject or MyBaseClass
##  Super class - superclass of entity, ie,  org.objectstyle.cayenne.art.auto._Artist
##	Sub class - class of entity, ie, org.objectstyle.cayenne.art.Artist
##
##  Classes available in template
##    objEntity - the ObjEntity class: See org.objectstyle.cayenne.map.ObjectEntity
##    stringUtils - class for string "helper" functions: See org.objectstyle.cayenne.gen.StringUtils
##    entityUtils - class for entity "helper" functions: See org.objectstyle.cayenne.gen.EntityUtils
##    importUtils - class for import statement management: See org.objectstyle.cayenne.gen.ImportUtils
##
##
${importUtils.setPackage($entityUtils.subPackageName)}##
${importUtils.addType("${entityUtils.superPackageName}.${entityUtils.superClassName}")}##
#macro (generateImportsForObjEntity $anObjEntity $inheritanceRelationName)
#foreach( $attr in ${anObjEntity.DeclaredAttributes} )
$importUtils.addType(${attr.Type})##
#end
#foreach( $rel in ${anObjEntity.DeclaredRelationships} )
$importUtils.addType(${rel.TargetEntity.ClassName})##
#end
#if( ${entityUtils.hasToManyRelationships($anObjEntity)} )
${importUtils.addType('java.util.List')}##
#end
#set ( $nextSuperEntity = $entityUtils.getEntityResolver().getObjEntity($anObjEntity.getClientSuperClassName()) )
#set ( $nextInheritanceRelationName = ${anObjEntity.getClientClassName()} )
#if ($entityUtils.getEntityResolver().getObjEntity($anObjEntity.getClientSuperClassName()))
#generateImportsForObjEntity($nextSuperEntity "${inheritanceRelationName}().${nextInheritanceRelationName}")
#end## ($nextSuperEntity)
#end##macro generateImportsForObjEntity
##
#set ( $superEntity = $entityUtils.getEntityResolver().getObjEntity($objEntity.getClientSuperClassName()) )
#set ( $inheritanceRelationName = $objEntity.getClientClassName() )
#generateImportsForObjEntity($superEntity $inheritanceRelationName)
$importUtils.addType("org.objectstyle.cayenne.PersistenceState")##
${importUtils.generate()}

/** Class _${entityUtils.superClassName} was generated by Cayenne.
  * It is probably a good idea to avoid changing this class manually, 
  * since it may be overwritten next time code is regenerated. 
  * If you need to make any customizations, please use subclass. 
  */
public class _${entityUtils.superClassName} extends ${entityUtils.superClassName} {

#if( $objEntity.DbEntity )
    // primary key properties
#foreach( $idAttr in ${objEntity.DbEntity.PrimaryKey} )
    public static final String ${stringUtils.capitalizedAsConstant($idAttr.Name)}_PK_COLUMN = "${idAttr.Name}";
#end
#end
#macro (generatePropertyNamesForObjEntity $anObjEntity $inheritanceRelationName)

   // ${anObjEntity.getName()} properties
## Create property names
#foreach( $attr in ${anObjEntity.DeclaredAttributes} )
    public static final String ${stringUtils.capitalizedAsConstant($attr.Name)}_PROPERTY = "${attr.Name}";
#end
#foreach( $rel in ${anObjEntity.DeclaredRelationships} )
    public static final String ${stringUtils.capitalizedAsConstant($rel.Name)}_PROPERTY = "${rel.Name}";
#end
#if ($anObjEntity.getSuperEntity())
#generatePropertyNamesForObjEntity($anObjEntity.getSuperEntity() "${inheritanceRelationName}().${anObjEntity.getClientClassName()}")
#end## ($anObjEntity.getSuperEntity())
#end##macro generatePropertyNamesForObjEntity
#macro (generateAttributesForObjEntity $anObjEntity $inheritanceRelationName)

   // ${anObjEntity.getName()} attributes
## Create attribute set/get methods
#foreach( $attr in ${anObjEntity.DeclaredAttributes} )
#if ("true" != "${anObjEntity.isReadOnly()}")
    public void set${stringUtils.capitalized($attr.Name)}($importUtils.formatJavaType(${attr.Type}) $stringUtils.formatVariableName(${attr.Name})) {
        ${inheritanceRelationName}().set${stringUtils.capitalized($attr.Name)}($stringUtils.formatVariableName(${attr.Name}));
        // writeProperty("${attr.Name}", $stringUtils.formatVariableName(${attr.Name}));
    }
#end
    public $importUtils.formatJavaType(${attr.Type}) get${stringUtils.capitalized($attr.Name)}() {
        return ${inheritanceRelationName}().get${stringUtils.capitalized($attr.Name)}();
        // return ($importUtils.formatJavaType(${attr.Type}))readProperty("${attr.Name}");
    }
    
    
#end
##
## Create list add/remove/get methods
#foreach( $rel in ${anObjEntity.DeclaredRelationships} )
#if( $rel.ToMany )
#if ( ! $rel.ReadOnly )
    public void addTo${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClassName}) obj) {
        ${inheritanceRelationName}().addTo${stringUtils.capitalized($rel.Name)}(obj);
        // addToManyTarget("${rel.name}", obj, true);
    }
    public void removeFrom${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClassName}) obj) {
        ${inheritanceRelationName}().removeFrom${stringUtils.capitalized($rel.Name)}(obj);
        // removeToManyTarget("${rel.name}", obj, true);
    }
#end
    public List get${stringUtils.capitalized($rel.Name)}() {
        return (List)readProperty("${rel.name}");
    }
#else
#if ( !${anObjEntity.isReadOnly()} && !$rel.ReadOnly )
    public void set${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClassName}) $stringUtils.formatVariableName(${rel.name})) {
        ${inheritanceRelationName}().set${stringUtils.capitalized($rel.Name)}($stringUtils.formatVariableName(${rel.name}));
        // setToOneTarget("${rel.name}", $stringUtils.formatVariableName(${rel.name}), true);
    }
#end

    public $importUtils.formatJavaType(${rel.TargetEntity.ClassName}) get${stringUtils.capitalized($rel.Name)}() {
        return ${inheritanceRelationName}().get${stringUtils.capitalized($rel.Name)}();
        // return ($importUtils.formatJavaType(${rel.TargetEntity.ClassName}))readProperty("${rel.name}");
    } 
#end
#end
#set ( $nextSuperEntity = $entityUtils.getEntityResolver().getObjEntity($anObjEntity.getClientSuperClassName()) )
#set ( $nextInheritanceRelationName = ${anObjEntity.getClientClassName()} )
#if ($entityUtils.getEntityResolver().getObjEntity($anObjEntity.getClientSuperClassName()))
#generateAttributesForObjEntity($nextSuperEntity "${inheritanceRelationName}().${nextInheritanceRelationName}")
#end## ($nextSuperEntity)
#end##macro generateAttributesForObjEntity
##
#set ( $superEntity = $entityUtils.getEntityResolver().getObjEntity($objEntity.getClientSuperClassName()) )
#set ( $inheritanceRelationName = $objEntity.getClientClassName() )
#generatePropertyNamesForObjEntity($superEntity $inheritanceRelationName)
    
    public void setPersistenceState(int persistenceState)
    {
        super.setPersistenceState(persistenceState);

        if (PersistenceState.NEW == persistenceState) {
            ${superEntity.getName()} parent = ${inheritanceRelationName}();
            if (null == parent) { // parent does not exist
                if (null != this.getDataContext()) { // child registered in a DataContext
                    parent = (${superEntity.getName()})dataContext.createAndRegisterNewObject(${superEntity.getName()}.class);
                }
                else {
                    parent = new ${superEntity.getName()}();
                    parent.setPersistenceState(PersistenceState.NEW);
                }
                
                this.s${inheritanceRelationName.substring(1)}(parent);
            }
            else { // parent already exists
                if (null != this.getDataContext()) { // child registered in a DataContext
                    if (null == parent.getDataContext()) {
                        dataContext.registerNewObject(parent);
                    }
                    else if (parent.getDataContext() != this.getDataContext()) {
                        throw new RuntimeException("entity and superEntity are in different DataContexts.");
                    }
                }
                else { // there's no data context
                    parent = new ${superEntity.getName()}();
                    parent.setPersistenceState(PersistenceState.NEW);
                }
            }
        }
    }
#generateAttributesForObjEntity($superEntity $inheritanceRelationName)
}

Here's an example ant target to use the above template:

    <target name="generate vi entity" depends="" description="generate entity">
        <cgen map="${template.datamap}"
            additionalMaps="${additional.template.datamaps}"
            version="1.2"
            makepairs="false"
            overwrite="false"
            mode="entity"
            includeEntities="${template.entity}"
            destDir="${java.src}"
            superpkg="${dataMapModelPackage}.entity.generated"
            template="${java.templates}/vertical-inheritance-superclass.vm"
            outputPattern="__*.java"
            usepkgpath="true">
            <config refid="vppconfig.datamap" />
        </cgen>
    </target>