Using Caché Objects
Defining Callback Methods
[Back] [Next]
   
Server:docs2
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

Callback methods are called by system methods to allow additional user-supplied processing. Callback methods are identifiable by having names that begin with “%On” or “On”, typically followed by the name of the method that invokes them. In most cases, this is the entire name, such as %OnNew().

To implement a callback method, use Studio to override the inherited method or define a new method with the correct signature; these techniques are equivalent. To override the method, use the Override dialog in Caché Studio; to access this dialog, click Class —> Override.
If a system method has an implemented callback method, then when the system method runs, that method invokes the callback method. For example, %Delete() invokes %OnDelete(), if %OnDelete() is implemented.
Important:
Do not execute callback methods explicitly.
Callback Methods
Callback Name Implemented for Method Type Private Method?
%OnAddToSaveSet() %RegisteredObject Instance Yes
%OnAfterDelete() %Persistent Class Yes
%OnAfterSave() %Persistent Instance Yes
%OnBeforeSave() %Persistent Instance Yes
%OnClose() %RegisteredObject Instance Yes
%OnConstructClone() %RegisteredObject Instance Yes
%OnDelete() %Persistent Class Yes
%OnNew() %RegisteredObject Instance Yes
%OnOpen() %Persistent, %SerialObject Instance Yes
%OnReload %Persistent Instance Yes
%OnRollBack %Persistent Instance Yes
%OnValidateObject() %RegisteredObject Instance Yes
%OnDetermineClass() %CacheStorage Class No
Note:
For all callbacks that are private methods, documentation for them is only visible in the Class Reference if the Private check box in the upper-right corner of the Class Reference is selected.
Callbacks and Triggers
For an application that uses both SQL and object access, if you implement a trigger, it is generally desirable to call the same logic at an equivalent point in object access. For example, if you insert an audit record when a row is deleted, you should probably also insert an audit record if an object is deleted.
If a trigger is defined with Foreach = row/object, then the trigger is also called at specific points during object access. See Triggers and Transactions in “Using Triggers” in Using Caché SQL.
If, however, you cannot create such triggers, and if you want the SQL and object behavior to be synchronized in the sense described previously, then it is necessary to implement one or more callbacks. In these implementations, use logic equivalent to that used in the trigger definitions. Note that the following callback methods have functionality equivalent to that of SQL triggers:
For more information on triggers, see the Using Triggers chapter in Using Caché SQL or the CREATE TRIGGER page in the Caché SQL Reference.
%OnAddToSaveSet()
This instance method is called whenever the current object is being added to a SaveSet by %AddToSaveSet(). %AddToSaveSet() can be called by:
If %OnAddToSaveSet() modifies another object, then it is the responsibility of %OnAddToSaveSet() to invoke %AddToSaveSet() on that modified object. When calling %AddToSaveSet() from %OnAddToSaveSet(), pass the depth as the first argument and 1 (literal one) as the second argument.
When you invoke %Save() on an object, called, for example, MyPerson, Caché generates a list of objects that MyPerson references. A SaveSet is the list of objects consisting of the object to be saved and all the objects that it references. In the example, the SaveSet might include referenced objects MySpouse, MyDoctor, and so on. For a fuller discussion, see Saving Objects in the chapter Working with Persistent Objects.”
The signature of %OnAddToSaveSet() is:
Method %OnAddToSaveSet(depth As %Integer,
                       insert As %Integer,
                       callcount As %Integer)
                        As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
where:
depth An integer value passed in from %AddToSaveSet() that represents the internal state of SaveSet construction. If you use %OnAddToSaveSet() to add any other objects to the SaveSet, pass this value to %AddToSaveSet() without change.
insert A flag indicating if the object being saved is being inserted into the extent (1) or that it is already part of the extent (0).
callcount The number of times that %OnAddToSaveSet has been called for this object. Due to the networked nature of object references, it is possible that %AddToSaveSet can be invoked on the same object multiple times.
The method returns a %Status code, where a failure status causes the save to fail and the transaction to be rolled back.
You can update objects, create new objects, delete objects and ask objects to include themselves in the current SaveSet by calling %AddToSaveSet(). If you modify the current instance or any of its descendants, you must let the system know that you have done this; to do so, call %AddToSaveSet() for the modified instance(s) and specify the Refresh argument as 1.
None of the modification restrictions imposed on %OnAfterSave(), %OnBeforeSave(), or %OnValidateObject() are in place for %OnAddToSaveSet().
If you delete an object using %OnAddToSaveSet(), be sure to call %RemoveFromSaveSet() to clean up any dangling references to it.
This method can be overridden in any subclass of %Library.RegisteredObject.
%OnAfterDelete()
This class method is called by the %Delete() method just after a specified object is deleted (immediately after a successful call to %DeleteData()). This method allows you to perform actions outside the scope of the object being saved, such as queuing a later notification action.
Its signature is:
ClassMethod %OnAfterDelete(oid As %ObjectIdentity) As %Status [ Private, ServerOnly = 1 ]  
{
    // body of method here...
}
where:
oid The object being deleted.
The method returns a %Status code, where a failure status causes %Delete() to fail and, if there is an active transaction, to roll it back. If %Delete() returns an error (either its own error or one originating in %DeleteData()), then there is no call to %OnAfterDelete().
Subclasses of %Library.Persistent have the option of overriding this method.
%OnAfterSave()
This instance method is called by the %Save() method just after an object is saved. This method allows you to perform actions outside the scope of the object being saved, such as queueing a later notification action. An example is a bank using the deposit in excess of a certain amount to cause it to send the customer an explanation of its deposit policies.
Its signature is:
Method %OnAfterSave(insert as %Boolean)As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
where:
insert A flag indicating if the object being saved is being inserted into the extent (1) or that it is an update of an existing object (0).
The method returns a %Status code, where a failure status causes %Save() to fail and ultimately roll back the transaction.
Subclasses of %Library.Persistent have the option of overriding this method.
%OnBeforeSave()
This instance method is called by the %Save() method just before an object is saved. This method allows you to request user confirmation before completing an action before saving the instance to disk.
Important:
It is not valid to modify the current object in %OnBeforeSave(). If you wish to modify the object before saving it, implement the %OnAddToSaveSet() callback instead and include your logic in that method.
Its signature is:
Method %OnBeforeSave(insert as %Boolean) As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
where:
insert A flag indicating if the object being saved is being inserted into the extent (1) or that it is already part of the extent (0).
The method returns a %Status code, where a failure status causes the save to fail.
Subclasses of %Library.Persistent have the option of overriding this method.
%OnClose()
This instance method is called immediately before an object is destructed, thereby providing the user with an opportunity to perform operations on any ancillary items, such as releasing locks or removing temporary data structures.
Its signature is:
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
{
    // body of method here...
}
The method returns a %Status code, where a failure status is only informational and does nothing to prevent the object from being destructed.
Subclasses of %Library.RegisteredObject have the option of overriding this method.
%OnConstructClone()
This instance method is called by the %ConstructClone() method immediately after the structures have been allocated for the cloned object and all the data has been copied into it. The method allows you to perform any additional actions related to the cloned object, such as taking out a lock or resetting any of the property values of the clone.
Its signature is:
Method %OnConstructClone(object As %RegisteredObject,
                        deep As %Boolean,
                        ByRef cloned As %String)
                        As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
where:
object The OREF of the object that was cloned.
deep How “deep” the cloning process is, where 0 specifies that the clone points to the same related objects as the original; 1 causes objects related to the object being cloned to also be cloned, so that the clone gets its own set of related objects.
cloned An argument whose use varies according to how %OnConstructClone() is being invoked. See class documentation on %Library.RegisteredObject for details.
The method returns a %Status code, where a failure status prevents the clone from being created.
Subclasses of %Library.RegisteredObject have the option of overriding this method.
%OnDelete()
This class method is called by the %Delete() method just before an object is deleted. This method can be used to ensure that deleting an object does not corrupt data integrity, such as by ensuring that an object designed to contain other objects is only deleted when it is empty.
Its signature is:
ClassMethod %OnDelete(oid As %ObjectIdentity)As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
where:
oid An object identifier for the object being deleted.
The method returns a %Status code, where a failure status stops the deletion.
Subclasses of %Library.Persistent have the option of overriding this method.
%OnNew()
This instance method is called by the %New() method at the point when the memory for an object has been allocated and properties are initialized.
Its signature is:
Method %OnNew(initvalue As %String)As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
where:
initvalue A string that the method uses in setting up the object, unless being overridden, as described in the next note.
Important:
The arguments for %OnNew() must match those of %New(). When customizing this method, override the arguments with whatever variables and types that you expect to receive from %New(). For example, if %New() accepts two arguments — dob for a date of birth and name for a first name and surname, the signature of %OnNew() might be:
Method %OnNew(dob as %Date = "", name as %Name = "") as %Status [ Private, ServerOnly = 1 ] 
{
 // body of method here...
}
The method returns a %Status code, where a failure status stops the creation of the object.
For example, with a class whose instances must have a value for their Name property, the callback might be of the form:
Method %OnNew(initvalue As %String) As %Status
{
    If initvalue="" Quit $$$ERROR($$$GeneralError,"Must supply a name")
    Set ..Name=initvalue
    Quit $$$OK
}
Subclasses of %Library.RegisteredObject have the option of overriding this method.
%OnOpen()
This instance method is called by the %Open() method just before an object is opened. It allows you to verify the state of an instance compared to any relevant entities.
Its signature is:
Method %OnOpen() As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
The method returns a %Status code, where a failure status stops the opening of the object.
Subclasses of %Library.Persistent and %SerialObject have the option of overriding this method.
%OnReload
This instance method is called by the %Reload method to provide notification that the object specified by oid was reloaded. Note that %Open calls %Reload when the object identified by oid is already in memory. If this method returns an error, the object is not opened.
Its signature is:
Method %OnReload() 
    As %Status [ Private, ServerOnly = 1 ]
  {
    // body of method here...
  }
The method returns a %Status code, where a failure status stops the rollback operation.
Subclasses of %Library.Persistent have the option of overriding this method.
%OnRollBack()
Caché calls this instance method when it rolls back an object that it had previously successfully serialized as part of a SaveSet. (See Saving Objects in the chapter Working with Persistent Objects.”)
When you invoke %Save() for a persistent object or a stream or when you invoke %GetSwizzleObject() for a serial object, Caché starts a save transaction which includes all the objects in the SaveSet. If the %Save() fails (because properties do not pass validation, for example), Caché rolls back all objects that it had previously successfully serialized as part of a SaveSet. That is, for each of these objects, Caché invokes %RollBack(), which calls %OnRollBack().
Caché does not invoke this method for an object that has not been successfully serialized, that is, an object that is not valid.
%RollBack() restores the on-disk state of data for that object to its pre-transaction state, but does not affect the in-memory state of any properties of that object that you have set, apart from its ID assignment. (For more details, see Saving Objects in the chapter Working with Persistent Objects.”) If you want to revert in-memory changes, do so in %OnRollBack().
The signature of this method is:
Method %OnRollBack() As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
The method returns a %Status code, where a failure status stops the rollback operation.
Subclasses of %Library.Persistent have the option of overriding this method.
%OnValidateObject()
This instance method is called by the %ValidateObject() method just after all validation has occurred. This allows you to perform custom validation, such as where valid values for one property vary according to the value of another property.
Its signature is:
Method %OnValidateObject() As %Status [ Private, ServerOnly = 1 ] 
{
    // body of method here...
}
The method returns a %Status code, where a failure status causes the validation to fail.
Subclasses of %Library.RegisteredObject have the option of overriding this method.
%OnDetermineClass()
The %OnDetermineClass() class method returns the most specific type class of that object. (For an introduction to the most specific type class, see %ClassName() and the Most Specific Type Class (MSTC) in the chapter Working with Registered Objects.”) %OnDetermineClass() is implemented by the default storage class. If you use custom storage or SQL storage, there is no default implementation for this method, but you can implement it.
Its signature is:
ClassMethod %OnDetermineClass(
        oid As %ObjectIdentity, 
        ByRef class As %String) 
    As %Status [ ServerOnly = 1 ]
where:
The return value is a status value indicating success or failure.
Subclasses of %Library.SwizzleObject have the option of overriding this method.
Invoking %OnDetermineClass()
%OnDetermineClass() can be invoked in either of two ways:
Set status = ##class(APackage.AClass).%OnDetermineClass(myoid, .myclass)
Set status = myinstance.%OnDetermineClass(myoid, .myclass)
where myoid is the object whose most specific type class is being determined and myclass is the class identified. APackage.AClass is the class from which the method is being invoked and myinstance is the instance from which the method is being invoked.
In this case, the method is computing the most specific type class for myoid and setting myclass equal to that value. If myoid is not an instance of the current class, an error is returned.
Consider the example of using %OnDetermineClass() with Sample.Employee, which is a subclass of Sample.Person. If there is a call of the form
Set status = ##class(Sample.Employee).%OnDetermineClass(myoid, .class)
and myoid refers to an object whose most specific type class is Sample.Person, then the call returns an error.
An Example of Results of Calls to %OnDetermineClass()
Suppose there is a MyPackage.GradStudent class that extends a MyPackage.Student class that extends a MyPackage.Person class. The following shows the results of invoking %OnDetermineClass(), passing in the OID of an object whose most specific type class is MyPackage.Student: