-- PedroRio - 21 Dec 2010

XEO Object Model Behavior- XEO Library

Custom Behavior in XEO Models can be achieved through various way (each with a specific purpose):

  • Model Events
  • Model 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 help declare behavior. Everything that can be done using BOL can be done using Java (BOL expressions are ultimately converted to Java like any XEO Model is converted to a java class).

In this chapter you'll create the custom behavior required for the XEO Models in the Library application and you'll learn the basics of XEO's Java API (and BOL).

XEO Java API (Basic Introduction)

Each XEO 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 XEO 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 a login form or using the Java API to create a session within the XEO application. Having access to a boSession instance, the Java API can be used to create new instances of XEO Models, load existing instances, change attribute values in existing instances as well as persist any changes made to those instances. One can also query XEO Model instances and iterate through the results. You can also check a XEO Model definition 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), acts as cache and has a connection to the database (meaning that creating many EboContext instances can consume all available connections to the database). It's possible to create two different contexts and load the same instance into both of them, in this scenario changes made to each instance are not visible from the other 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/WebXEO/XeoPrimerObjectBehavior/JavaAPIboSession.png" title="boSession and EboContext hierarchy" height="720" />

Figure OB.1 - boSession and EboContext hierarchy (with two instances with BOUI = 111, to depict the possibility to have different values)

In most situations, there's no need to explicitly 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 it's loaded) so even if a method receives as parameter a boObject, you still have access to the EboContext.

The following tables represent the most important classes in the XEO API and list some of the methods that are used to create behavior for XEO Models. More detailed information can be found on the XEO Java API Page.

boObject - Represents an instance of an XEO 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 (unless the value can be converted to that type - i.e. you can get the String value of a Numeric attribute, but you cannot get the Date value of a Boolean attribute).

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 (querying was purposely left out of the list) to define the behavior for the Object Models in the XEO Library application. Next, you'll create the first XEO Model Method, the removeCategories method, for the LIB_Book Model.

Creating the first XEO Method - removeCategories (LIB_Book)

The LIB_Book XEO Model has a collection of categories (representing Book categories). As a convenience, if a Librarian wrongly categorizes the book it'll have a method that will clean all categories for that book (instead of forcing the librarian to individually remove each category). Open the LIB_Book 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 XEO Model Method in XEO Studio

Whenever Java code needs to be created to perform custom-logic, XEO Studio creates a Java class for each XEO Model and names the methods appropriatelly with the correct parameters. In the case of XEO 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 your 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) XEO 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. The flow of java code for "remove all book categories" ill 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.

public void removeAllCategories(boObject obj) throws boRuntimeException {
        //Check if the object is saved in the database, new objects cannot remove categories
        if(obj.exists()) {
            //Get the collection handler (bridgeHandler) for the categories attribute
            bridgeHandler categories = obj.getBridge("categories"); 
            //Iterate through them all and remove
            while (categories.next())
            { categories.remove(); }
            //Save the object
            obj.update();
        }
    }

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.

package xeo.code.java.lib1_0;

import netgest.bo.runtime.*;

public class LIB_BookHandler {

    public void removeAllCategories(boObject obj) throws boRuntimeException {
        //Check if the object is saved in the database, new objects cannot remove categories
        if(obj.exists()) {
            //Get the collection handler (bridgeHandler) for the categories attribute
            bridgeHandler categories = obj.getBridge("categories"); 
            //Truncate the collection, removing all elements
            categories.truncate();
            //Save the object
            obj.update();
        }
    }
}

Figure OM.1c - removeAllCategoires method source code optimized

Next you'll add behavior to validate the e-mail attribute in the LIB_Librarian and LIB_User

LIB_Librarian & LIB_User Behavior - Validate the e-mail attribute

The LIB_Librarian XEO 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;
 }

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 (i.e. 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 does not exist. 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)

_

LIB_Message Behavior - Date Default Value and DisabledWhen

The Message XEO Model has a date attribute which represents the date the message was sent. Although a system SYS_DTCREATE attribute exists in all instances, this is added so that you could see some behaviors such as the default value and the disabled condition. The date attribute is a value that will not be user-editable (it would not make sense to change the date a certain message was sent), as such it's a value that needs to be displayed but not changed and XEO provides that ability by allowing a "disabled when" condition for each attribute.

Go the date attribute in LIB_Message and scroll to the "Default Value" section, choosing Java from the drop down and press edit (which will trigger the generation of a Java class handler for LIB_Message. The code to return the current date is the following:

public String date_DefaultValue(boObject obj,AttributeHandler attribute) throws boRuntimeException {
        Calendar dueDate = Calendar.getInstance();
        return new Date(dueDate.getTimeInMillis()).toString();
    }

Default value code for the data attribute

One could use BOL, instead of JAVA to set this value, as the Business Object Language has an expression, NOW , which returns the current date.

For the disabled condition, in the same attribute scroll to the "Disabled When" section and choose "BOL" from the drop down and type true as the value (Java code could also be used with a "return true" statement inside the function, this dual usage of BOL and Java is to highlight that it can be done and each language should be used whenever they make sense to you as a developer.

LIB_Movement - Events, default value and collection attribute restrictions

The Movement entity represents a book loan from a Library User. A Movement instance can be in one of two states. The "Open" state (where some, or all, of the books are still in possession of the Library User and the "Closed" state when the User has returned all books of that movement. Books that are currently loaned to a user have their state as "Unavailable" which is something done in the act of creating the Movement. Also, movements cannot be changed (i.e. if a user wants to add another book to a movement he can't, he must create a new movement)

Updating all Books in the creation of a movement (onBeforeSave)

To update all books in the creation of a movement you'll add an OnBeforeSave event to the LIB_Movement XEO Model, in which you'll change the state of the all of the books of the movement to "unavailable". To add an event, open the LIB_Movement Object Model and go the Events section, press the add button and choose "OnBeforeSave" from the drop down, then press "Edit" button to let XEO Studio generate the handler class for the LIB_Movement XEO Model, see figure OB.3 for an illustration.

Add onBeforeSave Event to Movement

Figure OB.3 - Adding the onBeforeSave event to the LIB_Movement Object Model

The condition for the onBeforeEvent is to change all instances of LIB_Book in the books collection attribute to a state of "Unavailable", but only if the movement itself is a new movement (it doesn't exist and its state attribute has value 0 [more on this in the following paragraph]). The java code to assure this condition is the following:

/**
     * 
     * Before a new Movement is saved, change the state of all books
     * 
     * @param obj The Movement instance
     * @param event The triggered event
     * @return True if the movement can be saved
     * 
     * @throws boRuntimeException If an error occurs while updating 
     * a book
     */
    public boolean onBeforeSave(boObject obj,boEvent event) throws boRuntimeException {

        //Only execute this action
        if (!obj.exists()
                && obj.getAttribute("state").getValueString().equals("0")) {
            //Retrieve the collection attribute and put the handler in
            //the first position
            bridgeHandler books = obj.getBridge("books");
            books.beforeFirst();
        
            //Iterate through all books and change state to 1 (see Lov values from LIB_Lov)
            while (books.next()) {
                boObject currentBook = books.getObject();
                currentBook.getAttribute("state").setValueString("1");
            }
        }
        
        //Return true so that the 
        return true;
    }

Note: There's no call to the update() method on each boObject representing a book. Whenever the owner of a collection attribute is saved, all elements in the collection are saved as well, and since this code is executed right before the save of the movement instance, when save actually occurs, all books will be updated.

Default State for the movement

The state attribute will have a default value of "Open", which means when a movement is created it's always because the user has loaned some books from the library. The "Open" state is the label corresponding (in the movement_type lov) to the "0" value, so in order to have that as a default value, go to the state attribute, find the "Default Value" section, choose "BOL" and type in "0" (without quotes).

Locking all attributes of an existing movement - onAfterLoad

Whenever an existing LIB_Movement instance is opened you want to lock all attributes so that they cannot be changed. The only thing that can be done in a movement is return books (which will be done later in the documentation due to the dependency on book selection from the visual layer). Add the onAfterLoad event to the LIB_Movement Object Model and choose Java as the language and press edit to let XEO Studio create another method within the existing Java class handler. The code for the event is the following:

/**
     * 
     * Disables all attributes of an existing movement
     * 
     * @param obj The instance movement
     * @param event The event 
     * 
     * @throws boRuntimeException If a problem occurs while disabling the attributes
     */
    public void onAfterLoad(boObject obj,boEvent event) throws boRuntimeException {
        
        //Only apply the condition if the object is saved
        if (obj.exists()) {
            
            //Get an enumeration of all attributes
            Enumeration atts = obj.getAllAttributes().elements();

            //Iterate through all of them and disable them
            while (atts.hasMoreElements()) {
                AttributeHandler currAtt = (AttributeHandler) atts.nextElement();
                currAtt.setDisabled();
            }
        }
    }

Collection Attribute restrictions (Filtering)

When adding a new collection attribute to an Object Model one has to choose the type of XEO Model instances which are allowed in the collection. You can choose a single type of instance (from one Object Model) or a set of different types. Business Logic, however, can dictate that not all instances from a given type are applicable and XEO provides a feature to allow filtering of instances.
Please note that this feature only has an effect when using XEO's Web Component Layer, i.e. if you create a filter on a collection and use the API to add an instance that is of a valid Object Model for that collection but does not match the rules of the filter, nothing is going to prevent it to be added. Filters are applied when listing instances in a lookup viewer. Most of the times, this is all that's needed as all interaction with the application is done through the Web layer but XEO provides mechanisms to allow you verify (programatically) that an instance added to a collection matches your filter (more on that in the following paragraphs)

In the books attribute of the LIB_Movement Object Model the only books that should be added are ones which are not currently loaned to a user (i.e. the state of the book is "Available", whose value is "0" as defined in book_state lov). In order to create a filter you'll need to know a bit about XEOQL (also known as BOQL, not BOL).

XEO QL Basic Introduction

XEO Query Language (or BOQL - Business Object Query Language) is a small SQL-based querying language in order to select a set of XEO Model instances. XEOQL allows to select a set of instances while being able to set conditions on that search (just like SQL), let's see an example:

select LIB_Book

The previous query selects all instances of the LIB_Book Object Model, let's see another example with some conditions.

select LIB_Book where title = 'Angels and Demons'

The previous query selects all LIB_Book instances which have a title equal to "Angels and Demons", see the next example for a demonstration of querying using relations between

select LIB_Book.categories where LIB_Book.BOUI = 1111

The previous query selects all Book Categories for the Book instance with BOUI equal to 1111

To know more about XEOQL read the XEOQL chapter. For now let's continue with the filter.

Going back to the LIB_Movement filter (and using the knowledge from XEO QL), the filter you want to apply can be described as "all book instances whose attribute state is available", translating to XEOQL would be " select LIB_Book where state = '0' ", to add an Object filter, select the "books" attribute in XEO Studio, scroll to the "Object Filters" section and press "add". A new window will open, in the XEOQL filed type the expression " select LIB_Book where state = '0' " (without quotes), see figure OB.4 for details.

Add Object Filter Movement

Figure OB.4 - Adding an Object Filter to the books attribute collection

Default Value for DueDate

The due date for a return of the books is seven days from the initial loan, as such you're going to add a default value for the dueDate attribute in the form of Java code. Go to the "Default Value" section of the dueDate attribute and select Java and press edit. In the Java editor, type the following code:

/**
     * 
     * Default value for dueDate attribute, which is seven days from the current
     * day
     * 
     * @param obj The instance movement
     * @param attribute The attribute handler for dueDate
     * 
     * @return A string with the date seven days from today
     * 
     * @throws boRuntimeException
     */
    public String dueDate_DefaultValue(boObject obj,AttributeHandler attribute) throws boRuntimeException {
        //Retrieve a calendar for current time
        Calendar dueDate = Calendar.getInstance();
        //Add seven days
        dueDate.add(Calendar.DATE, 7);
        //Return the date
        return new Date(dueDate.getTimeInMillis()).toString();
    }

This finishes our tour in the XEO Library Behavior. Next we'll go to the Web Components Layer (in the mean time we invite you to read more on the Java API, BOQL or BOL)

In the next chapter you'll start creating the XEO Web Viewers for the Library Application.

____

Topic attachments
I Attachment Action Size Date Who Comment
PNGpng AddEventMovement.png manage 18.2 K 2010-12-28 - 09:47 PedroRio Event for LIB_Movement
PNGpng AddObjectFilterMovement.png manage 98.1 K 2010-12-28 - 15:50 PedroRio  
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  

This topic: WebXEO > WebITDS > XeoPrimer > XeoPrimerObjectBehavior
Topic revision: r24 - 2011-04-04 - NicolauGrosskopf
 
This site is powered by the TWiki collaboration platform Powered by Perl

No permission to view TWiki.WebBottomBar