Customize relationship behavior


In this topic


About customizing relationship behavior

A geodatabase has relationship classes that are similar to relationships you can set up with a database management system. Relationship classes manage the associations between objects in one class (feature class or table) and objects in another, and actively maintain the referential integrity between related classes.
When a related class is modified or deleted, related class events, such as move to follow, cascade delete, and rotate on rotate are fired. Class extensions are developed, deployed, and applied to intercept these events and customize their behavior.
Class extensions can also be implemented to filter irrelevant event notifications from unwarranted related classes, thereby creating variations of simple and composite relationships between feature classes and tables.

Implementation

The following section describes a specific scenario explaining how you can customize relationship behavior using class extensions.
Scenario—In the relationship between a utility pole feature class and a transformer feature class, the utility pole and transformer features should maintain their spatial relationship on rotate and move, but must not allow cascade delete. The out-of-the-box simple relationship cannot satisfy the business rule, since the simple relationship does not support maintaining a spatial relationship. The composite relationship is not be suitable, since it defies the cascade delete clause of the business rule.

Develop the class extension

The scenario requires a custom relationship behavior that is a variation of a simple relationship. Create a simple relationship class (POLES_XFORMERS) between the poles and transformers feature class with forward message notification. 
Develop, deploy, and apply a class extension to the transformers feature class and customize the relationship behavior. The class extension must customize the behavior of geometry transformation events (move and rotate) and exclude the notification of cascade delete events of the utility pole related object.
  • Implement the IRelatedObjectClassEvents and IRelatedObjectClassEvents2 interfaces in your class extension to customize related class event behavior from a related object.

    IRelatedObjectClassEvents defines a single method, relatedObjectCreated, which is called when a new object is created in a related class.

    IRelatedObjectClassEvents2 defines additional methods, most notably relatedObjectChanged, which is called when an object in a related class is modified.

    Implement the relatedObjectSetMoved and relatedObjectSetRotated methods to maintain a spatial relationship.
  • Implement the IConfirmSendRelatedObjectEvents interface to customize filter event notifications received by the related objects. IConfirmSendRelatedObjectEvents consists of four methods with Boolean return values, each of which corresponds to a method in the IRelatedObjectClassEvents2 interface.

    If a class extension implements these methods and a method returns false, the corresponding IRelatedObjectClassEvents2 methods will not be called, effectively "short-circuiting" the event notification. This interface is important if an object or feature class is involved in multiple relationships, and the event notification must be received from specific relationship classes.
 
See the following items and code example (also available as a sample):
  • IFeatureClassExtension is implemented, since the class extension will be applied to a feature class.
  • IRelatedObjectClassEvents2 and IConfirmSendRelatedObjectEvents are implemented.
  • IConfirmSendRelatedObjectEvents.confirmSendRelatedObjectChanged() returns false to ignore the notification of change events to exclude cascade delete.
  • IConfirmSendRelatedObjectEvents.confirmSendRelatedObjectSetMoved() and IConfirmSendRelatedObjectEvents.confirmSendRelatedObjectSetRotated() are customized to confirm the event notifications are sent by the appropriate POLES_XFORMERS relationship class.
  • IRelatedObjectClassEvents2.relatedObjectSetMoved() and IRelatedObjectClassEvents2.relatedObjectSetRotated() are implemented so the relationship between the poles and transformer features are maintained spatially.
 
[Java]
package classextensions;
import com.esri.arcgis.geodatabase.*;
import com.esri.arcgis.geometry.ILine;
import com.esri.arcgis.geometry.IPoint;
import com.esri.arcgis.interop.AutomationException;
import com.esri.arcgis.interop.extn.ArcGISCategories;
import com.esri.arcgis.interop.extn.ArcGISExtension;
import com.esri.arcgis.system.*;
/**
 * For this extension to work properly, the poles and transformers relationship class must
 * have (at a minimum) messaging enabled from the poles class to the transformers class, and
 * it is assumed that the relationship class is named "POLES_XFORMERS".
 */
@ArcGISExtension(categories = {
    ArcGISCategories.GeoObjectClassExtensions
}

)public class TransformerExtension implements IClassExtension, IObjectClassExtension,
    IFeatureClassExtension, IRelatedObjectClassEvents2,
    IConfirmSendRelatedObjectEvents{

    /**
     * The name of the relationship class that the custom behavior applies to.
     */
    private final String REL_CLASS_NAME = "POLES_XFORMERS";
    /************************************************************************************************
     * IClassExtension members
     ************************************************************************************************/
    /**
     * Initializes the extension, passing in a reference to its class helper and its extension properties.
     */
    public void init(IClassHelper classHelper, IPropertySet extensionProperties){
        // Do nothing.
    }
    /**
     * Called when the extension's class is being disposed of from memory.
     */
    public void shutdown(){
        // Do nothing.
    }
    /************************************************************************************************
     * IRelatedObjectClassEvents2 members
     ************************************************************************************************/
    /**
     * Called when one or more poles are moved.
     */
    public void relatedObjectSetMoved(IRelationshipClass relationshipClass, ISet
        relatedObjects, ISet movedObjects, ILine moveVector)throws IOException,
        AutomationException{
        // Get the first related object.
        relatedObjects.reset();
        IFeatureEdit featureEdit = new IFeatureEditProxy(relatedObjects.next());
        // Move the set of related objects.
        relatedObjects.reset();
        featureEdit.moveSet(relatedObjects, moveVector);
    }
    /**
     * Called when one or more poles are rotated.
     */
    public void relatedObjectSetRotated(IRelationshipClass relationshipClass, ISet
        relatedObjects, ISet movedObjects, IPoint origin, double rotationAngle)
        throws IOException, AutomationException{
        // Get the first related object.
        relatedObjects.reset();
        IFeatureEdit featureEdit = new IFeatureEditProxy(relatedObjects.next());
        // Rotate the set of related objects.
        relatedObjects.reset();
        featureEdit.rotateSet(relatedObjects, origin, rotationAngle);
    }
    /**
     * Called when the geometry or attributes of a pole are changed.
     */
    public void relatedObjectChanged(IRelationshipClass relationshipClass, IObject
        changedObject, IObject relatedObject)throws IOException, AutomationException{
        // Do nothing.
    }
    /**
     * This method is currently reserved and is not called.
     */
    public void relatedObjectMoved(IRelationshipClass relationshipClass, IObject
        changedObject, ILine moveVector, IObject relatedObject)throws IOException,
        AutomationException{
        // Do nothing. 
    }
    /**
     * This method is currently reserved and is not called.
     */
    public void relatedObjectRotated(IRelationshipClass relationshipClass, IObject
        changedObject, IPoint origin, double rotationAngle, IObject relatedObject)
        throws IOException, AutomationException{
        // Do nothing.
    }
    /************************************************************************************************
     * IConfirmSendRelatedObjectEvents members
     ************************************************************************************************/
    /**
     * Called when one or more poles are moved to determine whether further events should occur.
     */
    public boolean confirmSendRelatedObjectSetMoved(IRelationshipClass
        relationshipClass, ISet movedObjects, ILine moveVector)throws IOException,
        AutomationException{
        // Get the relationship class name.
        IDataset dataset = new IDatasetProxy(relationshipClass);
        String datasetName = dataset.getName();
        return datasetName.equalsIgnoreCase(REL_CLASS_NAME);
    }
    /**
     * Called when one or more poles are rotated to determine whether further events should occur.
     */
    public boolean confirmSendRelatedObjectSetRotated(IRelationshipClass
        relationshipClass, ISet movedObjects, IPoint origin, double rotationAngle)
        throws IOException, AutomationException{
        // Get the relationship class name.
        IDataset dataset = new IDatasetProxy(relationshipClass);
        String datasetName = dataset.getName();
        return datasetName.equalsIgnoreCase(REL_CLASS_NAME);
    }
    /**
     * Called when the geometry or attributes of a pole are changed to determine whether further events should occur.
     */
    public boolean confirmSendRelatedObjectChanged(IRelationshipClass
        relationshipClass, IObject changedObject)throws IOException,
        AutomationException{
        return false;
    }
    /**
     * This method is currently reserved and is not called.
     */
    public boolean confirmSendRelatedObjectMoved(IRelationshipClass
        relationshipClass, IObject changedObject, ILine moveVector)throws
        IOException, AutomationException{
        return false;
    }
    /**
     * This method is currently reserved and is not called.
     */
    public boolean confirmSendRelatedObjectRotated(IRelationshipClass
        relationshipClass, IObject changedObject, IPoint origin, double
        rotationAngle)throws IOException, AutomationException{
        return false;
    }
}
After developing the Java class to deploy the class extension, compile the .java file and bundle the .class file as a Java Archive (JAR) file.

Deploy the class extension

To deploy the class extension, the created JAR file is placed in <ArcGIS Install Dir>/java/lib/ext. The ArcGIS application (ArcGIS Engine, ArcMap, ArcCatalog, and ArcGIS Server) recognizes the class extension when started by its annotation. If the ArcGIS application is running, restart it after the class extension is deployed.

Apply the class extension

The created class extension is applied to the transformer feature class. Apply the class extension for a custom relationship to the feature class (or table) receiving notification. This can be the origin or destination class (or both), depending on the direction of notification (forward or backward) of the relationship class.
For more information about how a class extension can be applied, see Apply class extensions.
For the related class events to be intercepted by the class extension, enable the correct message notification in the relationship class. If the class extension is applied on the origin class, set the notification to backward or both. If the extension is applied on the destination class, set the notification to forward or both.
The following code example shows how to apply the extension programmatically to an existing class:
[Java]
//Import the created class extension class files.
//Do not forget to add the class extension JAR file to the class path.
import classextensions.*;

//Declare the class variable.
public void applyClassExtension(FeatureClass xformerFC){
    try{
        //Verify the pathFieldName attribute is available in the feature class.
        //Verify that no other class extension is applied to the feature class.
        if (xformerFC.getEXTCLSID() == null){
            xformerFC.changeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);
            // Create a unique identifier object (UID) and assign the extension.
            UID extUID = new UID();
            //Notice, the fully qualified class name of the created class extension Java class 
            //is passed as a parameter.
            extUID.setValue(classextension.TransformerExtension.class.getName());

            //Apply the class extension to the feature class.
            xformerFC.alterClassExtensionCLSID(extUID, null);
            xformerFC.changeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
        }
    }
    catch (IOException e){

        e.printStackTrace();
    }

Customize object event behavior

Customize object event behavior by implementing the IObjectClassEvents interface rather than customizing relationship behavior. However, the following are scenarios where implementing a class extension to customize relationship behavior is useful:
  • Event source class already has a class extension applied
  • Creating a variation of relationships, for example, creating a relationship class that moves and rotates related features, but will not cascade deletion

Additional points to remember

  • Unlike many similarly named interfaces, IRelatedObjectClassEvents2 does not inherit from IRelatedObjectClassEvents. For a class extension to define event handlers for both, it must explicitly implement both.
  • The geometry transformation events, IReletedObjectClassEvents2.relatedObjectSetMoved() and IReletedObjectClassEvents2.relatedObjectSetRotated(), are triggered from the IFeatureEdit interface, which is called by the ArcMap editor when features or sets of features are moved or rotated, but not when they are reshaped (in this case, the relatedObjectChanged event is called). Also, if the related object's geometry is modified directly at the geometry level, for example, by using the ITransform2D.rotate() or ITransform2D.move() methods, these events will not be triggered.
  • The relatedObjectMoved and relatedObjectRotated methods of the  IReletedObjectClassEvents2 interface are currently reserved, and must not define any functionality.
  • Implement the IConfirmSendRelatedEvents interface if a class receives notifications from multiple related classes, and if events are only relevant from a specific class. Do this by checking the name of the relationship class in the method implementations.


See Also:

How to implement class extensions
Scenario: Customize attribute validation
Scenario: Customize object event behavior
Apply class extensions