This lesson introduces class Events where base behaviors can be tailored and custom behaviors defined. By completing this module, you will learn:
-
What a class event is and how it is used.
-
The base events that are available to all classes.
-
How to customize the business logic of a base event in a class.
Events are the behaviors of Classes and Aspects. A class inherits events from its base class; it can enhance or override base behaviors. Events can also take a list of arguments.
In this module, you create a new class attribute called primaryTelcom that identifies the preferred telecommunication method for reaching a given person. Then you use events to control the values that it takes on as training:Telcom instances are added and removed from the model. You also modify the isPrimary attribute that you created in Associated classes to be a calculated attribute dependent on the value of the primaryTelcom field.
Modify the business model
In this lesson you add the primaryTelcom attribute to the training:Entity class and define persistence for the new attribute.
Add the primaryTelcom attribute to the training:Entity class
To add the primaryTelcom attribute to the training:Entity class:
-
Open the
training:Entityclass and create a new attribute calledprimaryTelcomwith the following properties:
primaryTelcom attribute propertiesAttribute
Property
Value
primaryTelcom
Type
training:Telcom
Reverse
entity
-
In the Persistence Mapping tab, add an attribute mapping for primaryTelcom.
-
Set the Source Key to
TrnEntity.FK_PrimaryTelcomand set the Destination Key toTrainingTelcom.PK.
Modify the isPrimary attribute of the training:Telcom class
To modify the isPrimary attribute of the training:Telcom class:
-
Open the
training:Telcomclass for editing. Go to the Attributes tab and select theisPrimaryattribute. -
In the Value subtab, remove the Initializer value.
-
Set the Value property to (
= (@) (@ entity primaryTelcom)).
In this expression (@) resolves to the identifier for the training:TelcomInstance;(@ entity primaryTelcom) resolves to the ID of thetraining:Telcominstance identified by the primaryTelcomattribute of the relatedEntity. If they match, then theisPrimaryattribute is set to#t. Otherwise, it takes on a value of#f. -
Set the Dependency value to (
entity primaryTelcom).
This indicates that theisPrimaryattribute on atraining:Telcominstance should be recalculated whenever the primaryTelcomattribute of its relatedtraining:Entityinstance changes. -
In the Common subtab, set the Read-Only attribute to
true(selected). -
In the Persistence Mapping tab, remove the attribute mapping for the
isPrimaryattribute.
TheisPrimaryattribute does not need to be persisted anymore because it now behaves as a calculated attribute.
Modify the persistence model
The Update Data Source button on the Persistence Mapping tab of the classes you edited will not automatically make the changes to the persistence model for the classes you modified. You must manually modify the persistence model to support the changes you have made.
Modify persistence for the training:Entity class
To modify persistence for the training:Entity class:
-
In the Persistence layer, open the training:DB data source.
-
Select the Entity table and click the Columns tab.
-
Add a column with the following properties:
Column to add to the Entity tableColumn Name
Type
Allocation
Allow Nulls
Precision
Case Insensitive
primaryTelcom
binary
fixed
true
16
false
-
In the Indexes subtab, add an index with the following properties:
Index to add to the Entity tableName
Type
Unique
Index Columns
TrnEntity.FK_PrimaryTelcom
virtual
false
primaryTelcom
Modify persistence for the Telcom class
To modify persistence for the training:Telcom class:
-
Select the
training:Telcomtable and go to the Columns tab. -
Remove the
isPrimarycolumn.
Edit the upgrade file
Add an upgrade to your upgrade file to reflect the changes in the business and persistence models. To do this, you will add steps to drop the isPrimary column from the training:Telcom table, and create the primaryTelcom column in the training:Entity table. You will additionally create a virtual index on the new primaryTelcom column.
To update the upgrade file:
-
In the Persistence layer on the Data Sources tab right-click the
DefaultRelationalDatabaseand select Generate Upgrade Steps. -
Leave the default values for Upgrade and Current Data Source.
-
Select Old Model as the Old Data Source. You are going to use a published model as the old model.
-
Select Published Model JAR and then click the browse button. Select the model you published at the end of the last module and click Next.
-
Whenever you upgrade a database, you must increment the version number by a whole number. For example, if the version retrieved from the published JAR file is 5.39; change the Version to
6.39. -
Enter the following as the Description:
Dropped isPrimary column and added primaryTelcom attribute support. -
Click Finish to create the upgrade steps.
-
A warning appears because the upgrade steps include a DropColumn. Click Proceed to continue.
-
Your upgrade is added to the bottom of the list of upgrades.
-
You must ensure that your current model's version matches the upgrade version. Click the Set current model button
in the toolbar to open the Model Library.
-
With the current model selected, click Edit. Update the Model Version field to match the version associated with the upgrade you just created. So, if your upgrade is associated with version
5.39, then change the Model version to6.39. This enables NexJ Studio to recognize that the model has been changed from the previous version so that your update can be applied to the supporting data stores. -
Click OK. Note that the new version is now reflected in the table in the Models tab.
-
Click Close.
-
Click the Save button
in the toolbar to save your work.
Publish and upgrade
Complete the model updates by carrying out the following activities.
-
Publish the model
-
Upgrade the database.
Create the events
You have now updated your model to store the primary telecommunication channel for a given entity.
If you leave your model as it is now, every time you add a training:Entity with multiple training:Telcom instances you have to explicitly identify which associated training:Telcom instance is the primary one. If you delete the primary training:Telcom instance, you have to explicitly assign a new one as primary. In this lesson, you create class events to automatically manage the primary training:Telcom instance.
Events consist of one or more actions. Every event has at least an action called main. Actions are made up of scripts or Java methods that are executed in a particular order whenever the event is invoked. Actions are executed relative to main (and to each other), can be overridden in subclasses, and can be conditionally executed.
All classes inherit a set of events from the base class Object, which has no attributes.
In this lesson, you modify two inherited events and create a new one:
-
You modify the inherited create event so that new
training:Telcominstances automatically set their relatedtraining:Entity'sprimaryTelcomattribute if it is not already set. -
You modify the inherited
deleteevent. -
You create a new event called setPrimaryTelcom that automatically identifies a new primary telcom when a
training:Entity'sprimary telcom is deleted.
Add the events to the training:Telcom class
To add the three events to the training:Telcom class:
-
In the Business Model layer, open the
training:Telcomclass for editing. -
In the Events tab, click the Override Base Events button
and add the following events:
-
create()
-
delete ()
-
-
Click the Add
button to add a new event.
-
Set the Name of the new event to
getNextPrimary.
The Visibility property for the create and delete events is set to public, while the Visibility for getNextPrimary is protected. Visibility determines how events can be accessed by client applications using a remote procedure call (RPC). Public events can be invoked by clients through RPC, protected ones are only accessible within the NexJ Server.
Add actions to the create event
To add actions to the create event.
-
Select the
createevent and click the Actions subtab. -
Click the Add button
and select Before.
This adds a new action that runs before themainsection of thecreateevent. -
Set the Name of the action to
setPrimary. -
Click the Script subtab, and set the Condition property to:
(null? (@ entity primaryTelcom))
JavaScriptthis.entity.primaryTelcom == null;This condition ensures that the script associated with the action only executes if the current
training:Telcominstance's relatedEntitydoes not have a primaryTelcom (i.e. its primaryTelcom is null). -
Still in the Script subtab, enter the following Scheme code in the main entry field:
; If there is no primary for this telcom's entity, ; set it to the one you are creating. (logger'debug "setting empty primaryTelcom field on entity to " this) ((@ entity)'primaryTelcom this)
JavaScriptlogger.debug("setting empty primaryTelcom field on entity to ", this); this.entity.primaryTelcom = this;
Note the use of logger to echo information about what the script is doing to the system log.
-
Save your changes.
Add actions to the delete event
Now you add actions to the delete event to check whether a deleted training:Telcom instance is identified by its related training:Entity as being the primary training:Telcom. If it is the primary training:Telcom instance, then the delete event should call the getNextPrimary event.
To add an action to the delete event:
-
In the Events list, select the
deleteevent. -
Click the Actions subtab.
-
Click the Add button
and select Before. The event runs before the main action of deleting a
training:Telcominstance takes place. -
Set the Name of the action to
clearPrimary. -
Click the Script subtab, and set the Condition property to:
(= (@ entity primaryTelcom) this)
JavaScriptthis.entity.primaryTelcom == this;This condition ensures that the script associated with the action only executes if the current telcom's entity's
primaryTelcomis the telcom that is being deleted. -
Still on the Script subtab, enter the following Scheme code in the main entry field:
Scheme; clear the primary entity's primaryTelcom ((@ entity)'primaryTelcom '())
JavaScriptthis.entity.primaryTelcom = null;
Add an action to the delete event that sets a new value for the primaryTelcom attribute of an Entity:
-
Add a second action for the
deleteevent afterclearPrimaryin the Actions list. -
Set the Type to before.
-
Set the Name to
resetPrimary. -
In the Script subtab, set the Condition to:
(null? (@ entity primaryTelcom))
JavaScriptthis.entity.primaryTelcom == nullThe condition confirms that the primaryTelcom attribute is null.
-
Enter the following script:
; reset the entity's primaryTelcom to another telcom if one exists. ((@ entity)'primaryTelcom (this'getNextPrimary))
JavaScriptthis.entity.primaryTelcom = this.getNextPrimary();
-
Click the Save button
in the toolbar to save your changes.
Add a main action to the getNextPrimary event
Finally, add a main action to the new getNextPrimary event:
-
In the Events list, select the
getNextPrimaryevent. -
Click the Actions subtab.
-
Click the Add button
and select Main.
This defines the main block of code to run when the event is called. -
Leave the Name property as
main. -
In the Script subtab, enter the following Script:
; Try phones first, then emails if no phone is available (let ((primary (read-instance training:TelephoneNumber '() `(and (not (= (@) ,(@))) (= entity ,(@ entity))) '()))) (when (null? primary) (set! primary (read-instance training:EmailAddress '() `(and (not (= (@) ,(@))) (= entity ,(@ entity))) '())) ) primary ; return )
JavaScriptvar primary = #"read-instance"(#"training:TelephoneNumber, null, scm("`(and (not (= (@) ,(@))) (= entity ,(@ entity)))"), null); when (primary == null) { primary = #"read-instance"(#"training:EmailAddress", null, scm("`(and (not (= (@) ,(@))) (= entity ,(@ entity)))"), null); } return primary;
This script only works if the associated entity has at most one
training:TelephoneNumberand onetraining:EmailAddressassociated with it. If two or more associatedtraining:TelephoneNumberinstances ortraining:EmailAddressinstances exist, then theread-instancefunction returns an error. The script is a simplified example of code that you could use.
-
Save your work and validate your model by clicking the Validate model button
in the toolbar.
A note on the script for the getNextPrimary event
The script uses read-instance to read in an instance of a class that matches its Where clause.
In this case, the Where clause is:
`(and (not (= (@) ,(@))) (= entity ,(@ entity)))
(@)
A macro for the current instance.
(not (= (@) ,(@)))
the instance is not equal to the current instance.
(= entity ,(@ entity))
the training:Telcom's entity attribute equals the current instance's entity attribute.
Update the unit tests
Update the training:Telcom unit test to verify that the events work as intended.
To update the unit test:
-
Open the
training:Telcomunit test (Business Model → Unit Tests). -
In the Test Cases tab, click the Add button
to add a new test case.
-
Name the test case
PrimaryDefaulting. -
Enter the Description
Test primary defaulting. -
In the test case script area, enter the following Scheme script:
(define p (training:Person'new (: firstName "Bob") (: initials "A") (: lastName "Event") (: primaryLanguage (training:LanguageEnum'get'ENGLISH)))) (define tp (training:TelcomType'new (: name "Business"))) ; Part 1 - Add email and then add telephone number ; Result - Primary telcom is the email (define email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "bsled@appco.com") (: displayName "Bob Sled"))) (define tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "bsled@appco.com" ((p'primaryTelcom)'address)) ; Part 2 - Delete the primary telcom ; Result - Primary telcom is reset to phone number ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 3 - Delete primary telcom ; Result - Primary Telcom remains null ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (set! tp (read-instance training:TelcomType '() '(= name "Business") '())) (assert-null ((p'primaryTelcom)'address)) ; Part 4 - Add telephone number and then add email ; Result - Primary telcom is the telephone number ; test tel added first and removed first (set! tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (set! email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "bsled@appco.com") (: displayName "Bob Sled"))) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 5 - Delete the primary telcom ; Result - Primary telcom is reset to email ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "bsled@appco.com" ((p'primaryTelcom)'address)) ; Part 6 - Delete primary telcom ; Result - Primary Telcom remains null ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (set! tp (read-instance training:TelcomType '() '(= name "Business") '())) (assert-null ((p'primaryTelcom)'address)) ; Part 7 - (like part 4) Add telephone number and then add email ; Result - Primary telcom is the telephone number (set! tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (set! email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "bsled@appco.com") (: displayName "Bob Sled"))) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 8 - Remove email ; Result - Primary telcom remains as telephone number ((read-instance training:EmailAddress '() `(= (@ entity) ,p) '())'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 9 - Remove primary telcom ; Result - Primary telcom set to null ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-null ((p'primaryTelcom)'address)) ; Part 10 - Delete the person instance ; Result - Person is removed from database. (p'delete) (commit) (assert-null (read-instance training:Person '() '(= lastName "Event") '()))
JavaScriptvar p = new #"training:Person"({firstName : "Bob", initials : "A", lastName : "Event", primaryLanguage : #"training:LanguageEnum".get.ENGLISH}); var tp = new #"training:TelcomType"({name : "Business"}); ; Part 1 - Add email and then add telephone number ; Result - Primary telcom is the email var email = new #"training:EmailAddress"({entity : p, type : tp, name : tp.name, address : "bsled@appco.com", displayName : "Bob Sled"}); var tel = new #"training:TelephoneNumber"({type : tp, name : tp.name, address : "(416) 555-1212", entity : p, extension : "x123"}); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("bsled@appco.com", p.primaryTelcom.address); ; Part 2 - Delete the primary telcom ; Result - Primary telcom is reset to phone number p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("(416) 555-1212", p.primaryTelcom.address); ; Part 3 - Delete primary telcom ; Result - Primary Telcom remains null p.primaryTelcom.delete; commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); tp = #"read-instance"(#"training:TelcomType", null, scm("'(= name \"Business\")"), null); #"assert-null"(p.primaryTelcom.address); ; Part 4 - Add telephone number and then add email ; Result - Primary telcom is the telephone number ; test tel added first and removed first tel = new #"training:TelephoneNumber"({type : tp, name : tp.name, address : "(416) 555-1212", entity : p, extension : "x123"}); email = new #"training:EmailAddress"({entity : p, type : tp, name : tp.name, address : "bsled@appco.com", displayName : "Bob Sled"}); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")", null); #"assert-equal"("(416) 555-1212", p.primaryTelcom.address); ; Part 5 - Delete the primary telcom ; Result - Primary telcom is reset to email p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("bsled@appco.com", p.primaryTelcom.address); ; Part 6 - Delete primary telcom ; Result - Primary Telcom remains null p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); tp = #"read-instance"(#"training:TelcomType", null, scm("'(= name \"Business\")"), null); #"assert-null"(p.primaryTelcom.address); ; Part 7 - (like part 4) Add telephone number and then add email ; Result - Primary telcom is the telephone number tel = new #"training:TelephoneNumber"({type : tp, name : tp.name, address : "(416) 555-1212", entity : p, extension : "x123"}); email = new #"training:EmailAddress"({entity : p, type : tp, name : tp.name, address : "bsled@appco.com", displayName : "Bob Sled"}); commit(); #"reset-context"(); p #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("(416) 555-1212", p.primaryTelcom.address); ; Part 8 - Remove email ; Result - Primary telcom remains as telephone number #"read-instance"(#"training:EmailAddress", null, scm("`(= (@ entity) ,p)"), null).delete); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")", null); #"assert-equal("(416) 555-1212", p.primaryTelcom.address); ; Part 9 - Remove primary telcom ; Result - Primary telcom set to null p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-null"(p.primaryTelcom.address); ; Part 10 - Delete the person instance ; Result - Person is removed from database. p.delete(); commit(); #"assert-null"(#"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null);
-
Review the script.
-
Save your changes.
-
Run the unit test. All the test cases execute, including the ones you created in a previous module.
Publish the model
Adding the events does not change the persistence model, so you do not need to update the dump files or database. However, you should increment the model's revision number from 8.1.0.4 to 8.1.0.5 and republish it to ensure that you have a published version that contains all of your changes.
More About Events
In this module, you made use of the create() and delete() events that are inherited from the base class Object.
The full list of events provided by the base class Object is as follows:
:class()
static - Returns the class object of this instance.
:oid()
Returns the oid object of this instance.
commit()
Invoked before the instance is committed.
create()
Invoked after the instance has been created and initialized.
delete()
Invoked to delete the instance.
isNew()
Indicates if the instances state is New.
load(attributes)
Loads on demand an undefined attribute.
lock()
Invoked to lock the instance.
new(values)
static - Creates and initializes an instance of this class.
old(attribute)
Gets the original value of an attribute as it was read.
openCursor(attributes where orderBy count offset xlock)
static - Opens a cursor to read the specified instances of this class.
read(attributes where orderBy count offset xlock)
static - Reads the specified instances of this class.
update()
Invoked after the instance has been created and initialized.
updated(attribute)
Checks whether an attribute value has been updated but not committed.
A static event is not tied to any class instance. It cannot interact with instance variables or call instance events (i.e. non-static events).