Tags:
create new tag
, view all tags
-- 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.

boSession and <span class=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).

Studio Create Method

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)

StudioJavaHandlersSource.png

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.

Remove All Categories Method

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.

Remove All Categories Method Optimized

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.

Formula Behavior

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

____

Topic attachments
I Attachment Action Size Date Who Comment
PNGpng FormulaBehavior.png manage 16.4 K 2010-12-27 - 17:22 PedroRio  
PNGpng JavaAPIboSession.png manage 31.7 K 2010-12-23 - 11:38 PedroRio boSession e EboContext
PNGpng RemoveAllCategoriesMethod.png manage 9.8 K 2010-12-27 - 09:18 PedroRio Remove Book Categories method
PNGpng RemoveAllCategoriesMethodOptimized.png manage 6.0 K 2010-12-27 - 09:40 PedroRio RemoveCategoriesOptimized
PNGpng StudioCreateMethod.png manage 35.2 K 2010-12-23 - 16:28 PedroRio  
PNGpng StudioJavaHandlersSource.png manage 3.5 K 2010-12-23 - 16:37 PedroRio  
Edit | Attach | Print version | History: r24 | r13 < r12 < r11 < r10 | Backlinks | Raw View | Raw edit | More topic actions...
Topic revision: r11 - 2010-12-27 - PedroRio
 

No permission to view TWiki.WebTopBar

This site is powered by the TWiki collaboration platform Powered by Perl

No permission to view TWiki.WebBottomBar