Customize object edit events behavior


Summary
This topic describes the timestamp scenario and explains the customization of the object edit events behavior by developing, deploying, and applying class extensions.

In this topic


About customizing object event behavior

Class extensions allow you to listen to events that fire when one or more rows in a table or a feature class is created, modified, or deleted. The class extension intercepts these events and customizes the event behavior. The following are some of the common scenarios to customize object event behavior:

Implementation

The following timestamp scenario explains customizing object event behavior:
Scenario—Create a timestamp when a row is created or modified and populate the corresponding fields in the table with the timestamp. The following illustration and description shows how creating and applying a class extension to the DEEDS feature class can customize the behavior of its object creation and modification events.

  1. New row with ObjectID (OID) 723 is created.
  2. The Object create event is triggered by the DEEDS feature class and intercepted by the class extension. The custom behavior of the Object Create event of the class extension is invoked.
  3. The custom behavior populates the Created_At field of the new row with the current timestamp.
  4. The Owner attribute of the row (OID 546) is modified as Tom Cruise.
  5. The Object change event is triggered by the DEEDS feature class and intercepted by the class extension. The custom behavior of the Object Change event of the class extension is invoked.
  6. The custom behavior for the Object Change event populates the Modified_At field with the current timestamp.
 
In a similar manner you could develop and apply class extensions to customize the behavior of object events of your object and feature class.

Develop the class extension

To implement the timestamp scenario, develop a class extension that implements the _IObjectClassEvents interface and customizes the object events behavior. The _IObjectClassEvents.onCreate(), _IObjectsClassEvents.onChange(), and _IObjectsClassEvents.onDelete() methods of the class extension are invoked when an object (record) is created, modified, or deleted in a table, respectively.
In the scenario, the onCreate event behavior is customized to update the table's Created_At field with the current timestamp. Similarly, the onChange event behavior is customized to update the table's Modified_At field with the current date. Notice the following in the code example (also available as a sample):
These class extension's methods are invoked following the IRow.store() or IRow.delete() methods and before the notification of related or external objects. Do not call IRow.store() and IRow.delete() from the class extension's implementation of the custom behavior.
[Java]
package classextension;
import java.io.IOException;
import java.util.Date;
import com.esri.arcgis.geodatabase.*;
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.IPropertySet;
import com.esri.arcgis.system.PropertySet;
@ArcGISExtension(categories = {
    ArcGISCategories.GeoObjectClassExtensions
}

)
//Notice the class implements _IObjectClassEvents interface
public class TimestampExtension implements IClassExtension, _IObjectClassEvents,
    IObjectClassExtension{

    /**
     * The property set keys used to get and set the extension properties.
     */
    public final static String CREATED_FIELD_PROPERTY = "CREATION_FIELDNAME";
    public final static String MODIFIED_FIELD_PROPERTY = "MODIFICATION_FIELDNAME";


    /**
     * The default field names to use as timestamp fields, if none are defined when the
     * class extension is applied.
     */
    private final static String DEFAULT_CREATED_FIELD = "CREATED_AT";
    private final static String DEFAULT_MODIFIED_FIELD = "MODIFIED_AT";


    /**
     * The indexes of the timestamp fields in the extension's class. A value of –1 indicates
     * the field is not used or could not be found during initialization.
     */
    private int createdFieldIndex =  - 1;
    private int modifiedFieldIndex =  - 1;


    /************************************************************************************************
     * IClassExtension members
     ************************************************************************************************/
    /**
     * Initializes the extension, passing in a reference to its class helper and its extension properties.
     */
    public void init(IClassHelper classHelper, IPropertySet extensionProperties)
        throws IOException, AutomationException{
        // Get a reference to the extension's object class.
        IClass baseClass = classHelper.esri_getClass();

        // Make sure a valid property set was provided.
        if (extensionProperties != null && extensionProperties.getCount() != 0){
            // Get the property set field names.
            Object createdFieldProperty = extensionProperties.getProperty
                (CREATED_FIELD_PROPERTY);
            Object modifiedFieldProperty = extensionProperties.getProperty
                (MODIFIED_FIELD_PROPERTY);


            // Get the created field index.
            if (createdFieldProperty != null){
                createdFieldIndex = baseClass.findField
                    (createdFieldProperty.toString());
            }

            // Get the modified field index.
            if (modifiedFieldProperty != null){
                modifiedFieldIndex = baseClass.findField
                    (modifiedFieldProperty.toString());
            }

        }
        else{
            // If the extension properties are null or empty, assume the class has
            // been created without extension properties. Apply the default property values.
            ISchemaLock schemaLock = null;
            try{
                // Attempt to acquire an exclusive schema lock. If this fails, an
                // AutomationException will be raised.
                schemaLock = new ISchemaLockProxy(classHelper.esri_getClass());
                schemaLock.changeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

                // Create a default set of extension properties.
                IPropertySet propertySet = new PropertySet();
                propertySet.setProperty(CREATED_FIELD_PROPERTY,
                    DEFAULT_CREATED_FIELD);
                propertySet.setProperty(MODIFIED_FIELD_PROPERTY,
                    DEFAULT_MODIFIED_FIELD);


                // Use the IClassSchemaEdit2 interface to persist a new set of
                // extension properties.
                IClassSchemaEdit2 classSchemaEdit2 = new IClassSchemaEdit2Proxy
                    (classHelper.esri_getClass());
                classSchemaEdit2.alterClassExtensionProperties(propertySet);
            }
            catch (AutomationException autoExc){
                // An exclusive schema lock could not be acquired. Allow the extension to
                // finish initializing, but no custom behavior will occur.
            }
            finally{
                try{
                    // Ensure the schema lock is shared.
                    if (schemaLock != null)
                        schemaLock.changeSchemaLock
                            (esriSchemaLock.esriSharedSchemaLock);
                }
                catch (Exception exc){
                    // Ignore errors.
                }
            }
        }
    }

    /**
     * Called when the extension's class is being disposed of from memory.
     */
    public void shutdown()throws IOException, AutomationException{
        // Do nothing.
    }

    /************************************************************************************************
     * _IObjectClassEvents members
     ************************************************************************************************/
    /**
     * This event is fired when an existing object is modified and Store is called.
     */
    public void onChange(IObject obj)throws IOException, AutomationException{
        // Make sure the modified field is defined.
        if (modifiedFieldIndex !=  - 1){
            obj.setValue(modifiedFieldIndex, new Date());
        }
    }

    /**
     * This event is fired when a new object is created and Store is called.
     */
    public void onCreate(IObject obj)throws IOException, AutomationException{
        // Make sure the created field is defined.
        if (createdFieldIndex !=  - 1){
            obj.setValue(createdFieldIndex, new Date());
        }
    }

    /**
     * This event is fired when an object is deleted.
     */
    public void onDelete(IObject obj)throws IOException, AutomationException{
        // Do nothing.
    }

}
To deploy the class extension after developing the Java class, 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. When started, an ArcGIS application (ArcGIS Engine, ArcMap, ArcCatalog, and ArcGIS Server) recognizes the class extension by its annotation. If the application is running, restart it after the class extension is deployed.

Apply the class extension

The created class extension can be applied to any object class. For more information about the various ways a class extension can be applied, see Apply class extensions. The following code example shows how to apply an 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 fc){
    try{
        //Verify that no other class extension is applied to the feature class.
        if (fc.getEXTCLSID() == null){
            fc.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.JTimeStamper.class.getName());

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

canByPassStoreMethod and canByPassEditSession

When creating class extensions to customize object event behavior you can implement optional interfaces IObjectClassInfo and IObjectClassInfo2 that provide some additional methods to customize object classes data behavior.
The canBypassStoreMethod and canBypassEditSession are accessed through the object or feature class's instance. However, if IObjectClassInfo and IObjectClassInfo2 are implemented by the class extension applied to the object or feature class, the class defers its response to that of the extension.
canByPassStore—By default, in an edit session when new records are created by an insert cursor, the IRow.store() method is not called for object classes (tables) and simple feature classes. This results in increased performance, with the downside that any custom behavior triggered by the Store method (i.e. IObjectClassEvents.onCreate) will not take place. Classes with custom behavior often require the IRow.store() method to be called, as it triggers the IObjectClassEvents.onCreate() method. Implementing the IObjectClassInfo interface and returning false from the canBypassStoreMethod() method will ensure that the Store method is always called, guaranteeing the custom behavior will take place.
Insert cursors are used in ArcGIS Desktop applications, such as in the ArcCatalog simple data loader and in geoprocessing tools. If a class extension implements the IObjectClassEvents interface and has important functionality in the onCreate method, or the class has other listeners of IObjectClassEvents, implement the canByPassStore() method and return false. If it returns true, Store will not be called by the cursor. See the following code snippet
[Java]
public Boolean canBypassStoreMethod(){
    return false;
}
Several geoprocessing tools (i.e. Calculate Field, Append, and Add Field) use insert cursors. For an object or feature class with custom behavior defined by the class extension to behave appropriately with these tools, canBypassStoreMethod should return false.
canByPassEditSession—The canBypassEditSession method indicates whether a class requires objects to be created, deleted, or changed outside of an edit session. Some classes, such as those participating in a network, require an edit session to ensure proper multiuser behavior and the correct management of objects internally cached database states.
If a class extension requires edit events to be handled (such as those from the IWorkspaceEditEvents interface) to validate or modify classes or private datasets, implement this method and return false which prevents applications from editing the class outside an edit session.
[Java]
public boolean canBypassEditSession(){
    return false;
}
Class policies (whether it requires the Store method or an edit session) can only be tightened by these methods. For example, a feature class that participates in a topology must be edited in an edit session. Implementing a class extension that returns true from the canByPassEditSession method will not change this.


See Also:

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




Development licensingDeployment licensing
Engine Developer KitEngine
ServerServer