--
PedroRio - 21 Dec 2010
XEO Object Model Behavior- XEO Library
Custom Behavior in XEO Object Models can be achieved through various way :
- Object Events
- Object Methods
- Attribute Events
- Attribute Properties
To define custom behavior there are two methods: BOL and the Java API. BOL is an acronym for Business Object Language which is essentially a small expression language specific to the XEO platform to aid in creating custom behavior. Everything that can be done using BOL can be done using Java (BOL expressions are ultimately converted to Java as the whole Object Model is also converted to Java).
In this chapter we'll create the custom behavior required for the Object Models in the XEO Library and introduce briefly the XEO Java API (and BOL).
XEO Java API (Basic Introduction)
Each XEO Object Model is ultimatelly converted to a Java class to support operations (database tables are also created to persist data) and abstract from the relational data model. The classes created from Object Models extend from a java class included in the framework, the
boObject class , which has a set of base methods to deal with attributes and other Model properties.
A user can login to a XEO Application via the login form or using the Java API to create a session within the XEO application. With access to a
boSession, the Java API can be used to create new instances of Object Models, load existing instances, change attribute values in an existing instance as well as save the changed values. One can also query object instances and iterate through the results as well as checking the Object Model definitions to perform operations based on that information.
In order to load an instance object, to create a new instance or to make a query XEO needs a "context". A context is an instance of the
EboContext class which can be created anytime (given there's access to a
boSession) and the operations to load an object, create an object and query objects all require a parameter which is the context. The
EboContext keeps in memory the loaded objects (and changes to their attribute values) and acts as cache and has a connection to the database (as such they should be managed with care). It's possible to create two different contexts and load the same object instance into both of them, in this scenario changes made each instance are not visible from another context and if both objects are changed and then saved , the last save will crush the first one. The hierarchy of these classes is depicted in figure OB.1.
EboContext hierarchy" src="http://wiki.itds.pt/pub/WebITDS/XeoPrimerObjectBehavior/JavaAPIboSession.png" title="boSession and
EboContext hierarchy" height="720" />
Figure OB.1 - boSession and EboContext hierarchy
In most situations, there's no need to explictly create instances of
EboContext, because an
EboContext is always created in each request (
FIXME: Vericar se isto é correcto) and that context can be used inside your custom code. Each
boObject instance has access to an
EboContext instance (the context in which its loaded) so even if a method receives as parameter a boObject, you still have access to the
EboContext.
Most Important APIs for defining custom behavior in the XEO Library (the list is a sub-set of the entire methods for the given classes only to illustrate the more commonly used methods in the XEO API), more detailed information on the
XEO Java API Page.
boObject - Represents an instance of an Object Model
Method Name |
Description |
Arguments |
Return type |
update |
Saves the object instance |
- |
void |
getAttribute |
Retrieves the handler for an attribute (non-collection) |
(String) attributeName |
AttributeHandler |
exists |
Checks whether the instance is saved in the database |
- |
boolean |
getBridge |
Retrieves the handler for a collection attribute |
(String) collectionName |
BridgeHandler |
AttributeHandler - Represents a handler to deal with an attribute (non-collection) of an Object Model
Method Name |
Description |
Arguments |
Return Type |
getName |
Returns the name of the attribute (as defined in the Object Model) |
- |
String |
getValueString |
Retrieves the value of the attribute as a String* |
- |
String |
getValueLong |
Retrieves the value of the attribute as a Long* |
- |
Long |
getValueDate |
Retrieves the value of the attribute as a Data* |
- |
java.util.Date |
getValueiFile |
Retrieves the value of the attribute as a binary data Wrapper (iFile)* |
- |
netgest.io.iFile |
getValueBoolean |
Retrieves the value of the attribute as a Boolean* |
- |
Boolean |
setValueString |
Sets the value of the attribute with a String* |
(String) newVal |
void |
setValueLong |
Sets the value of the attribute with a Long* |
(Long) newVal |
void |
setValueDate |
Sets the value of the attribute with a Date* |
(Date) newVal |
void |
setValueiFile |
Sets the value of the attribute with a IFile* |
(iFile) newVal |
void |
setValueBoolean |
Sets the value of the attribute with a Boolean* |
(Boolean) newVal |
void |
* - If an attribute is of a given type (String, Number) and you try to retrieve/set the value of the attribute of a different type it will throw an exception.
bridgeHandler - Represents a handler to deal with a collection attribute of an Object Model (similar to an java.util.Iterator)
Method Name |
Description |
Arguments |
Return Type |
getName |
Returns the name of the collection attribute |
- |
String |
beforeFirst |
Resets the handler to the first position of the collection |
- |
Boolean |
next |
Advances the handler to next position of the collection |
- |
Boolean |
getObject |
Retrieves the object in the current position of the handler |
- |
boObject |
add |
Add a new object to the end of the collection |
(Long) BOUI |
boolean |
remove |
Removes the boObject in the current position of the handler |
- |
boolean |
boManagerBean - Manages the load/create operations for boObjects
Method Name |
Description |
Arguments |
Return Type |
loadObject |
Loads a given instance object to a context |
(EboContext) ctx, (Long) BOUI |
boObject |
createObject |
Creates a new instance object of a given Object Model in a context |
(EboContext) ctx, (String) ObjectModelName |
boObject |
This set of classes will be almost enough (Object querying was purposely left out of the list) to define the behavior for the Object Models in the XEO Library application. Next, we'll create the first Object Model Method, the removeCategories for the LIB_Book.
Creating the first XEO Method - removeCategories (LIB_Book)
The LIB_Book Object Model has a collection of categories (representing Book categories). As a convenience, if a Librarian wrongly categorizes the a book we'll have a method that will clean all categories for that book (instead of individually removing each category). Open the LIB_Book Object Model and go to the "Methods" section and press the add (plus) button to add a new method. Type in "removeAllCategories" for the name of the method and "Remove all categories" for the Label, go to the "Body" section of the method, choose "JAVA" as the language and press the "edit" button (see figure OB.1 for details), which will open a "LIB_BookHandler" Class in Eclipse's Java Editor (see figure OB.1, again, for details).
Figure OB.1 - Creating a new Object Model Method in XEO Studio
Whenever Java code needs to be created to perform custom-logic, XEO Studio creates a Java class for each Object Model and names the methods appropriatelly with the correct parameters. In the case of Object Model Methods the method declaration is always the name of the method given in the Object Model definition with the boObject representing the current instance as parameter.
These auto-generated classes are placed in a different source-code folder (as depicted in figure OB.1a) within the xeo.code.java.XEOPackageName (in this case
XEOPackageName = LIB$1.0 = LIB1_0). There's one java package for each XEO Package in our project (that uses custom java code, of course)
Figure OB.1a - Different source code folder for XEO Studio's generated classes.
Remove All Book Categories - The Java Code
Each (and every) Object Model Method will be generated by XEO Studio using the method's name property and passing a boObject instance (representing the current instance at runtime) as a parameter. To code the remove all book categories from the instance book object, the flow of java code will be like the following:
- Check if the instance is saved (if it exists, it does not make sense to remove book categories from a book which does not yet exist in the library)
- Load the collection attribute and position the handler in the first position of the collection
- Iterate through all elements and remove them
- Save the instance
The code to remove all book categories is depicted in figure OB.1b.
Figure OM.1b - Remove all book categories method souce code.
The
removeAllCategories methods could be optimized, because the
bridgeHandler class has a convenience method,
truncate, which will remove all elements in the bridge. The result is depicted in figure OM.1c.
Figure OM.1c - removeAllCategoires method source code optimized
LIB_Librarian & LIB_User Behavior - Validate the e-mail attribute
The LIB_Librarian Object Model has an
email attribute, representing the Librarian's e-mail. E-mail addresses follow a specific pattern and can be validated before saving a Librarian instance. Open the LIB_Librarian Model, select the
email attribute, in the details panel scroll to the section with "Valid" as title and select JAVA from the dropdown menu and click "edit". XEO Studio will generate a handler class with the following method:
public boolean email_Valid(boObject obj,AttributeHandler attribute) throws boRuntimeException {
return true;
}
To know more about attribute behavior, check the
Attribute Behavior Notes at the end of the chapter.
The objective of this document is not to teach Java Programming, as such the code for the email_Valid method, could be the following:
public boolean email_Valid(boObject obj,AttributeHandler attribute) throws boRuntimeException {
//Keep the value to return
boolean res = false;
//Get the attribute value
String email = attribute.getValueString();
//Create a pattern to check for e-mails
Pattern p = Pattern.compile(".+@.+\\.[a-z]+");
Matcher m = p.matcher(email);
//If the pattern matches, e-mail is ok
if(m.matches() || email.length() == 0)
res = true;
else
obj.addErrorMessage("Invalid e-mail " + email); //Else, add error
//Return the result
return res;
}
For the LIB_User Model add the same valid behavior to the email attribute (since it's the same validation, you can create a class with the code and invoke it from both places.
LIB_Librarian & LIB_User Behavior - Username formula
When creating a new Libriarian or a new User a username must be chosen for both of them. Assume the library has a default "rule" for creating usernames which is the concatenation of their name, a separator (a dot) and the last name.
A formula is a behavior that allows to have an attribute whose value can be calculated based on the values of other attributes (its dependencies). The formula is triggered whenever one of the depedencies (value of the attribute from which this depends) is changed.
Open the LIB_Librarian Object Model and locate the
username attribute, find the
Formula section and press "Edit" to let XEO Studio generate the Java class for the handler and then add two attributes as "depends" (see red bordered zone in figure OB.2), the
name and
lastname attributes (name is inherited from
iXEOUser interface; this means the formula behavior will be invoked whenever the value of name/lastname are changed.
Figure OB.2 - Editing dependents in a Formula for the username attribute in LIB_Librarian
Go to the Java handler class created by XEO Studio and fill in the following code:
public String username_Formula(boObject obj,AttributeHandler attribute) throws boRuntimeException {
//Only use the rule for a new librarian
if(!obj.exists()) {
//Get the name value
String firstName = obj.getAttribute("name").getValueString().toLowerCase().toString();
//Get the last name value
String lastName = obj.getAttribute("lastname").getValueString().toLowerCase().toString();
//If both values are not null
if(firstName.length() == 0 || lastName.length() == 0)
return firstName + "." + "librarian";
else
return firstName + "." + lastName;
} //In an existing object, return the existing value
else
return obj.getAttribute("username").getValueString().toString();
}
The code for the formula will attempt to concatenate the name and lastname, if both have a value, or the name and the string "librarian" (all strings are separated by a dot), in case the object instance is not saved. If the instance object already exists, it returns the current value of the username attribute.
The (almost) same code is valid for the formula of the username in the LIB_User Object Model (instead of concatenating with ".librarian" in one of the situations it concatenates with ".user".
public String username_Formula(boObject obj,AttributeHandler attribute) throws boRuntimeException {
//Only use the rule for a new librarian
if(!obj.exists()) {
//Get the name value
String firstName = obj.getAttribute("name").getValueString().toLowerCase().toString();
//Get the last name value
String lastName = obj.getAttribute("lastname").getValueString().toLowerCase().toString();
//If both values are not null
if(firstName.length() == 0 || lastName.length() == 0)
return firstName + "." + "user";
else
return firstName + "." + lastName;
} //In an existing object, return the existing value
else
return obj.getAttribute("username").getValueString().toString();
}
Code for Formula of the username attribute (in the LIB_User Object Model)
a
LIB_Message Behavior - Date Default Value and DisabledWhen
The Message Object Model has a
date attribute which represents the date the message was sent. Although a system SYS_DTCREATE attribute exists in all instances this was added so that we could show some behaviors such as the default value and the disabled.
IA FAZER O DEFAULT VALUE DATE DA LIB_MESSAGE
a
Attribute Behavior Notes
All event behavior-handling funcitons generated by XEO Studio follow the same naming convention:
attributeName_behavior. In this situation the email attribute and the valid behavior, generated the
email_Valid method. Each behavior may return different types of values, summarized in the table OBT1, but all have the same parameters (the
boObject instance and the
attributeHandler to the own attribute):
Behavior |
Return Type |
required |
boolean |
onChangeSubmit |
boolean |
defaultValue |
String |
valid |
boolean |
formula |
String |
hiddenWhen |
boolean |
disableWhen |
boolean |
Table OBT1 - Return types for attribute behaviors
____