I&C School of Computer and Communication Sciences
Ecole Polytechnique Fédérale de Lausanne    
   

The WebLang Components

A few tips
  • You can compile by right-clicking the source window.
  • Completion works for the modules symbols, as well as for many objects (forms, precompiled objects...).
  • You can use the outline or the diagrams to quickly jump to a module.
  • An additional documentation file is available documentation.pdf

  Diagrams You may click any separation line to jump to the top of the page !             

The plugin developed for WebLang offers a possibility to display a diagram corresponding to the content of a .cg file. In order to see the diagram, select
    Window > Show View > Other > Codegen > Generic
A new view containing the diagram appears. You can move the components around to arrange them and click them to jump into the .cg file.

If you click in the window when an element is selected, the following menu is displayed, otherwise, that menu appears. The latter allows you to save the position of the components in a folder named .codegen in your project. It also contains function Auto Position, which spreads the elements to ease their positioning. Example.

Parameters and Settings

The WebLang compiler can get information about how to compile your files by two main means: reading the annotations introduced in the .cg files or the parameters introduced in the window accessed through Window > Preferences... > Code Generator > Runtime Engine. Annotations may be specific to components (single @), while other ones are general (@@).

Customization
Various parameters such as the overwriting of the output files by the compiler, or the configuration of the projects in which the files are created.
Deployment location

The location where the application packages (.war or .ear) are copied for deployment may be set at two different places.

· The following statement defines the location at which the package will be stored (entered in CGpackaging.xml). It may be placed before any module and may contain \" and \\ characters. It must end with either a \\ or a /.

@@deploypath="C:/jboss/server/default/deploy/"

· It is also possible to set the deploy path in the text area opened by selecting Window > Preferences... > Code Generator > Runtime Engine. The path entered in the .cg file has precedence over the path entered under deploypath in the preferences.

Preventing the Overwriting of Compiled Files

· If the following annotation is placed before a component, the subsequent compilations will not overwrite the previous version. On the other hand, they will continue to create the auxiliary files (.xml files).

@frozen

· The overwriting can also be controlled by using the report that appears after you have requested a compilation and you have set the Show Report flag in Window > Preferences... > Code Generator. In that page, you can chose between several actions for each file: Create Only, Overwrite, Deactivated and for the Java files, Merge (See details).

Host Name

The rich-clients, the files in the README folder and a few other ones require the introduction of a hostname. There are several possibilities to set this name, as described in the following.

· The hostname can be entered in an annotation in the .cg file as shown below.

@@hostname="localhost:8080"

· It is also possible to set the hostname in the text area opened by selecting Window > Preferences... > Code Generator > Runtime Engine. The path entered there under hostname has precedence over the path entered in the .cg file and shown above.

· Finally, if none of the previous indications has been used, the compiler gets the name by which the local host is accessed from the outside ( actually, it calls InetAdress.getCanonicalHostName() ).

Templates

The templates used to create the various files can be copied to the developer's project where he can modify them for the subsequent compilations. The templates are handled with the following annotation placed before the corresponding module. See details.

@templatedir = redefinition_package

In all modules, you may enter specific parameters that can be passed to the template and complete the available parameters. The use of these parameters is described in the generic module, a module that you can use very easily to create your own modules. To be clear, the parameters can be used in the generic modules as well as in any module.

Application Deployment

By default, WebLang generates container-managed Web applications to be deployed into JBoss Application Server 5.x, and the created EAR archives only include extra-libraries that are not available with JBoss.

However, in order to defined applications that works with Apache Tomcat or as standalone applications, the following global annotations (which are equivalent) can be used. This standalone module does not support the container-managed modules like CMP Beans or Session Beans, but can handles for instances Classes, Servlets, Struts, and JPA beans. The created WAR archives include all required libraries such as JPA-Hibernate, Struts, or JSF.

@@standalone

 

@@tomcat

File Inclusion

Files can be composed by including other files. The corresponding statements must be written at the beginning of a .cg file. Here is an example:

include "ejb.cg";
include "web.cg";

Config Global

The module config allows the definition of global package and import elements and affects all following elements of the current file until a next config declaration.

config {
  package web;
  import ejb.*;
}

Comments

Single line and multi-line comments are allowed:

// this is a comment that is not compiled

/* a comment that could span several lines */

HTML Pages
Book, Section 7.5

html pageName {
    package packName;
}

The pages produced by these statements contain very little information, but you can augment the HTML template. They just define the directory in which they will be stored and the statements that will load them into the server (in CGpackaging.xml). The HTML files will be created within a directory named WebContent1 in the project within subdirectory package. The URLs of the pages can be found in another HTML-file, in directory README.

Note: The CGxdoclet.xml need not be executed if the application contains only HTML pages.


1 This name can be changed by introducing the new name in Window > Preferences... > Code Generator > Runtime > java.web.content

Servlets

The next servlet is generated into src, package servletPack. It just contains the doGet method and writes a simple text, but remember that you can create your own template to create specific servlets.

servlet MyServlet {
    package servletPack;
}

The next servlet contains a method that  receives the parameters (of type String or int, boolean, char, byte, short, long, float and double) transmitted by an HTML <form>. Several such methods can be defined in the same servlet. If the method has not the keyword public, it can only be used locally. If the method has PrintWriter out as parameter as shown below, the compiler will generate the code that transmits the object out, to allow the method to send text to the HTML page built by the servlet, but it will of course not request it from the user.

servlet SomeServlet {
    package servletPack;
    public void someCall (PrintWriter out, String name, double number) {
        out.println(name + " " + number);
    }
}

The method arguments with the following types, PrintWriter, HttpServletRequest, HttpServletSession or HttpServletResponse, are handled as described above by the compiler. If the type of the object is of any another class, a null is coded in the call to the method.

The methods can be called from a <form>, located in an HTML file generated by WebLang and loaded in the server during deployment. The user can call this HTML file by loading a file located in directory README, which contains a link for each servlet. The HTML file can also be called from a servlet by using the statement

response.sendRedirect("servletPack/SomeServlet.html");

A servlet can access session and entity beans, and the types of the local variables are treated according to these indications.

Attention, a servlet's local attributes should only be used with extreme caution in a servlet, as several threads can access the same servlet object !

See "servlet MyServlet" below, to know how to access a CMP bean from a servlet in the CMP bean section.

JSP Pages
Book, Section 7.8
Sun documentation

This module does not do much, it just generates a mostly empty JSP file, the directories, the support XML files, an HTML files with the address of the JSP and the CGpackaging.xml, which allows you to deploy and use it. It can beused as a starting point to introduce JSPs into an application.

jsp MyJsp {
    package jspPackage;
    class MyClass;
    tag MyTag;
    applet XAPle;
}

WebLang defines JSP pages that may contain a class, a tag class and an applet. The order of these elements is irrelevant and they may not be all present. However, there can currently be at most one of each.

The JSPs are compiled by javac, the address of which JBoss finds by means of variable JAVA_HOME

Warnings:

Classes

Classes are stored in the .war or in the .jar file depending on the presence or the absence of EJBs.

class MyClass {
    package aPackage;
    import java.util;
    access remote;            // optional, default is local
    outputchannel myCh (queue, A);
    Bean bean;
    public void aMethod() throws Exception {
        System.out.println("a method called");
        Bean b = beanHome.create();
        b.method();
    }
}

sbean Bean {
   package aPackage;
   public Bean create();
   public void method() {}  

}

WebLang defines classes, with a package and an access statements on the first lines. If package statements are placed before the jclass module, between other modules, and no package statement is found inside the module, the package of the class is taken from the previous package statement found in the text. The replacement of attributes and local variables that refer to a bean module are handled as indicated in that page. They may contain channels, for which extra methods and attributes are inserted.

If the statement access is present with keyword Remote, all home and object references are generated for a remote access.

If you use this module only to introduce classes in the CGpackaging.xml, insert protected class myClass { package xxx;} in the WebLang file and create the classes in the usual Eclipse environment to take advantage of the Java editor.

Basic Struts

Introduction

WebLang defines two kinds of struts: the basic Struts, described in this section, and the FSM Struts described in the (next section).

This paragraph first introduces the general concept of Struts.

Struts are built around so-called action servlets, namely servlets with extra capabilities for storing the data coming with the URL into Java beans called action forms (more below) -- remember, a Java bean is a class that contains mostly getters/setters. These servlets are called by JSP pages (more below) that contain HTML forms (more below). Once the user clicks the submit button of an HTML form, the data entered in that form are transmitted to the main code of the action servlet within the Java beans. These data are handled by the action servlets and forwarded to new JSP pages, also through Java beans.

The same forms can be reused by all servlets. A JSP may be bound to a specific action servlet (in file struts-config.xml).

An action form may contain other action forms and this recursively, forming a tree of forms, as well as arrays of leaf forms.

The basic possibilities of the Struts are represented in the figure below: the successive steps of an application are embedded within a network of JSPs and action servlets.

Example of a Basic Struts

In the code below, the forward statement defines a symbol that may be introduced in a statement mapping.findForward("mainHtml"), such as the one that appears before the page definition. The content of the body block is copied into the execute method. The page defines a JSP page with the HTML forms that contain the fields to be filled by the user in his browser (see next section). By default the forms are included into the session object and can be retrieved using method (TypeForm)request.getSession().getAttribute("nameForm").

basicstruts Login {
      package web;
      forward mainHtml = "/start/Lottery.jsp";
      body {
            try {
                  CustomerData custData  = (CustomerData)request.getSession().getAttribute("customerData");
                  Facade fac = FacadeHome.create();
                  fac.registerCustomer(custData.getName());
                  out.println("You are logged in!");
            } catch (Exception e) {
                  out.println("An error has occured during login: "+ e.getMessage());
                 return mapping.findForward("LoginPage");
            }
            return mapping.findForward("mainHtml");
      }
     page LoginPage {
            customerData (Enter) {}
      }
}

The names of the pages and action to which the struts can forward control are available in WebContent/WEB-INF/struts-config.xml.

Compiling and running a Struts application

The Struts module requiresat minimum the Java 5.0 compiler (Right-click the project name, > Properties > Java Compiler > Compilance Level: 5.0). You may also have to set the Project Facets, located below Java Compiler in the window accessed above.

The Struts (basic and fsm) require several jars. Some of them are copied into WebContent/WEB-INF/lib and introduced into the Build Path (in order to see them, use the Navigator!). The class path used by JBoss is rather awkward and in order to make sure the jars can be accessed from the servlets, from the Entity beans and from the JPA, we had to put some jars in directory jboss/server/default/lib. Thus in order to use Struts, you must copy the following jar into that folder:

struts-core-1.3.8.jar
commons-beanutils-1.7.0.jar
commons-digester-1.8.jar,
commons-validator-1.3.1.jar
and
commons-chain-1.1.jar.

They are available in the WebLang plugin, at Eclipse/features/ch.epfl.lti.codegen_2.x.x/engines/ch.epfl.lti.codegen.weblang/templates/struts/libjboss

All the jar files are introduced in the project class path (in CG_Struts), but they disappear when Eclipse is shutdown. Just recompile the .cg file to reintroduce them.

In order to prepare an application that contains Struts, compile them with the WebLang compiler, execute CGxdoclet.xml and CGpackaging.xml, and finaly load the HTML file that is located in the README folder.

Struts Including an FSM
Preparing a Project With Struts
See last section
FSM Struts Component Defined by WebLang

WebLang proposes an enhanced version of the Struts presented in the previous section. This new version uses only standard code, but implements a finite state machine (FSM). A Struts module as defined by WebLang may contain all the steps of an application, the corresponding pages, the action forms and a bunch of auxiliary files. The application can also be splitted into several action servlet, each one with its own state machine (see below), following the model described by a state chart, if its complexity grows.

The following figure provides an abstract view of the construct defined by a Struts module. Each state is bound to a JSP, namely each time the action servlet is in state X, the JSP linked to X is displayed in the user's browser (and redisplayed if the latter does not respond from this page). The state machine is entered at the state state_s, but never goes back to it in that session.

The action servlets generated by WebLang have provision for checking that the user sends commands from the last page sent by the server. Here is the reason for this behavior. In a Web application, the user has the possibility to use the BACK or the FORWARD button of his or her browser, which may desynchronize the session state from the user's browser state. This is particularly annoying when the user must acknowledge an order or a service, because it may lead to double ordering. Actually, many applications just warn the user not to click the acknowledgement button twice to prevent them from entering their orders twice. Our approach uses the basic features of the struts to introduce identifiers in the pages and to check the identifiers returned by the users. If the user has desynchronized the pages, the page from which the user is expected to answer is sent again.

The Action Servlets

The following shows an example of a Struts module. Its components are describes in the subsequent paragraphs.

struts Supplier {
    package store;
    import fp.*;
    statemachine {
        state state_0 > supply;
        switch (sessionState) {
        case state_s: // first entry into the FSM
            Thing thing = new Thing();
            thing.setSubName("");
            petForm.getThingN().add(thing); // page
            sessionState = state_0;
            break;
        case state_0:
            String s = petForm.getName();
            Thing th = ((java.util.ArrayList<Thing>)petForm.getThingN()).get(0);
            if ((th != null)&&(s!=null)) {
                th.setSubName(s.toUpperCase());
                th.setNumber(s.length());
                out.println("Name is with upper cases");
            }
            break;
        }
    }
    page supply {
        petForm(Enter) {}
        text = "In order to see something, enter bbb above";
        {   // a group of forms in the same block
            petForm { }
            petForm.thingN[] (OK) { // an array of sub-action forms (default is readonly)
                readwrite name;
                combo (AAA, BBB) subName;
            }
        }
    }
}
form PetForm {
    package fp;
    String name; // attribute
    Thing thingN []; // subform
}
form Thing {
    package fp;
    String name;
    String subName;
    int number;
    boolean check;
}

Action Servlets: the Statements (See figure)

  • The statemachine block contains a list of state definition statements followed by a list of standard Java statements.

  • The page that is displayed while the action servlet is waiting in a given state is specified by a state statement. Thus, all transitions that start from a given state have a well defined set of data to handle. On the other hand, several states may display the same page.

  • The states of the FSM (finite state machine) are defined with the standard Java statements, to avoid the definition of yet another language construct. The states correspond to the cases of a switch, guarded by the symbols appearing in the state statements, but the compiler accepts any Java statement sequence after the state statements and it is the responsability (and the freedom) of the developer to code a state machine or any other statement she or he finds convenient.

  • There must be one case guarded with state_s with the assignment to sessionState as indicated above. state_s is entered before the creation of the first page, to give the servlet the opportunity to initialize it. The use of state_r is explained below and is optional.

  • A facility for displaying texts on the next JSP is provided under the form of statements.

    out.println("yyy");

    out.print("xxx");

    out.print(2.8);

    out is a Java bean, the content of which is automatically displayed at the beginning of the next JSP. It is cleared at the beginning of each execution of an action servlet.

  • The exit from the application is done with the following statements:

    request.getSession().invalidate();
    return mapping.findForward("start") ;

    In order to forward control to another page (JSP or HTML), add a line similar to the one that contains start in the file data/struts/merge/global-forwards.xml created in your project by the WebLang compiler, or, better, modify the corresponding template.

  • The page that starts the struts application in the README folder contains a command to clear the client session. This command resets the state of the FSM, to prevent it to restart at the location it was left in in the last execution.

  • The WebLang compiler creates state variables (for storing the session state) and state identifiers (for the cases of the switch). The names of the states of the switch cases are automatically transformed to distinct names by the compiler.

  • These indications apply to the action servlets !

 

Action Servlets: Advice

  • The struts require a lot of auxiliary files. You may want to have a look at WEB-INF/struts-config.xml and WEB-INF/web.xml, which contain the most important information.

  • The forms are handled by a so-called request processor before they are delivered to the action servlet. This processor is located in strutsfsm.StateMachineRequestProcessor.java, generated by the WebLang compiler into your source folder.

  • The forms are by default handled as session objects, they can also support the request mode as explained below.
  • Do not begin the names of the forms with two upper case letters, the JSPs do not find the getter/setter, in these cases, it seems.

  • The struts use JSPs, which are compiled by JBoss by means of javac. JBoss needs thus variable JAVA_HOME to access this compiler.

  • The following programming trick eases the initialization of the pages used in each state: store all the statements that must be executed before displaying a page in a given state in a switch statement that follows the FSM switch. When the code leaves the switch of the state machine, the next state and the JSP that must appear in that next state will be initialized whatever the last state was.

  • When debugging an FSM, it is very useful to introduce System.out.println(sessionState); after the FSM switch.

  • The CMP bean proxies may be defined inside the FSM in the same way as in the servlets.

  • The following sections provide enough details to use the Struts within WebLang, but here is a detailled description for the readers who want to extend them.

The Forms

The following listing shows the definition of several action forms.

form SportForm {
    package fpackage;
    String sportName;
    int location;
}
form PersonForm {
    package fpackage;
    String personName;
    SportForm sportForm;
    JobForm jobFormN [];
}
form JobForm {
    package fpackage;
    String jobName;
    int location;
}

A Java bean is created for each of these form modules. A form module contains a package statement and (optionally) a list of attributes and forms. The form definitions may have the [] indication, but not the attributes.

The allowed field types are : "String", "boolean", "char", "byte", "short", "int", "long", "float", "double". Note: the boolean are represented by checkboxes. The value of the boolean field must be set to false before it is represented on the page, otherwise the user cannot reset it (Struts bug/feature).

The fields name "submit", "pageid" and "flowId" are reserved for internal processing, and this recursively.


Use of subforms

The action forms may contain subforms (like sportForm in personForm in the first figure of struts). The subforms can be referred to as follows:

In the JSP:    

    formName.subform.field

In the servlet:

    formName.getSubform().getField()

One can also specify arrays of subforms in the forms statements located within the form components, by means of the usual signs. In that case the getters/setters handle java.util.Collection.

The Pages

A page displays a series of HTML forms, each one corresponding to the fields of an action form or a subform. The subforms that lay besides the fields are ignored if they are not explicitly referenced. The subforms of the pages must correspond to subforms that are defined in the forms statements. Here is an example of a page with the command that has generated it.

A block of forms and subforms can be grouped inside a single form, as shown by the block 3. In such a block, one can insert the root or branches that start at the same root.

       

  page supply {
      petForm (Enter) {}   1
      text = "In order to see something, enter 'bb' above";  2
      {
          petForm { }
          petForm.thingN [] (OK, Cancel) { 3
              readwrite name;
              combo (AAA, BBB) subName;
          }
      }
  }

  form PetForm {
  package fp;
    String name;         // attribute
    Thing thingN [];     // subform
  }

  form Thing {
    package fp;
    String name;
    String subName;
    int number;
    boolean check;
  }

The attributes of a simple forms appear in column, whereas the arrays of forms appear in lines in the pages. You may thus code an array with a single line to display a line of fields.

By default, the fields in the pages have the same names as the classes, but the first letter is turned to a lower case. This name is also used as identifier in the servlet session/request. It is also possible to enter a class name and to have thus two object of the same class in the session/request, which is illustrated in the example below.

  page supply {
      pet<PetForm> (Enter) {}  
      {
          pet1<PetForm> { }
          pet1<PetForm>.thingN [] (OK, Cancel) { X
              readwrite name;
              combo (AAA, BBB) subName;
          }
      }
  }

Forms can be compared to classes, while pages can be compared to objects. Thus, unlike the fields of the forms, the fields of the pages do not always contain a class indication. A field starting with a lower-case letter correspond to the form with the upper-case. If one needs two names for fields with the same form, as shown above, one can insert the form name between arrows.

The syntax is a bit awkward, but the class defines the part of the variable before the dot. If we would have put the class before the name of the field, the class PetForm on line X would correspond to the elements of ThingN[], which is not the case.

Properties of the fields

The following properties may be defined in the pages, before the attribute names, but of course they must be related to attributes defined in the corresponding forms. You may put either one of the lines shown below:

hidden
message
readonly
readwrite


readonly |
readwrite

         

radio (aaa,bbb) |
combo (aaa,bbb) |
password

      // one of each group, e.g. readwrite radio


radio (aaa,bbb)
combo (aaa,bbb)
password

Example

        case state_0:
            if(whatDoYouWant.getRadioChoice().equals( "buyPet" ))
                branchToMachine( "buying_subTask", request);
        }
    }
    page choice {
    text = "You can enter new pets " ;
        whatDoYouWant (Choose) {
            radio (supplyPet, buyPet) radioChoice;
        }
    }

 
form WhatDoYouWant {
    package store;
    String radioChoice;
}

Forms Scope and Creation

By default the forms are handled as session objects; they are instanciated when an FSM starts and automatically saved into the session. The formLink1 defined in the following code is equal to default behavior. On the contrary, the following formLink2 is defined as request object and it is not automatically instanciated; it must be done manually by the developer.

page page1 {
    formLink1 tosession instance (Action1);
    formLink2 torequest noinstance (Action2);
}

Submit button

If there is no button in the statement, no button appears and the fields cannot be edited. There may be several buttons.

The name of the submit button that has been clicked can be checked in the servlet with :

    submit.equals("Cancel")

Note: Currently, a field String submit is defined in each form to pass the name of the button clicked on the page. Thus, the compiler does not let the developer create a field named submit.

Validation of the inputs

It is possible to request the compiler WebLang (+xdoclet) to create a default validation file in WEB-INF for particular forms by introducing the following command right after the package statement.

    validation;

In that case, the page is requested again if the user has not filled the fields correctly; the validation properties are specified using xdoclet statements in the produced form classes, and the xdoclet processing build the WEB-INF/validation.xml file. This configuration file can be modified freely by the developer to insert more evloved validation rules, more details available at Struts User Guide.

When a form's field is specified using Java simple types, such as int, short, and long, the validation process is not able to differentiate a non-entered value from 0, use the Java types based on a real class, such as Integer, Short and Long, to handle this problem.

The xdoclet processing is not able to deal with the automatic validation of a sub-array of forms, in this case please modify the validation.xml file and insert the following statements for each field to validate:

<form name="superForm">
  <field property="fieldName" indexedListProperty="subforms" depends="required">
   <arg position="0" key="superForm.subforms.fieldName"/>
  </field>
  ...
</form>

It is also possible to specify manually the validate process in the Java generated form, in redefining the validate(...) method as presented bellow:

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
  // call super validation check
  ActionErrors errors = super.validate(mapping, request); ...
  errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("key", "arg0"));
  ...
  return errors;
}

Or to add a validation message directly in the action process, in calling the addFSMErrorMessage(...) method as presented bellow:

addFSMErrorMessage(HttpServletRequest request, String key, String... args)

The automatic validation of forms composed of other forms and/or of simple fields is ONLY possible if the page related to this form includes all fields (no hidden fields or subforms).

Jumping from one State Machine to the Other

The figure below depicts a jump from one state machine to another one and the back jump (the dark blue elements represent states, the light blue ones execution code).

In order to handle the initialisation of the page to forward to in case of a redirection, the definition of a redirect state (state_r) is mandatory. A redirection happens when a request is not valid or not expected, or when an FSM jumps back to an existing FSM.

The Redirect state must be build in the following way:

case state_r:
    // some user code

    sessionState = suspendedSessionState;
    break;

It allows the program to update the input JSP to the last data (either in the State_R case or in a general switch that concentrates all initializations of the JSPs). The suspended state is saved automatically. When the state_r case is missing, the system automatically restore the suspended session state, but the user code that must process the suspended state can be jumped.

The following statements perform the jumps from one state machine to another one. The same statements may be used in the destination struts to come back or to jump farther. If a user tries to re-enter the struts that has performed this jumps, by using the BACK or the FORWARD button of the browser, the struts will deviate the call to the current struts and state, and the current page will be redisplayed. Warning: the name of the destination Struts FSM must be exactly the same as the static variable MACHINE_ID.

    sessionState = state2;
    branchToMachine(OtherStruts.MACHINE_ID, request);

The following statements can be used to call another state machine

    sessionState = state3;
    pushAndBranch(OtherStruts.MACHINE_ID, request);

and these ones to return to the caller whowever it was:

    sessionState = state1;
    popAndBranch(request);

Internationalization (a.k.a. i18n)

The folder resources created by WebLang contains files application.properties that store properties. Attention, the folder is stored in src/resources as specified by WTP, and thus, it does not appear in the package explorer unless you have enabled the display of the empty packages.

The messages in file application.properties may be accessed by commands such as <bean:message key="supplier.townPage.townForm.text"/> to display the corresponding texts.

If the browser used to call the application is set to French, for example, the Struts package will find this indication and use file application_fr.properties, which is assumed to contain the French translations of the texts appearing on the pages, instead of the basic application.properties file.

In order to create such a file, copy the basic file to its corresponding language-specific version (_fr, _en, _de), ... and edit its content (see java.util.Locale for other extensions).

Parallel Execution

By default only one FSM instance (conceptually, because several Java instances are used) of the same type can be executed at a time. It means that the current state of a given FSM is unique (within the same Web browser instance). However, in order to handle specific cases, it is interesting to support the execution in parallel of several instances of the same FSM (each one with a different current state).

An FSM can be defined with the parallel_flow capabiliy as presented in the following code. In this case, it supports the creation of multiple instances of FSM; the following list gives information to use this feature.

struts Lottery {
  statemachine parallel_flow{
    ...

    flowSession.put("myForm", formLink);
  }

...
  page page {
     formLink torequest(Action);
  }

}

  • All form links must be defined with parameter torequest to not overwrite forms of another parallel flow.
  • The branching ability of FSM is also available for parallel FSM, but all FSMs must be defined as parallel.
  • The map flowSession (HashMap<String, Object>) allows the developers to store ans retrieve forms or other information through the global flow execution. This map is unique foreach flow.
  • Another map is provided to access objects that belongs to the current FSM of the current flow: fsmInfo.getContextMap()
  • In order to initiate the execution of a new flow, the flowId parameter must be filled by the name of the flow to create or access if already exists. It can be done using the following URL link: http://localhost:8080/fsmname.do?flowId=flow2
Permissive History Access

By default an FSM only executes requests coming from a unique expected page, all past requests are not executed and the view is automatically redirected to the expected one. In some cases, this control is not mandatory and certains requests can be executed many times without problems.

An FSM can be defined with the history_access capabilitiy as presented in the following code. In this case, a user can post a similar request several times (using back in the Web browser) without being redirected. However, by marking a state with block_history, it is possible to block this automatic access to history pages. In the following example, as soon as the state_confirm action has been executed (the user clicks a button in the ConfirmPage), all pasts requests cannot be executed anymore.

struts Lottery {
  statemachine history_access {
   state state_question > QuestionPage;
   state state_confirm > ConfirmPage block_history;
    state (sessionState) {

JavaServer Faces (JSF) including an FSM

JavaServer Faces (JSF) is a Web application framework specification (version 1.2: JSR252) based around the model-view-controller pattern. Unlike request-driven frameworks (e.g. Struts), JSF uses a component-based approach (e.g. Swing), and transparently handles saving and restoring the UI components states through the request/response cycles. The default JSF components provided by the core implementation (Sun Mojarra or Apache MyFaces Core) can be easily extended through addtional libraries such as MyFaces Tomahawk or RichFaces. By default, JBoss 5.x includes Mojarra as JSF core, but the JSF-based applications generated by WebLang also uses Tomahawk. Note that the add-ons RichFaces and the Java Standard Tag Library (JSTL) are also available.

WebLang proposes an enhanced version of JSF including a finite state machine (FSM). The definition of a JSF fsm is very similar with the previous Struts FSM.

JSF FSM Action

The following shows an example extract of a JSF module. Its components are describes in the subsequent paragraphs.

jsf Lottery { 
    statemachine { 
        state state_login > LoginPage;
        state state_lottery > LotteryPage;
        state state_choose > ChoosePage;
        state state_pay > PayPage;
        state state_confirm > ConfirmPage; 
        state state_mytokens > ListPage;

        
        switch (sessionState) {  
            case state_s: sessionState = state_login; break;
            case state_r:
                sessionState = suspendedSessionState;
                break;

            case state_login:
                try {
                    FacadeLocal fac = FacadeUtil.getLocal();
                    fac.registerCustomer(customer);

                    addFsmMessage("You are logged in!");
                    sessionState = state_lottery;
                } catch (Exception e) {
                    addFsmMessage("An error has occured during login: " + e.getMessage());
                }
                break;



            case state_lottery:
                if (submit.equals("Choose")) {
                    sessionState = state_choose;
                } else if (submit.equals("Logout")) {
                   session.invalidate();
                   redirect("LoginPage.jsf");
                   return;
                } else if (submit.equals("MyTokens")) {
                    sessionState = state_mytokens;
                }
                break;



            case state_choose: sessionState = state_pay; break;

            case state_pay: sessionState = state_confirm; break;

            case state_confirm:
                try {
                    if (submit.equals("Confirm")) {
                        FacadeLocal fac = FacadeUtil.getLocal();
                        addFsmMessage(fac.registerTokenCard(customer, mytoken, cardForm.getNumber()));
                    } else {
                        addFsmMessage("Your order has been cancelled, as you required");
                    }
                } catch (Exception e) {
                    addFsmMessage("An error has occured: " + e.getMessage());
                }
                mytoken = new Token();
                cardForm = new CardForm();
                sessionState = state_lottery;
                break;

 
            case state_mytokens:

                try {
                    if (submit.equals("Reset")) { // reset the losing tokens
                        FacadeLocal fac = FacadeUtil.getLocal();
                        addFsmMessage(fac.resetTokens(customer));
                    }
                } catch (Exception e) {
                    addFsmMessage("An error has occured: " + e.getMessage());
                }
                sessionState = state_lottery;
            }

            // init token list
            if (sessionState.toInt() == state_mytokens.toInt()) {
                FacadeLocal fac = FacadeUtil.getLocal();
                addFsmMessage(fac.genListTokens(customer, tokenList));
            }
    }
    page LoginPage {
       customer (Enter );
    }         
    page LotteryPage { 
        lotteryForm (Choose, MyTokens, Logout);
    }
    page ChoosePage { 
        mytoken<Token> (Enter) { hidden result; hidden date; hidden draw; }
    }
    page PayPage { cardForm(Enter); }
    page ConfirmPage {
        mytoken<Token> (){ hidden result; hidden date; hidden draw;}
        cardForm (Confirm, Cancel) { readonly number; }  
    }
    page ListPage{ 
        tokenList.tokens[](Exit, Reset);  
    }    
}

 

// ... SBean Facade ...



form CardForm {
    int number;
}
form TokenList {
    import ejb.Token;
    Token[] tokens; 
}
form LotteryForm { }
form Token { 
    int value;
    String result; 
    int draw;
    String date;
}
form Draw  { 
    long date;
    int value;
}
form Customer {
    String name;
}

FSM Statements

  • The statemachine block contains a list of state definition statements followed by a list of standard Java statements.

  • The page that is displayed while the FSM is waiting in a given state is specified by a state statement. Thus, all transitions that start from a given state have a well defined set of data to handle. On the other hand, several states may display the same page.

  • The states of the FSM (finite state machine) are defined with the standard Java statements, to avoid the definition of yet another language construct. The states correspond to the cases of a switch, guarded by the symbols appearing in the state statements, but the compiler accepts any Java statement sequence after the state statements and it is the responsability (and the freedom) of the developer to code a state machine or any other statement she or he finds convenient.

  • There must be one case guarded with state_s with the assignment to sessionState as indicated above. state_s is entered before the creation of the first page, to give the servlet the opportunity to initialize it. The use of state_r is explained here (Struts FSM) and is optional.

  • A facility for displaying dnamic messages on the next JSP is provided under the form of statements:

    addFsmMessage("You are logged in!");
    addFsmMessage("An error has occured: " + e.getMessage());

  • The exit from the application is done with the following statements:

    session.invalidate();

    redirect("LoginPage.jsf");

    Note that the argument path of the redirection is relative to the current displayed page. This function can also be used to forward control to an external page (JSP or HTML).

  • The page that starts the JSF application in the README folder contains a command to clear the client session. This command resets the state of the FSM, to prevent it to restart at the location it was left in in the last execution.

  • The WebLang compiler creates state variables (for storing the session state) and state identifiers (for the cases of the switch). The names of the states of the switch cases are automatically transformed to distinct names by the compiler. Be carefull to only compare state identifiers using == sign.

  • These indications apply to the JSF actions !

Advices

  • The following programming trick eases the initialization of the pages used in each state: store all the statements that must be executed before displaying a page in a given state in a switch statement that follows the FSM switch. When the code leaves the switch of the state machine, the next state and the JSP that must appear in that next state will be initialized whatever the last state was.
  • The management of the FSM is handled by a phase listener located in fsm.FsmPhaseTracker.java, which is generated by the WebLang compiler into your source folder.
  • The main FSM actions are handled as JSF managed beans including a function execute(ActionEvent event) that response to all button actions. The page forms are simply handled as member objects.
  • The forms are by default handled as session objects, they can also support the request mode as explained below.
The Forms

The following listing shows the definition of several action forms.

form SportForm {
    String sportName;
    int location;
}
form PersonForm {
    String personName;
    SportForm sportForm;
    JobForm jobFormN [];
}
form JobForm {
    String jobName;
    int location;
}

A Java bean is created for each of these form modules. A form module contains a package statement and (optionally) a list of attributes and forms. The form definitions may have the [] indication, but not the attributes.

The default allowed field types are : "String", "boolean", "char", "byte", "short", "int", "long", "float", "double", but it is possible to use any types through the use of converters (see below)

The Pages

A page displays a series of HTML forms, each one corresponding to the fields of an action form or a subform. The subforms that lay besides the fields are ignored if they are not explicitly referenced. The subforms of the pages must correspond to subforms that are defined in the forms statements. Here is an example of a page with the command that has generated it.

A block of forms and subforms can be grouped inside a single form, as shown by the block 3. In such a block, one can insert the root or branches that start at the same root.

form        

  page supply {
      petForm (Enter) {}   1
      text = "In order to see something, enter 'bb' above";  2
      {
          petForm { }
          petForm.thingN [] (OK, Cancel) { 3
              readwrite name;
              combo (AAA, BBB) subName;
          }
      }
  }

  form PetForm {
  package fp;
    String name;         // attribute
    Thing thingN [];     // subform
  }

  form Thing {
    package fp;
    String name;
    String subName;
    int number;
    boolean check;
  }

The attributes of a simple forms appear in column, whereas the arrays of forms appear in lines in the pages. You may thus code an array with a single line to display a line of fields.

By default, the fields in the pages have the same names as the classes, but the first letter is turned to a lower case. It is also possible to enter a class name and to have thus two object of the same class in the session/request, which is illustrated in the example below.

  page supply {
      pet<PetForm> (Enter) {}  
      {
          pet1<PetForm> { }
          pet1<PetForm>.thingN [] (OK, Cancel) { X
              readwrite name;
              combo (AAA, BBB) subName;
          }
      }
  }

Forms can be compared to classes, while pages can be compared to objects. Thus, unlike the fields of the forms, the fields of the pages do not always contain a class indication. A field starting with a lower-case letter correspond to the form with the upper-case. If one needs two names for fields with the same form, as shown above, one can insert the form name between arrows.

The syntax is a bit awkward, but the class defines the part of the variable before the dot. If we would have put the class before the name of the field, the class PetForm on line X would correspond to the elements of ThingN[], which is not the case.

Properties of the fields

The following properties may be defined in the pages, before the attribute names, but of course they must be related to attributes defined in the corresponding forms. You may put either one of the lines shown below:

 

hidden
message
readonly
readwrite

         

radio(aaa,bbb)
combo(aaa,bbb)
password

parameter("required=\"true\")

subelement("<f:validateLength minimum=\"6\"/>")

      // one of each group, e.g. readwrite radio

        case state_0:
            if(whatDoYouWant.getRadioChoice().equals( "buyPet" ))
                branchToMachine( "buying_subTask", request);
        }
    }
    page choice {
    text = "You can enter new pets " ;
        whatDoYouWant (Choose) {
            radio (supplyPet, buyPet) radioChoice;
        }
    }

 
form WhatDoYouWant {
    package store;
    String radioChoice;
}

the parameter statement allows the inclusion of extra parameters to the correponding JSF tag and the subelement statement defines entirely a new sub element (tag).

Properties of the Buttons

The following properties may be defined in the pages, after the button identifier in order to define additional information concerning the button presentation and/or action.

button1 parameter("immediate=\"true\"")
button2 a4j (jsMethod, "reRender=\"form_id_1\"" )
button3 tagdefinition("<a4j:commandButton reRender=\"form_id_1\" actionListener=\"#{fsmName.jsMethod}\" id = \"MyID\" value=\"ButtonName\"/>")

The statement a4j creates a javascript button, which is defined to call the Java method named jsMethod1(ActionEvent e). The statement tagdefinition allows the entire definition of the tag as presented upper (fsmName is the name of the current JSF FSM).

The definition of the javascript method responding to a JS button can be simply done as presented below and added at the end of the JSF FSM block.

jsf FsmName { 
  statemachine {
      ...
  }
  page { ... }
  
  public void jsMethod(ActionEvent e){
    form.setValue("JS Update");
  }
}

Text Statements

The text statements defined as string elements are directly inserted into pages, and can therefore include any HTML/JSF statements as presented in the following (the character quote can be defined using \").

text = "<rich:tabPanel> <rich:tab label=\"Client\">";
...
formLink1 (Action1, ...);
...
text = "</rich:tab></rich:tabPanel>";

Forms Scope and Creation

By default an FSM is handled as a session object (managed bean), and all member forms are instanciated when the FSM starts. In this case, the same instance of managed bean processes all successive user requests that belongs to the current FSM.

In the following the JSF FSM is declared with a request scope. In this case a new instance of managed bean is instanciated to process each request of a given user; the member forms are also instanciated in foreach request processing.

The formLink1 defined in the following code is equal to default behavior. On the contrary, the following formLink2 is defined is not automatically instanciated, when the managed bean is created and must be done manually by the developer.

jsf FsmName { 

    statemachine torequest { 

    ...

    }

    page page1 {
        formLink1 instance (Action1);
        formLink2 noinstance (Action2);
    }

}

Submit Id

If there is no button in the statement, no button appears and the fields cannot be edited. There may be several buttons.

The name of the submit button that has been clicked can be checked in the servlet with :

    submit.equals("Cancel")

Note: this submit value is retrieved by the parameter ActionEvent : event.getComponent().getId()

Validation of Fields (Input)

The JSF frameworks provides default validators using special tags (Tag Library). They can be specified for a specific form field using WebLang and the subelement statement, as presented in the following :

formLink1 (Button1){
 subelement("<f:validateLongRange minimum="20" maximum="1000" />") valueInteger;
 subelement("<f:validateLength minimum="6" maximum="16" />") valueString;
}

When the validation is more complex, JSF allows the developers to define the process using a Java method. The following parameter can be added to a formLink field, and the corresponding method must be created as presented in the following in the JSF FSM block. This method can throw validation exceptions with a dynamic message; this message can be translated (see Internationalization) as presented in the second throw statement below.

jsf FsmName { 

  statemachine {

  ...

  }

  page {

    formLink1 (Button1){

      parameter("validator=\"#{fsmName.validateComplex}\"") valueComplex;

  } }

  

  public void validateComplex (FacesContext context, UIComponent component, Object valueComplex) {

     if(....)
       throw new javax.faces.validator.ValidatorException(new FacesMessage("Complex value error: ..."));

     else if (....)
       throw new javax.faces.validator.ValidatorException(fsm.StateAction.getResourceMessage("validation_complex_key", "param1", ...));


  }

}

Conversion of Fields (Input/Output)

The JSF frameworks provides default convertors using special tags (Tag Library). They can be specified for a specific form field using WebLang and the subelement statement, as presented in the following :

formLink1 (Button1){
    subelement("<f:convertDateTime pattern="dd.MM.yyyy" />") valueDate;
}

In order to handle other types of data, JSF allows the developers to define the conversion process using a Java converter class. The annotation @jsf_convertor = pack.ConverterName in front of a class statement allows the subsequent class (data) to be transparently transmitted to and from the page. The annotation value defines a converter class name with a package path. This converter class will be generated by the WebLang compiler; developers can then specify the conversion process in Java and is encourged to block the subsequent generation of this file.

form Form1 {
   PhoneNumber phone;
}
@jsf_convertor = PhoneConverter
class PhoneNumber{
  package conv;
  public String areaCode;
  public String prefix ;
  public String number
}

Branching from one FSM to the other

The figure below depicts a jump from one state machine to another one and the back jump (the dark blue elements represent states, the light blue ones execution code).

state

In order to handle the initialisation of the page to forward to in case of a redirection, the definition of a redirect state (state_r) is mandatory. A redirection happens when a request is not valid or not expected, or when an FSM jumps back to an existing FSM.

The Redirect state must be build in the following way:

case state_r:
    // some user code

    sessionState = suspendedSessionState;
    break;

It allows the program to update the input JSP to the last data (either in the State_R case or in a general switch that concentrates all initializations of the JSPs). The suspended state is saved automatically. When the state_r case is missing, the system automatically restore the suspended session state, but the user code that must process the suspended state can be jumped.

The following statements perform the jumps from one state machine to another one. The same statements may be used in the destination FSM to come back or to jump farther. If a user tries to re-enter the FSM that has performed this jumps, by using the BACK or the FORWARD button of the browser, the FSM will deviate the call to the current FSM and state, and the current page will be redisplayed. Warning: the name of the destination FSM must be exactly the same as the static variable MACHINE_ID.

    sessionState = state2;
    branchToMachine(OtherFSM.MACHINE_ID, request);

The following statements can be used to call another state machine

    sessionState = state3;
    pushAndBranch(OtherFSM.MACHINE_ID, request);

and these ones to return to the caller whowever it was:

    sessionState = state1;
    popAndBranch(request)

Internationalization (a.k.a. i18n)

The folder resources created by WebLang contains files application.properties that store properties. Attention, the folder is stored in src/resources as specified by WTP, and thus, it does not appear in the package explorer unless you have enabled the display of the empty packages.

The messages in file application.properties may be accessed by commands such as <h:outputText value="#{msg.key}"></h:outputText> to display the corresponding texts.

If the browser used to call the application is set to French, for example, the Struts package will find this indication and use file application_fr.properties, which is assumed to contain the French translations of the texts appearing on the pages, instead of the basic application.properties file.

In order to create such a file, copy the basic file to its corresponding language-specific version (_fr, _en, _de), ... and edit its content (see java.util.Locale for other extensions).

Parallel Execution

By default only one FSM instance (conceptually, because several Java instances are used) of the same type can be executed at a time. It means that the current state of a given FSM is unique (within the same Web browser). However, in order to handle specific cases, it is interesting to support the execution in parallel of several instances of the same FSM (each one with a different current state).

An FSM can be defined with the parallel_flow capabiliy as presented in the following code. In this case, it supports the creation of multiple instances of FSM; the following list gives information to use this feature.

jsf Lottery {
  statemachine torequest parallel_flow{
    ...

    flowSession.put("myForm", formLink);
  }

...
  page page {
     formLink(Action);
  }

}

  • The main JSF statemachine must be defined with parameter torequest to not share member forms of another parallel flow.
  • The branching ability of FSM is also available for parallel FSM, but all FSMs must be defined as parallel.
  • The map flowSession (HashMap<String, Object>) allows the developers to store ans retrieve forms or other information through the global flow execution. This map is unique foreach flow.
  • Another map is provided to access objects that belongs to the current FSM of the current flow: fsmInfo.getContextMap()
  • In order to initiate the execution of a new flow, the FLOW_ID parameter must be filled by the name of the flow to create or access if already exists. It can be done using the following URL link: http://localhost:8080/fsmname.do?FLOW_ID=flow2
  • The following statement allows the redirection of the view to another flow flow2 (creation or transfer if already exist) ; the url address (go.jsf) is not required to exist, the new flow will automatically redirect the view to the current state (or start page when newly created).

    saveSessionStateData(sessionState, request, flowId); // optional : useful when a new state has been defined before for the current FSM flow
    redirect("go.jsf?FLOW_ID=flow2");
    return;

Permissive History Access

By default an FSM only executes requests coming from a unique expected page, all past requests are not executed and the view is automatically redirected to the expected one. In some cases, this control is not mandatory and certains requests can be executed many times without problems.

An FSM can be defined with the history_access capabilitiy as presented in the following code. In this case, a user can post a similar request several times (using back in the Web browser) without being redirected. However, by marking a state with block_history, it is possible to block this automatic access to history pages. In the following example, as soon as the state_confirm action has been executed (the user clicks a button in the ConfirmPage), all pasts requests cannot be executed anymore.

jsf Lottery {
  statemachine history_access {
   state state_question > QuestionPage;
   state state_confirm > ConfirmPage block_history;
    state (sessionState) {

CMP Beans + Generalities on Enterprise Java Beans (EJB 2)

The EJBs include the session beans, the CMP entity beans (Container Managed Persistence entity beans) and the BMP entity beans (Bean Managed Persistence entity beans). A session bean is an object that contains operations or group of operations. It is instantiated by its client and cannot be accessed by any other client. It is used to build a façade (Design Pattern). An entity bean represents a row in a database. It can be created, deleted and retrieved like the rows of a database and shared by several clients, but under the protection of transactions.


Notes  - Here is a complete specification of the EJBs, including the query language, EJB-QL.
  - These indications apply to EJBs !
  - The BMP beans are defined, but they have not been checked carefully.
  - The preparation of the CMPs is the same as the preparation of the previous components (Compile WebLang, CGxdoclet.xml CGpackaging.xml). During the preparation, before the interfaces are created, errors may of course appear temporarily, but they should all be gone after the CGxdoclet.xml has been executed.

The follow listing shows simple CMP beans and a servlet that calls them.

cmpbean Person {
    package beans;
    relation <attends N:1 isTakenBy> Course;
    String firstName;
    String lastName;
    int age;
    Person createPerson(String firstName, String lastName, int age);
    Person findSomePerson(String aName) {
        query = "SELECT OBJECT(o) FROM Person o WHERE o.lastName=?1"
    }
    Collection findAll() {   // returns an ArrayList
        query = "SELECT OBJECT(o) FROM Person o"
    }
}
cmpbean Course {
    package beans;
    transaction Required;
    relation <isTakenBy 1:N attends> Person;
    String profName;
    String courseName;
    public Course createCourse(String profName, String courseName);
    Course findSomeCourse(String aName) {
        query = "SELECT OBJECT(o) FROM Course o WHERE o.courseName=?1"
    }
    public String aUserMethod(String s) {
        return s.toUpperCase();
    }
}
servlet MyServlet {
    package servlets;
    public void calledByHtmlPage (PrintWriter out, String firstName, String lastName, int age)
    throws Exception {
        Course aCourse;
        aCourse = courseHome.createCourse("Einstein", "Physics");
        Person aPers;
        try{
            aPers = personHome.findSomePerson(lastName);
        } catch (javax.ejb.FinderException fe) {
            aPers = personHome.createPerson(firstName, lastName, age);
        }
        aCourse.addIsTakenBy(aPers); // add person to the physics course (constant here!)
    }
}

The first lines of a cmpbean module must contain a package statement, 0-n import statements, 0-1 transaction statement and 0 or N relation statement. After these statements, a CMP entity bean may contain attributes, createXXX() methods, findXXX() methods and simple methods.

In the source code of the servlet shown above, a Course bean is created. Then aPers receives the object proxy returned by a find or a create method. If the bean is already available in the database, the finder will succeed. Otherwise, it will throw a javax.ejb.FinderException and the catch block will create the person.

In order to delete an object from the database, use its remove method (either the one that is defined in the bean, or the one that is defined in a 1:1 relationship). In order to delete a collection, set it to the empty collection. If you want your deletes to be cascaded, you must introduce the corresponding xdoclet in the beans (See @ejb.relation in the xdoclet documentation).

Each CMP entity beans has a method getPrimaryKey(), (which returns a Long if it has been produced by WebLang).

A type followed by a name forms an attribute (more below) and is stored in the database automatically. Attributes are initialized with the values passed in the arguments of the creators (more below).

Attributes

An attribute is defined by a type followed by a name . It is automatically given a column in the database and is provided with the abstract getters/setters required by the JEE servers. The following types are handled: String, boolean, char, byte, short, int, long, float, double, java.util.Date.
 
If the attribute has a initialization -- int i=0; -- however, then it is considered as a simple attribute

Simple methods

Methods that do not begin with a find or create are simply introduced in the bean. They can be called in the proxy object that is returned from a creator or a finder, provided they are preceded by keyword public. If they are not public or are private, they may be called only locally.

The type of a local variable that corresponds to a bean, such as Person, is automatically converted to package.PersonLocal.

The symbol made of a bean name, with a lower case as the first character and followed by Home, such as personHome, is transformed into a call to beans.PersonUtil.getLocalHome(), a method that returns an object of class PersonLocalHome, and its package is added to the import list if needed. This method is generated by the xDoclet compiler in PersonUtil.java.

Pay attention:, the name of the module from which the symbol xxxxHome is derived must start with an upper case letter (Xxxx -> xxxxHome).

Removers

Removing an object from the database is done simply by executing cmpBean.remove(), where cmpBean is the object obtained from the cmpBeanHome object.

In order to remove an object from the collection of a relationship, one uses xxxx.getYyyy().removeYyyy(yyyy); (see below).

Creators

A creator method must have the name of the cmpbean as return type and it must begin with the letters "create". The parameters of the createXX methods must correspond to a subset of the attributes, placed in any order. WebLang introduces the code that initializes the attributes with the values entered in the parentheses. The create method is terminated by a semicolon. There are no private creators and the keyword public is introduced automatically if it is not found in the source code.

Relationships

The relationship statements are placed after the package statement, before the attributes. They can provide the two directions of the relationship with names. Several relationship statements can be defined in the same bean.

relation <hasBills 1:N attributedTo> Bill;
relation <livesAt 1:1 ofPerson> Address;

Note - The notation 1:N follows the JEE use, not the databases'. In an entity-relationship model, one would rather write:   0..1 ------- 0..N

Here are the possibilities of indications (we assume the statement is placed in a bean of name Xxxx). The indication target indicates that the primary key handling the relationship is located at the other end of the relationship. Thus, if a relation is 1:1 target in a bean and 1:1 in the corresponding one, a single column will be created to handle the relationship. On the other hand, if either 1:1 target or 1:1 is placed at both ends, there will be two primary keys, but the server updates both keys automatically.

Here is the summary of the possible combinations of the two ends of a relationship and the methods created :
 

indication in Xxxx in Yyyy methods available in Xxxx

<hasY 1:1 hasX> Yyyy

no relation

<hasX 1:1 hasY> Xxxx
<hasX 1:1 target hasY> Xxxx

Yyyy getHasY()
setHasY(yyyy)
(setHasY is made from the forward relationship
  name with first letter capitalized
)

<hasY 1:1 target hasX> Yyyy

no relation

<hasX 1:1 hasY> Xxxx

Yyyy getHasY()
setHasY(yyyy)

<hasY 1:N hasX> Yyyy

no relation

<hasX N:1 hasY> Xxxx

java.util.Collection y = getHasY()
setHasY(collection)
addHasY(yyyy), removeHasY(yyyy)

<hasY N:M hasX> Yyyy

no relation

<hasX N:M hasY> Xxxx

java.util.Collection y = getHasY()
setHasY(collection)
addHasY(yyyy), removeHasY(yyyy)

The methods defined in the third column above may be used to attach the objects embedded in a relationship.

A collection expressed by a relationship (1:N or N:M), returned by a call to bean.getXxxN() cannot be managed in a servlet. It must be handled within a transaction, for example within a session bean, which, by default, is executed within a transaction. On the other hand, the collections returned from the finders are not submitted to this constraints.

The following example shows how to introduce a bean in a relationship (this code could be placed in a servlet or a session bean for example):

    Person aPers;
    aPers = personHome.createPerson(firstName, lastName, age);
    Course aCourse;
    aCourse = courseHome.findByName(courseName);
    // first possibility (Course has a 1:N relation, isTakenBy with Person)
    aCourse.addIsTakenBy(aPers);
    // other possibility (if Person has a 1:1 relation attends with Course)
    aPers.setAttends(aCourse);

If the relation is bidirectional, the element may be introduced at either end and it can be retrieved from the other end (although possibly not within the same transaction).

Finders

(See Book, Paragraph 8.4.12, and Sun's specification, Chapter 11, to get more indications on the EJB query). You can find examples on Internet with strings such as "EJBQL LIKE CONCAT".

Here is a typical finder with a simple query, which may be introduced in a CMP bean of name Person.

public Person findSomePerson(String aName) {
    query = "SELECT OBJECT(o) FROM Person o WHERE o.lastNameWHERE cx.cc LIKE CONCAT(?1, '%')"
}

The SELECT OBJECT(o) refers to the returned object. The FROM clause (detailed below) specifies the tables on which the query is performed and the WHERE part contains the usual boolean expressions, with comparison among the fields of the database and the external variables passed as arguments.

The name after the FROM keyword must correspond to the class name.

The above example shows how to perform pattern matching. CONCAT returns a string and can thus be nested within other CONCAT. ?1 refers the first parameters. % means any string and _ would mean any character.

Remember that the parameters of the finder methods must be typed with a simple type (int), a type that belongs to package java.lang (Integer, String, ..) or a fully qualified name.

In the following, we show a number of typical queries that may appear in finders. These examples are a bit extrem in order to highlight the concept. The first line of each example describes the beans and the relationships on which the query applies.

The following lists represent cascades of tables related by 1:1 and 1:N relationships. Note that an N:M relationship is handled in the same way as two 1:N relationships.

A  --1:1--  B  --1:1--  C  --1:1--  D  -  dd

  // must be defined within an A
  public A findA(String name) {
      query = "SELECT OBJECT (o) FROM A o WHERE o.a2b.b2c.c2d.dd=?1"
  }

   (Source code: queries11.cg)

An  --1:N--  Bn  --1:N--  Cn  --1:N--  Dn  -  dd

  // must be defined within bean An
  public java.util.Collection findA(String name) {

        query = "SELECT OBJECT (o) FROM An o, IN(o.an2bn) bx, IN(bx.bn2cn) cx, IN(cx.cn2dn) dx WHERE dx.dd=?1"
  }

   (Source code: queriesNM.cg)

A  --1:1--  B  --1:N--  Cn  --1:1--  D  -  dd

  // must be defined within bean B as it returns a B or a collection thereof
  public java.util.Collection findB(String s) {
      query = "SELECT OBJECT (o) FROM A o, IN(o.a2b.b2cn) cx WHERE cx.cn2d.dd=?1"
  }

(returning a component or a collection different from the FROM table)

  // must be defined within bean B as it returns a B or a collection thereof
  public java.util.Collection findB(String s) {
      query = "SELECT o.a2b FROM A o, IN(o.a2b.b2cn) cx WHERE cx.cc=?1"
  }

(returning a collection defined in an IN clause)

  // must be defined within bean Cn as it returns a collection of Cn
  public java.util.Collection findCn(String s) {
      query = "SELECT OBJECT(cx) FROM A o, IN(o.a2b.b2cn) cx WHERE o.aa=?1" 
  }

   (Source code: queriesNM11.cg)

Note that it would be more rational to use the primary key wherever possible. It is accessible simply as pk in EJB-QL and as object.getPk() in the Java code.

Not found

If a finder does not find the requested element, it throws an exception:

        try{
            aPers = personHome.findSomePerson(lastName);
        } catch (javax.ejb.FinderException fe) {
            aPers = personHome.createPerson(firstName, lastName, age);
        }

Selectors

EJB-QL also has statements that return single values, not components. These statements must be stored in select methods, as shown below. Note that unlike finder methods, which are reachable from the home proxies, the select methods are stored in the objects (Actually, this option often raises problems for the developer). The following select method returns the maximal primary key of objects Draw. For details of the EJB-QL, see the SUN documents mentioned at the beginning of this section (click this paragraph's title!).

cmpbean Draw {
    package myejb;
    int value;
    public Draw create(int value);
    public Long selectMaxPK() {
        query = "SELECT MAX(o.pk) FROM Draw o"
    }
}

Here is a program that calls the previous method:

Draw dr = drawHome.create(token.getValue());
Long max = dr.selectMaxPK();
out.println("The highest primary key is " + max);

Transactions

The CMP beans are automatically executed within a transaction, but this transaction may have several forms, the following ones being the most important ones (check the JEE literature for more details): Required, RequiresNew, or Mandatory. Any other symbol understood by the CMP bean is also accepted (Note that bean managed transactions are not available for entity beans).

Transaction type
defined in B
Transaction available
before entering B
Transaction created
by the container for B
Required available
none
keeps it
starts a new one
RequiresNew available
none
starts a new one
starts a new one
Mandatory available
none
keeps it
error
       
     

An example is available at the end of the next section.

Session Beans

Unlike the entity beans, the session beans have no relation to the database. Each session bean is bound to one client and it disappears with the latter. They define creators, but no finders, because the latter would allow a client to grasp the session bean of another client. A session bean allows a client to group several operations inside a single object located in the server, possibly executed within one or more (user or bean managed) transactions. For example, session beans may be used to handle the collections defined by the relations, which must be performed within transactions.

The following listing defines a session bean.

Only the public methods are decorated with XDoclets making them reachable from other beans. Private methods or methods without defined property can only be reached from inside.

sbean ControllerSession {
    package beans;
    public ControllerSession create ();
    public void newPerson (String firstName, String lastName, int age) {
        Person aPers;
        try{
            aPers = personHome.createPerson(firstName, lastName, age);
        } catch (Exception e) { e.printStackTrace(); } 
    }
}

By default a session bean is stateless and thus cannot have attributes. A simple create method, as shown below, is mandatory for stateless session beans. If attribute are required, one should use a stateful bean, specified by the statement appearing in the following bean. A stateful bean may have different create methods. The latter must all begin with the prefix create.

Transactions

The transaction statement may contain Bean, in which case the transaction can be handled by the developer as shown below. beanContext is a variable that is initialized by JBoss, when it calls (automatically) setSession Context() defined in the bean class:

sbean ControllerSession {
    package beans;
    state stateful;
    transaction Bean;
    String st;
    public ControllerSession createXX (String st);
    public void newPerson (String firstName, String lastName, int age) throws Exception {
        Person aPers;
        beanContext.getUserTransaction().begin();
        aPers = personHome.createPerson(firstName, lastName, age);
        beanContext.getUserTransaction().commit();    // or rollback() 
    }
}

If no transaction is indicated, or if it is specified as Required, RequiresNew, Mandatory or another valid symbol, the compiler implements the Required container managed transaction (CMT) or the specified CMT.

Session Bean Called by a Timer

It is possible to start an action defined in a stateless session bean from a timer. In the following example, a servlet contains the commands used to start a timer that executes a session bean. The timer can be cancelled from the same servlet.

Note - After generation, or with the help of a new template, the session bean, TimerBeanBean must be completed with ", javax.ejb.TimedObject" after its implements statement.

servlet X {
      package xx;
      public void startTimer (int nextTime) throws Exception {
        TimerBean tb = timerBeanHome.create();
        tb.startMyTimer(nextTime);
      }
      public void cancelTimer () throws Exception {
            TimerBean tb = timerBeanHome.create();
        tb.cancelTimer();
      }
}
// The following line can be used to handle the template that introduces the implements
//@templatedir=timer
sbean TimerBean {
      package xx;
      public TimerBean create();
      private static javax.ejb.Timer timer = null;
 
      public void startMyTimer(long nextTime) {
        javax.ejb.TimerService timerService = beanContext.getTimerService();
        timer = timerService.createTimer(nextTime, "timer " + nextTime);
      }
      public void ejbTimeout(javax.ejb.Timer timer) {    // method called by the timer
            System.out.println(timer.getInfo());
      }
      public void cancelTimer () throws Exception {
            timer.cancel();
      }
}

Complete CMP Example

This complete example contains a servlet, a few CMP beans and a session bean. It exercises most methods available in the CMP beans and presents the expected and the actual results. You can compile, xdocletize, package this file and call the various functions of the servlet from the HTML file stored within the README directory.

Here is an example with a transaction in a session bean. Remember that if you load different projects in JBoss, which may use the same symbols you must stop JBoss, erase the .ear files in C:/jboss-4.0.0/server/standard/deploy and the databases in C:/jboss-4.0.0/server/standard/data/hypersonic and restart JBoss.

Java Client

The clients run like usual Java programs. They are started with the Run command. They may handle EJBs and channels, in which case they automatically use the remote accesses and require C:\jboss-4.2.3.GA\client\jbossall-client.jar in their project — or the equivalent if you have loaded your server directories elsewhere. The channels offer a means to receive messages unexpectedly.

The following listing defines a Java client with two public functions that call CMP beans and a session bean.

jclient ClExample {
    package clientPack;
    void method1 (String s) throws Exception {
        Person aPers;
        Course aCourse;
        aPers = personHome.createPerson("a", s+"pers", 12);
        aCourse = courseHome.createCourse("prof", s+"course"); 
    }
    public void method2 () throws Exception {
        ControllerSession ctrl = controllerSessionHome.create();
        ctrl.newPerson("Vasco", "da Gamma", 100);
        print("Some Text\n");
    } 
}

Method print(. . .) writes the argument on the text area of the client's window.

The modules jclient may contain private and public methods. The WebLang compiler creates GUI for the public methods, in the same way as servlets are handled. In this GUI, there is a tab for each method as well as a text area that can be accessed with the help of the function print, println or getJTextArea(). The GUI is not generated if the jclient does not define any method.

Complete example in which a client uses CMP beans.

Channels (queues, topics, durable topics)

Warning - We have not taken much time, yet, to assure that the attributes that point to the auxiliary objects (context, factories) are saved correctly in all components and in all situations. In a commercial application the handling of these attributes should be checked carefully. Maybe, in CMP beans the activate and passivate methodes should be called to the rescue.

JBoss can maintain FIFO buffers into which programs can send messages and out of which other programs can extract the messages. These lists form FIFO channels that are handled according to the various rules described below. Input channels may only be introduced into Java clients, Java classes and MDBeans. Output channels may be introduced in most modules (see below). They are part of the JMS (Java messaging system).

Java clients (above) and Java classes (above) can handle several input and output channels. The input channels can be read by listeners or by synchronous calls (the source code corresponding to the synchronous calls are integrated under the form of comments in the files generated by WebLang). The names of the input channels used in the Java clients are used as the names of the object from which the messages can be read synchronously.

rmi modules (below) can handle several output channels.

MDBeans (below) accept several output channels, but only one input channel. An MDBean is intimately bound to its input channel. It cannot access it in a synchronous manner and the channel is created when the MDBean is created.

Servlets and Struts may contain only output channels (because their entry points correspond to the call to their doGet method.

Session beans and entity beans may contain only output channels for the same reasons.


Note: The inputchannel and the outputchannel must be defined just after the package statement, first the input and then the output channels.

The figure below shows an application containing two clients that write into a topic attached to an MDBean (see below) and that wait data from the topic testTopic available by default in JBoss. The MDBean forwards the messages it receives to testTopic. In the client, the input and the output channels are independent and the code that manages them even run on different threads. You can start the same client several times on several computer (provided you have specified the server's address in the CodeGen Preferences or let the compiler choose the current local host.


Types of Channels

queue Each message is consumed once it has been read by one client. Of all connected clients, only one receives the next message.
topic Each client that is connected to such a channel receives all messages. The messages are thus multicast to the clients.
durabletopic   All clients that have connected once to such a channel receive all messages, even the clients that have been down for a while. Durable topics are meaningful only for subscribers, not for publishers. The username and the password are hard-coded in the Java source code, we leave it to you to modify it.
Creation of Channels in JBoss

The management of channels has changed since JBoss version 5, JBoss Messaging is now used in place of JBoss MQ (old information for JBoss 4 is available here).

In JBoss 5, the channels must be created directly in file deploy/messaging/destinations-service.xml as presented in the following. Warning, it is also required to create manually the input channels used by a MD Bean.

<mbean code="org.jboss.jms.server.destination.QueueService"
    name="jboss.messaging.destination:service=Queue,name=queue1"
    xmbean-dd="xmdesc/Queue-xmbean.xml">
  <depends optional-attribute-name="ServerPeer">jboss.messaging:service=ServerPeer</depends>
  <depends>jboss.messaging:service=PostOffice</depends>
</mbean>

<mbean code = "org.jboss.jms.server.destination.TopicService"
    name = "jboss.messaging.destination:service=Topic,name=topic1"
    xmbean-dd = "xmdesc/Topic-xmbean.xml">
  <depends optional-attribute-name="ServerPeer">jboss.messaging:service=ServerPeer</depends>
  <depends>jboss.messaging:service=PostOffice</depends>
</mbean>

The file destinations-service.xml defines queues A,B,C,D, testQueue, generalQueue, and topic testTopic.
Input from channels

Here is the inputchannel statement. It can be used with either a queue or a topic

inputchannel varName (queue, QueueName) {
    String s = ((javax.jms.TextMessage)msg).getText(); // get the incoming message
    application code
}

The compiler introduces the code defined in the body of the channel shown above into a channel listener, as indicated in the following. onMessage is called each time a msg arrives. Look at the Javadoc to see which kinds of messages may be transmitted through the channels.
An input channel may also be read by means of a blocking read, but only in a client (sees the code commented out in a jclient produced by WebLang).

public void onMessage(java.jms.Message msg) { // generated by WebLang
    try {
        application code written by the developer in the input channel
    } catch (Exception e) {
        System.out.println("Error in class " + getClass().getName() + "\n"+e.getMessage());
    }
}

The other possible channels are listed below (with a typical statement found in the listener):

inputchannel varName (topic, TopicName) {
    String s = ((javax.jms.TextMessage)msg).getText(); // get the incoming message
}

inputchannel varName (durabletopic, DurTopicName, subscription) {
    String s = ((javax.jms.TextMessage)msg).getText(); // get the incoming message
}

Output into channels

outputchannel varName (queue, QueueName);

outputchannel varName (topic, TopicName);

Here is an example of the use of a queue in some module, which shows how to send data to a queue:

outputchannel myqueueObj (queue, myqueue);

public void myMethod() {
    try {
        javax.jms.TextMessage tm = queueSession.createTextMessage("x");
        myqueueObj.send(tm);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Here is an example of the use of a topic, which shows how to forward data to a topic:

outputchannel mytopicObj (topic, mytopic);

public void myMethod() {
    try {
        javax.jms.TextMessage tm = topicSession.createTextMessage("x");
        mytopicObj.publish(tm);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Reply queues

It is possible to pass the identifier of the queue (topic) on which the client expects the answer to come back within the forward message in the following way:

javax.jms.Queue replyQueue;
TextMessage tm = queueSession.createTextMessage(x);
tm.setJMSReplyTo(replyQueue);
sender.send(tm);

Other examples of the use of the queues and the topics in Java client are available at the end of the next section.

Message Drive Beans
Book, Section 8.7
Syntax
Transactions (below)
Example (below)

Input channels cannot be integrated into entity nor into session beans. On the server side, they require the use of so-called MDBeans. The queues and topics used as input by the MDBeans (one per MDBeans) are defined by WebLang code similar to the following example:

mdbean MyMDB {
  package myPackage;
  inputchannel (queue, MDBQueueName) {
    String s = ((javax.jms.TextMessage)msg).getText(); // get the incoming message
 }
}

The MDB is bound to a queue (or a topic) with the name defined in the channel. The inputchannel specifies the kind of channel that must be used: queue or topic, corresponding to the categories described above. Warning since JBoss version 5, the input channels that belongs to a MD Bean must be created manually as presented here.

Here is an example of an MDB that uses topics and that forwards the incoming message to an output channel:

mdbean MyMDB{
  package myPackage;
  inputchannel (topic, MDBTopicName) {
    String s = ((javax.jms.TextMessage)msg).getText(); // get the incoming message
    try {
      javax.jms.TextMessage tm = topicSession.createTextMessage(s);
        chanReference.publish(tm);
    } catch (Exception e) {
       e.printStackTrace();
    }
  }
  outputchannel chanReference (topic, TopicName);
}

Transactions

Transactions can be defined in MDBeans as shown below. The transaction parameter is either "Bean" or "Required". The first one allows the developer to enter the statements beanContext.getUserTransaction().begin() and beanContext.getUserTransaction().commit(). The second one executes the MDBean within a container managed transaction (CMT). Note that the message is returned to the queue, in case of roll back, only with container managed transaction.

mdbean MyMDB {
  package myPackage;
  transaction Required;
  inputchannel (topic, MDBTopicName) {
    String s = ((javax.jms.TextMessage)msg).getText(); // get the incoming message
  }
}


Examples

Here is a complete example of queue handling. In order to start these programs, you must compile the WebLang source, execute CGxdoclet.xml, CGpackaging.xml, run file inclient.java, then outclient.cg and finally type in lines of text as indicated in the console windows.

Singleton MDB

Several instances of an MD bean may run in parallel. If one wants to serialize the execution of the messages coming from the queue, the following command (fourth line) must be entered in the definition of the MD bean, in build/classes/META-INF/jboss.xml:

<message-driven>
    <ejb-name>myqueue_mdpack_MyqueueBean</ejb-name>
    <destination-jndi-name>queue/myqueue</destination-jndi-name>
    <configuration-name>Singleton Message Driven Bean</configuration-name>
 

Web Services

A Web service allows an application located in a Java client, in the same server as the service or in another server, to call a method that offers a service through an HTTP channel. The WebLang Web service component creates the files that can be deployed in a server within a .war file, possibly with other components in the same .cg file.

The module has the same form as a class. It contains methods and possibly attributes.

The methods of the services can have classes as arguments and return (but currently not the client). These classes must have a constructor without parameters or the default constructor, and accessors (getter/setter). The types of the arguments and the return must be fully qualified (package+class name).

webservice YourWsName {
    package pck;

    public int addValues(int value1, int value2) {
        return (value1 + value2);
    }
}


Preparing a Web Service

The WebLang compiler creates a folder data/web/merge with files that must be included by the xDoclet compiler in other files (as these files are incomplete, Eclipse displays errors, don't bother), a file named server-config.wsdd, containing the description of the interface, as well as a few other auxiliary files. After you have compiled the Web service component, you must execute the CGxdoclet.xml script Then execute the CGpackaging.xml script (or select Run As > Run On Server). Among the files created by CGxdoclet.xml you will find a file WebContent/wsdl/xxx.wsdl, which is uploaded by CGpackaging.xml and may thus be accessed through http://localhost:8080/ProjectName/wsdl/YourWsName.wsdl. This file is the public description of the service. It can be used by another developer to create a call to the service from any other system.

Then, in order to call the service, you may either create a client or call it with the help of the Web Service Explorer
 

Packaging info : the Webservice classes (interface, stub and service) are packaged into the war library. Therefore by default the JBoss EAR environment cannot access the Webservice classes, please redefine the packaging build script if necessary.

Note : if you use a standard Java Project, make sure that the libraries axis.jar, axis-ant.jar, commons-discovery-0.2.jar of WebContent/WEB-INF/lib are added to the build path of the project, before compiling the .cg file and executing the xdoclet script. Otherwise, store the Web project in a Dynamic Web Project, because with such projects, the axis libraries are copied into WebContent/WEB_INF/lib and are automatically included in the project references in the Web App Libraries.

Note : The first compilation creates CGxdoclet.xml before the classpath is initialized, and the XDoclet script misses an axis library. It then returns an error: Could not load definitions from resource axis-tasks.properties. It could not be found.
At the second compilation of the .cg file, the libraries contained in WebContent/WEB_INF/lib will have been added.

Client of a Web Service

The Web service can be called from anywhere (servlet, Java program, ...). In this case however, the WebLang environment cannot handle classes in the arguments and the return of the methods, only simple types (int String...). Here are the statements to do it.

        IYourWsName yourWsName = YourWsNameStub.get();
        int x = yourWsName.addValues(35, 22);
Web Service Explorer

The WTP offers a tool that you can use to debug the Web service:
 
        right-click the .wsdl file created by the docklet (in WebContent/wsdl) > Web Services > Test with Web Service Explorer

Select your operations in the navigator window that appears. This creates a form that can be used to call the service. Each time you restart your service, a new block is loaded in the navigator view, but you can clear them by clicking the rubber located at the top right of that view.

You can also access the service by entering the URL of the wsdl file, given above, in the Web Service Explorer. In order to enter the WS explorer open a dummy.wsdl file, or click the icon    in the top menu.

General documentation about the Web Services
Generic Module

The templates of every module can be extended in a simple way. The templates receive all the information entered in the modules, in a form that depends on the module. The developer can use extra parameters that are not integrated in the predefined syntax, thanks to the statement parameters defined below.

Actually, it is sometimes necessary to create files that resembles no other module. In that case you can use the generic module. It makes it possible to create new files almost from scratch. The following module will create a file named ModuleA.java that will be located in package gen of the src directory (after it has been compiled and the usual compiler is called):

@templatedir = xxx
generic
MyGeneric {
      package src.gen;
      filename ModuleA.java ;
      parameters {
            xx = www "q q q";
            yy = a;
      }
}

In order to use this module, you must write an annotation before the module, right-click the .cg file and select menu Get Templates, which copies a blank JET templates into your project (folder _cg_resources). You can modify it there, compile it by right-clicking the .cg file again and selecting Compile CG Template. Note that the compilation takes place in a project .CG_JETEmitters (usually invisible, because it starts with a dot), where you can find the classes produced by the Jet compiler. At the next usual compilation, the template will be generated according to your template.

If you add the following lines at the end of the template retrieved as described above...

package gen;
public class ModuleA { // example
      String <%=parameters.getValue("xx").get(0)%> () {
            return "<%=parameters.getValue("xx").get(1)%>";
      }
}

... you will get the following program. Exactly like in a JSP, the simple text is copied to the generated file without any modification, the strings defined between <%= %> are computed and inserted into the output file and the statements between <% %> are simply executed.

package gen;
public class ModuleA {  // example
    String ww () {
      return "q q q";
    }
}

Additional details are available on this page

JPA (EJB 3) - Hibernate
Hibernate home
Documentation
Another document
D. Panda, R. Rahman, D. Lane, EJB3 in Action, Manning
 
Syntax
Transaction modes: JTA or Resource_local (below)
Example of an JPA's definition (for both modes) (below)
Use of an JPA in JTA mode (below)
Use of an JPA in stand-alone mode (below)
Deletion of objects (below)
Relationships (below)
More on the transactions (below)
Single user transactions (below)
Concurrent transactions (below)
A class used both as an action form and an EJB (below)

Examples:
Lottery 4 (Struts/JPA/EJB3)- Lottery 5 (JSF/JPA/EJB3) - Lottery 6 (JSF/JPA)
 
Introduction

The extension of Java 5 has introduced the concept of annotations. Annotations may play the same role as the xDoclets, but with the advantage that the information they generate is available in the binaries of the classes rather than in the source files only. This feature has been used to create a new version of persistent EJB, called JPA, which is much lighter than the CMP beans described above.

Programs have more control on the synchronization of these beans with respect to the database than the EJB2, namely the developer can decide when a bean is attached to the database, when it is updated against the database and when it is refreshed by the current content of the database. An object is normal until it has been attached to the database, within a transaction. From the moment a bean is attached, the manager handling the transaction registers all the modifications requested by the user and introduces them into the database at the latest when the transaction is closed.

These beans may be executed in a JBoss server, in a client or a in a Tomcat server. In JBoss, they can either use the JTA (Java transaction API) entity manager (namely within session beans 3) or the RESOURCE_LOCAL entity manager. The clients and the Tomcat applications can only use the latter, of course.

One of the advantages we have found to this approach is the possibility to have the same class to be used simultaneously as an action form and a JPA. WebLang has thus provision to define a class with the two aspects.

The documentation below is both a summary of and a complement to this documentation.

Transaction modes: JTA or Resource_local

Using global annotations, it is possible to configure the database and the transaction mode, because the handling of all objects must occur within transactions, which can be managed either implicitly within session beans or explicitly. The first transaction mode, called CMT (container managed transaction) or JTA follows the JEE specifications. The code handling the transactions is injected into the methods of a session bean 3. They are thus automatically started at the entry into the methods and terminated at their exit.

The second mode stand-alone or local can be used in JBoss, in POJO (plain old Java objects), as well as in Tomcat or in clients. In this mode, the libraries contain all the code that handles the link to the database. A different entity manager is used.

The properties defined in the following are used to set up the src/META_INF/persistence.xml file. In particular, the local manager is created by the factory when the RESOURCE_LOCAL parameter is set in the persistence.xml file.

By default, the active mode is JTA, and the database is HSQL; the following configuration is equivalent to default :

@@db_datasource="java:/DefaultDS"
@@db_dialect="org.hibernate.dialect.HSQLDialect"
@@db_dbauto="update"

The values possible for @@db_dbauto are (validate | update | create | create-drop), which validates the schema of the database, updates the tables if the column names have changed, creates them if they do not already exist or creates and drop them whenever a new application is loaded.

If you have enabled MySQL, you must insert the following lines:

@@db_dialect="org.hibernate.dialect.MySQLDialect"

In order to set the JBoss transaction mode as RESOURCE_LOCAL, the following global annotation is provided. This mode is mandatory when for instance a Servlet (Web layer)directly stores or retrieves a JPA; it is usually recommended to go through SBean3 or MDBean3.

@@resource_local


In order to support Tomcat server or other client applications that are not based on JTA, the persistence properties can be set as follows respectively for HSQL and MySQL databases.

@@standalone

 

// HSQL

@@db_dialect="org.hibernate.dialect.HSQLDialect"

@@db_driver="org.hsqldb.jdbcDriver"
@@db_address="jdbc:hsqldb:hsql://localhost:1701"
@@db_username="sa"
@@db_password=""

 

// MYSQL

@@db_dialect="org.hibernate.dialect.MySQLDialect"
@@db_driver="com.mysql.jdbc.Driver"
@@db_username="root"
@@db_password=""
@@db_address="jdbc:mysql://localhost:3306/DB_NAME"

Note that the global annotation @@standalone (equivalent to @@tomcat) is mandatory and ignore the definition of datasources. This annotation also changes the packaging script so that only a WAR archive will be generated, and the management of the libraries are also different, since Tomcat does not include by default technologies like JSF and JPA (EJB3).
Example of an JPA's definition (for both modes)

jpa Country {
      package myPackage;
      relation <hasTowns 1:N isIn> Town;
      String name;
      int number;
      public Country create(String name, int number);
      public Country find(String name) {
            query = "select c from Country c where c.name=?"
      }
}

JPA have no home objects, they are instantiated by the new statement and then explicitly persisted into the database. WebLang generates static methods for creating and finding JPA. The calls to these methods are thus preceded by the name of their class:

x = Country.create("xxx", 123);
The create methods must start with the letters "create". They perform a new, followed by em.persist(). They expect the entity manager to be present in the thread as explained in the next paragraphs. The parameters of the finders are set in the query at the places of the "?", in the order of the arguments of the method. Documentation about the queries may be found on this page (see chapter 14).

The @cascade statement introduces the corresponding annotations into the code. It indicates to the manager that the members of the relationship must be persisted, merged and so on depending on the specification.


jpa Country {
      package myPackage;
      @cascade = "CascadeType.PERSIST"
      relation <hasTowns 1:N isIn> Town;
      String name;
}

The possible values are : CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH and CascadeType.ALL. Two or more indications can be introduced:

@cascade = "CascadeType.PERSIST, CascadeType.REMOVE"

Important - Do not introduce cascading on both side of 1:N or a N:M relationship, as the manager may loop over the cascades!

Use of a JPA in JTA mode

In these first explanations, we assume that the objects are only accessed by one user at a time. The management of concurrent accesses is presented below.

sbean3 Facade {
      package web;
      transaction REQUIRED;
      public void newCountry (String name, int nb,
                              java.io.PrintWriter out) throws Exception{
            try {
                  ejb3_utility.Manager.set(em); // required by the finder to get the entity manager
                  try {
                        Country.find(name);
                  } catch (javax.ejb.FinderException fe) {
                        Country.create(name, nb);
                  }
                  out.println("<br><a href=\"TestA.html\">Next</a><br>");
            } catch (Exception e) {
                  out.println(e.getClass().getName()+" - "+e.getMessage());
            } finally {
                  out.println("creatCountry terminated");
            }
      }
}

The annotations inject the calls that handle the transactions (begin, commit...) around the method code. If you want to cancel a transaction, you must introduce the following statement: em.getTransaction().setRollbackOnly() (em is an attribute of the session bean).

The finders generated by WebLang access the entity manager by getting it from a ThreadLocal object (see generated class ejb3_utility.Manager). It is thus set by the first statement after the try statement in the code above. This statement needs only be written once per transaction.

Use of a JPA in stand-alone mode

In the stand-alone mode, all operations related to the transaction manager are visible, which actually shows its functioning. The JPA objects use a so-called entity manager, which must be opened and stored within a ThreadLocal object (See ejb3_utility.Manager.java in your project). This manager can return a transaction and is used by the finders, the creators as well as to connect the beans to the database. After a bean has been connected, the database is automatically updated whenever the bean is modified. On the other hand, when the database changes, the bean must still be explicitly refreshed to receive the updates.

We also assume here that the objects are only accessed by one user at a time, and we postpone the discussion on the management of concurrent accesses to this section (see also).

Here is the creation of a manager and a transaction, and the statements that manage a bean. The transaction is opened to handle the operation requested by a user from a servlet or a Struts. Note that it should not be kept open from one execution of a servlet to the next execution.

javax.persistence.EntityManager em = ejb3_utility.Manager.open();
javax.persistence.EntityTransaction tx = em.getTransaction();
tx.begin();
 
    Country entity = new Country();
    // set arguments of entity
 
    em.persist(entity);
 
    Country c = Country.create("Switzerland", 1291); // includes the code above
    c.setNumber(1291);   // will appear in the DB at the latest at the commit
 
 
. . . Other calls to the EJBs in the same transaction . . .
 
tx.commit();     // or tx.rollback();
ejb3_utility.Manager.close();

In the code above, an object is instantiated and then persisted. The latter operation introduces a reference in the entity manager, which will remember all subsequent modifications that it will undergo and forward them to the database when the transaction is committed. WebLang can generate creators and finders from definitions similar to the ones that are shown in the example.

In the code below, if an object is found, it is automatically linked to the manager. If the object is not found, a new object is created and persisted, in such a way that after the try-and-catch block, an attached object is available in all cases..

try {
    country = Country.find("Switzerland");
} catch (javax.ejb.FinderException fe) {
    country = Country.create("Switzerland");
}

The creators generated by WebLang include a new and a persist statement. The finders include the following code, which converts the exception to have the same exception as the one found in the CMP beans.

Query query = em.createQuery("select c from Country c where c.name=?");
query.setParameter(1,name);
try {
  Country result = (Country)query.getSingleResult();
  return (result);
} catch (javax.persistence.NoResultException enf) {
  throw new javax.ejb.FinderException(enf.getMessage());
}

Deletion of an object

The following line removes the data corresponding to an object from the database

em.remove(country);

Relationships

The available relationships are listed in the table below (same as the EJBs'):

indication in Xxxx in Yyyy methods available in Xxxx
<hasY 1:1 hasX> Yyyy no relation
<hasX 1:1 hasY> Xxxx
<hasX 1:1 target hasY> Xxxx
Yyyy getHasY()
setHasY(yyyy)
(setHasY is made from the forward relationship
  name with first letter capitalized
)
<hasY 1:1 target hasX> Yyyy no relation
<hasX 1:1 hasY> Xxxx
Yyyy getHasY()
setHasY(yyyy)
<hasY 1:N hasX> Yyyy no relation
<hasX N:1 hasY>
java.util.Collection y = getHasY()
setHasY(collection)
addHasY(yyyy), removeHasY(yyyy)
<hasY N:M hasX> Yyyy no relation
<hasX N:M hasY> Xxxx
java.util.Collection y = getHasY()
setHasY(collection)
addHasY(yyyy), removeHasY(yyyy)

The relationships between the objects, as implemented by WebLang, connect the other end of the relationships. Thus, the two statements below are equivalent.

country.addTown(town);
town.setCountry(country);
tx.commit();     // the database is up-to-date

More on the transactions

The transactions have three roles:

  • gathering a set of actions and have either all of them or none executed
  • isolating the actions of a user from the actions of another user, when they access the same records
  • and, specific to the JPA, maintain the list of modifications before they are transferred to the database

For a complete picture, we must also consider two levels of transactions:

  • the sequence of operations made by a user to perform a specific "long" task (minutes); for example choose several items and save them into a basket to purchase goods on-line
  • the one that wraps the operations started from a single HTML page or servlet; for example introduce the next item into the basket
Single user transactions

Let us temporarily put the isolation of the users aside and focus our attention to the re-insertion of objects persisted within a transaction into a new one (merging). The following table contains a "long" sequence of "short" transactions, corresponding to the two levels described above.

User Server User Server
Servlet
session
new data begin/persist/commit modify data begin/merge/commit
DB data stored data updated

In this sequence, a user enters data that are stored in a new object (for example by means of a Struts). The object is then persisted to transfer its content to the database. The object remains in the servlet session, but a transaction cannot be left open from one user intervention to the next one, because that would degrade the performance of the application too much. Thus, if the new modifications made by the user must be stored in the database, the object must be re-attached to a new transaction, a functionality that is provided by the merge function. However, this function attaches a copy of the object, not the object itself, which may, for example, require the object memorized in the servlet session to be updated !)

. . . presentation layer (Struts), generates client in the servlet session . . .

tx.begin();
    Client client = session.getAttribute("client");
    em.persist(client);
tx.commit();

. . . presentation layer (Struts) . . .

tx.begin();
    Client client = session.getAttribute("client");
    x = em.merge(client);        // x is now reattached to the transaction
    request.getSession().setAttribute("client", x); // reintroduce the new copy into the session
tx.commit();

Concurrent transactions

The problem becomes more complex in the case several users have access to the same record, for example to book a number of seats in a flight. It may actually happen that two users read the same flight object, check whether there are enough free seats, decrease the current number of seats by the number of seats they want to book and write their objects back to the database, which would leave the first object written back being overwritten.

Note that if the database would be accessed directly through SQL, one could avoid that problem by using an adequate query, but the JPA solution is not bad either. The JPAs offer the possibility to add a version number in a column in each table and to check, before every update, whether the number in the attached object and in the database match. If it is not the case, it means that another user has modified the record and an exception is raised. The program must then re-do the transaction and possibly ask the user for new inputs (for example if there are not enough seats left any more).

This solution solves both the problem of the "long" transactions, the ones that cover the requests from several pages, and the one of the "short" transactions in most situations (see the lines below for special cases).

      try {
            country = em.merge(country);
            country.setNumber(country.getNumber()+1000);
            tx.commit();     // or tx.rollback();
      } catch (javax.persistence.OptimisticLockException oe) {
            out.println("Throws"+t.getClass().getName()+" "+ t.getMessage());
      }

In the code above, both the merge and the commit methods may raise exceptions when the version in the database does not correspond to the value stored in the merged object or in all the objects referenced in the manager for the commit method.

In some cases, you may want to make a set of modifications in an atomic way (although rarely) and avoid that several threads interleave the actions of their "short" transactions, which may particularly occur with the capacity of JBoss/Tomcat to simultaneously run servlets on different threads. This problem requires the locking of the record until the end of the transaction, as explain below.

The method below finds an object (country, which contains a name and a number) and increases its number. The method reacts correctly when several servlets call it and interleave their actions. Complete sources are available with stand-alone transactions in the JBoss environment, with session transactions and with transactions in a client. These sources contain sleep calls to allow the user to call the servlets in a way that the actions of either the "long" or the "short" transactions are interleaved. Comments on how to use the programs are available in the source with the stand-alone transactions.

      public void updateNumber (String name, boolean wait,
                                java.io.PrintWriter out)

                                                                                  throws
Exception {
            javax.persistence.EntityTransaction tx=null;
            try {      
                  javax.persistence.EntityManager em = ejb3_utility.Manager.open();
                  for (int i=0; i<5; i++) {
                        tx = em.getTransaction();
                        try {
                             tx.begin();
                             Country country;
                             country = Country.find(name);
                             em.lock(country, javax.persistence.LockModeType.WRITE);
                             //The previous lock assures that no other threads will update the current row
                             country.setNumber(country.getNumber());
                             tx.commit();    
                        } catch (javax.persistence.OptimisticLockException oe) {
                             tx.rollback();  // version changed
                             continue;
                        }
                        break;
                  }
            } catch (Exception e) {
                  out.println(e.getClass().getName()+" - "+e.getMessage());
            } finally {
                  if ((tx!=null) && tx.isActive()) {
                        tx.rollback();
                  }
                  ejb3_utility.Manager.close();
            }
      }

This code has a loop that tries 5 times to update the country object. It first finds the objet by its name, and then locks it. If the version number has been changed after the find has been executed, the locking operation throws an exception. The first idea would be to nest the finder within the lock, but this is impossible, because the latter needs the found object as argument. However, as the lock checks that the version has not been changed since the find has been performed, the lock is safe. If a javax.persistence.OptimisticLockException is raised, the loop rolls the transaction back and restarts the whole sequence of operations. The lock is useful for the "short" transactions and the versions are important for both.

A class used both as an action form and an EJB

A JPA can extend another class. If it extends strutsfsm.StateForm, as shown below, it can be used both as a bean and as an action form. The bean defined below can thus be referenced in a Struts FSM. Actually, this definition replaces the form definition. Note that the basic Struts require the standard classes: extends ActionForm.

In order to reference a JPA in a JSF FSM, no extend statement is required. The generator automatically searchs for the JPA referenced in JSF pages or other forms (The annotation @form is available to force this behavior).

jpa Country extends strutsfsm.StateForm { // an jpa used as an action form
    package myPackage;
    relation <hasTowns 1:N isIn> Town;
    String name;
    int number;
}

Remember, the action forms are connected to the Struts session, whereas the JPA are connected to the database. Thus, some precautions must be taken to avoid side effects (which is actually the case of all Java objects). Let us consider the following example (for the sake of simplicity, the occurrence of errors are not taken into account) assuming that the program executes the following statements repeatedly, allowing the user to fill country between the executions.

// town is assumed to be an action form filled in by a user in his browser

javax.persistence.EntityManager em = ejb3_utility.Manager.open();
javax.persistence.EntityTransaction tx = em.getTransaction();
tx.begin();
    try {
        country = Country.find(country.getName());
        request.getSession().setAttribute( "country" , country);
    } catch (javax.ejb.FinderException fe) {
        country.setId(0);
        em.persist(country);
    }
tx.commit();
ejb3_utility.Manager.close();
. . .
request.getSession().removeAttribute("country");
or
country.setId(0);

One of the statements above must be executed in order to disconnect it either from the client session or from the database, depending on what you want to do, if the object is going to host new data. In the example below, one checks if a country already exists before persisting it. If it exists, it is presented to the user, otherwise the object created by the Struts is persisted.

Lazy evaluation: in the first part of the code above, be sure to access the whole collection of the relationship in some way. As the system uses lazy evaluation, it would otherwise delay the access to the collection members, and complete it only within the presentation layer, outside the transaction. This would produce a error (lazy evaluation...) when the JSP accesses it to display it.

tx.begin();
    client = Client.findByName("xxx");
    client.getHasBills().size(); // forces the collection to be loaded
tx.commit();

. . . presentation layer (Struts) can access client.getHasBills() . . .

RMI, Remote Method Invocation

Given the listing below, the compiler generates a server object, named RemoteTest.java, its class-interface named Test.java and a client that is able to call it from the same computer. Calling a remote object located in another computer, just requires the introduction of the name of the remote computer in the code.

rmi Test {
    package rmis;
    public void print (String s) {
        System.out.println("X "+s);
    }
}


jclient Client {
    package rmis;
    Test x;     // Test is the name of the module; x is automatically initialized to point to the remote object
    RemoteTest y;     // is also a remote object, but not automatically initialized, see method connect
    public void print (String s) throws Exception {
        try {
            x.print(s);         // calls the remote object
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void connect() throws Exception {
        new RemoteTest(true);       // creates, in this client, a remote object that can be called from another one
    }
}

From the rmi module, the WebLang compiler creates an object named RemoteTest.java, which can be instantiated in any program: new RemoteTest(true) (true requests the registration of the object). However, this object contains a main, which makes it possible to start it like any Java program. In other words, you can start the RemoteTest.java program (see details below) and then the method print of the Java client or the method connect of a Java client and then method print of another Java client.

The figure below illustrates the various part involved in the management of a remote connection.

 

Possibilities of Instantiation and Execution of the RMI Objects

The object prepared by WebLang from the module Test defined above can be used in various ways.

  1. You may just run RemoteTest.java like any Java program. This object contains a main that instantiates this object, which registers itself in rmiregistry.

  2. An object RemoteTest may also be declared in a Java client or a bean and instantiated as shown below. It is automatically registered too, if it is passed parameter true.

    RemoteTest obj = new RemoteTest(true);

  3. It is possible to extract the stub referencing a remote object RemoteTest and to pass it to another program, for example through another RMI connection to establish a reverse connection (see below):

    x = new RemoteTest(false);     // do not register it
    xStub = (Test)java.rmi.server.UnicastRemoteObject.toStub(x));
    otherStub.pass(xStub);

  4. WebLang initializes the attributes corresponding to rmi modules (=stubs) to have them pointing to their remote object. This is done by means of a lookup, such as the one shown below:

    Test stub = (Test)java.rmi.Naming.lookup("//localhost:1099/RemoteTest");

Of course, you can pick these statements from the generated files and arrange them to your needs.

- Notes JBoss uses ports 1098 and 1099 ! In order to use another port, use rmiregistry 1100 and enter
Naming.rebind("//localhost:1100/RemoteXxxx", this); in the remote object.

The WebLang compiler generates the three usual files CGxdoclet.xml, CGpackaging.xml and application.xml, although they are not used at all.

Preparation of the excutable files

The preparation and execution of the resulting RMI application require the use of external tools located in directory bin of Java. They are called in the following way:

  • Click the     icon of the Eclipse menus, select External Tools..., browse to find the files indicated below or/and enter the required Variables

Here are the steps requited to create an application:

  • Creating the auxiliary files with rmic (one command for each rmi module)

    • Select External tools... and click Program

    • Use the browsing button of the Location field to find rmic in the bin directory of your Java directory.

    • Select the ${project_loc} variable in the working directory field and complete it as ${project_loc}/classFolder, where classFolder is the path of your class folder.

    • Enter -keep -d ${project_loc}/src yourPackage.RemoteYourXxx in the arguments area.
      Note that you could also create an empty file with the same name as the RemoteXxx.java, but without extension, in the directory of the Remote file, enter -keep -d ${project_loc}/src ${container_name}.${resource_name} and select it when you run rmic. In that case, you can use the same rmic command for all remote objects.

    • Set the Refresh flag.

    • Select a file in your project (in order to set ${project_loc}) and run the command just prepared.

    • Verify that file ..._Stub.java has been created in yourPackage.

  • Creating a command to launch rmiregistry

    • Select External tools..., enter rmiregistry into the Location field, by browsing the Java bin directory.

    • Enter ${project_loc}\classes as working directory. Possibly enter a port number in the arguments area.
      Attention: both JBoss and (by default) rmiregistry use port 1099 !
      Remember that rmiregistry must have all stubs in its classpath (or none if you use the codebase options). Thus you must select a file in your current project when starting rmiregistry, to set ${project_loc} property.

Note that all these commands could be started from a terminal window in the directory classes.

Running the application
  • Launch the rmiregistry prepared above in your project.

  • Select the RemoteTest.java and start it as a Java application.

  • Select the client containing Test and start it as a Java application.

If you want to start the client in another computer, change localhost to the server's host name in the client source, create a jar file with at least the interface, the stub and the client and transfer it where you want to run the client. Be sure that rmiregistry, the remote object and the client have accesses to the stub and the interface through their class path.

Note that in the tutorials about RMI, the contrary of the latter rule is required: neither the rmiregistry nor the client should have all access to the stub. This is because they use the codebase option, which makes things quite a bit more complex. The program is best tested first without the codebase option (See book).

Example of a Bidirectional Transmission

Here is an example of a reverse communication (Figure, animated PowerPoint). It is a bit more tricky. The client creates an object reachable by the server and passes the stub it extracts from this object to the server through the direct rmi connection.
In order to execute this application, start rmiregistry, the program RemoteServer.java and then Client.java. Click Submit in page sendStubPointingToMyObject, type a text in boomerang and submit it. The message will go to the other server and come back. (Note that it is possible to use UnicastRemoteObject.exportObject(remoteObj,0) to avoid binding the object and starting rmiregistry in the client).

Here is an example of a game that uses reverse communications. The program is quite symmetrical (it is both a client and a server). It can run on two computers if you change localhost to your own host names. To create and start the game read the indications in the .cg file.

Persistent Objects Based Only on SQL - DEPRECATED

Beta version

Book, Chapter 6
Syntax
Introduction (below)
Example (below)
Volatile attributes (below)
Finders (below)
JDBC accesses and transactions (below)
Complete examples (below)
Running these beans

These beans only require a JDBC library. The database can be created by executing the README/initDB.xml ant file generated by the WebLang compiler.
- The hsqldb.jar connector is available in JBoss/server/standard/lib. It must be inserted in the classpath of the initDB.xml ant file.
- The mysql connector must be introduced into the project classpath and, if JBoss or Tomcat is used, in folder lib of the JBoss server used (jboss/server/standard/lib) and finally, the ant script that is generated to initialize the database is expected to find it at the root of the project.
- If the program is run directly within the project, the classpath must include the corresponding connector.

Introduction

The Hibernate beans presented in the previous section do not use containers and are thus simpler than container managed beans. When using Hibernate, an object can be disconnected from the database, which is an advantage, because an object can be prepared in memory and stored into the database only when it is complete and validated, and without copying its content into a new CMP bean. However, several objects can contain the same row of data, which is a disadvantage for coordination reasons. As we have seen in the previous paragraph, it is very interesting to have the possibility of disconnecting the objects from the database (an object may simultaneously be a bean and an action form), and thus, it is better to tolerate the disadvantage of having several instances of the same data.

Actually, if this is the case, one can go one step forward and use objects that define methods for storing their data into and reloading them from the database, without relying on a manager nor on reflection. The SQL beans presented here explore this possibility. They give actually the same flexibility as the other beans, but all the code is accessible to the developer, providing a simpler debugging. They are prepared off-line, which provides better performances and allows the loader to check that all required classes are available at the start of a run. Moreover these objects use the standard SQL managed through the standard JDBC interface, which puts all the power of these concepts into the hands of the developer.

The beans we propose contain the set of methods depicted on the figure shown below, plus a number of auxiliary methods that are available to the developer, if they would appear to be useful. Actually, the right set of methods is still to be determined, but the figure depicts a set that seems coherent. The states of the objects described in that figure are the following:

Detached: The object has been instantiated, but there is no equivalent in the database and it is not included in a relation with the other objects.
Attached: The object has a corresponding set of data in the database and it memorizes the primary key that indentifies the row in the database.
Linked: The object is embedded in a structure corresponding to one of the relations available in the database, but it has no primary key that would identify data in the database. Its pk attribute is set to 0.
Attached, linked
and connected:
The object has a primary key and is embedded in the database, as well as in memory, within some relationship. In that state, the methods that introduce new objects in the structure modify both the relation information in the database and the pointers in memory.

It is not safe to have some objects of the structure that are attached and other that are linked, but not attached. Thus, the dotted lines should not be used, although the system does not checked this.

The methods appearing on the diagram perform the following actions:

store Copies the data of the object into the database and introduces the new primary key into the object.
findXxx This method (which starts with find) is built around a query defined by the developer in pure SQL. The query should return a complete row, the columns of which are introduced into the object automatically. The object must be instantiated before the method is called.
findPKXxx This method (which starts with findPK) only modifies the PK of the object. The query may use the parameters of the method and the attributes of the object for its request. The method returns true if the row is found and the returned type is boolean. On the other hand, if it is anything, but boolean, the method throws an exception if the searched row is not found. It offers thus two modes of operations, depending on the type defined for the return value.
reload This method copies the data of the database row with primary key this.pk into the object. It may be used in conjunction with findPKXxx.
delete This method removes the data in the database, but keeps the object untouched in memory, apart from its primary key, which is reset. The memory object can be used for handling new values.
set, add
and remove
These methods establish the connections between the objects according to the relationships. The two directions of the relationship are handled. Both the in-memory and the database references are updated, in the case the primary key attribute of the object is set. Otherwise only the memory links are updated (both directions).
update This method is the dual of the reload. In contrast with the later, this method updates the database data that have the PK found in memory, to have them reflect the state of the object.
reloadAll, storeAll,
deleteAll and updateAll
These methods call all objects that are bound by the relationships supported by the object in which the method is called. With the exception of the deleteAll, they handle the in-memory references and the database relationships. These methods call methods reloadRelationship, storeRelationship, deleteRelationship and updateRelationship, which are also available to the developer
Example

Here is an example (from version 2.4.3). (See also the complete examples)

    @@deploypath = "c:/jboss-4.0.4.GA/server/default/deploy/"; // if Struts are present
    @@db_address = "jdbc:hsqldb:hsql://localhost:1701";
    @@db_user = "sa";
    @@db_password = "";
    @@db_jdbcjarpath="C:/jboss-4.0.4.GA/server/default/lib/hsqldb.jar"
    @@db_type = "hsqldb";                        // for hsqldb

    

    // @@deploypath = "c:/jboss-4.0.4.GA/server/default/deploy/";      // if Struts are present

    // @@db_address = "jdbc:mysql://localhost:3306/test";                 // standard database

    // @@db_user = "";

    // @@db_password = "";

    // @@db_jdbcjarpath="/users/mysql/mjc/mjc.jar"

    // @@db_type = "mysql";                              // for mysql

    

    sqlbean Town extends strutsfsm.StateForm {
        package appliT;
        relations (Country = N:1, <isCapitalOf 1:1 hasCapital> Country);
        String name;
        public Town(String s) throws Exception {
              name = s;
        }
        public long findPK_OfTown() throws Exception {

            query = "SELECT * FROM Town WHERE name=$name"

            //query = "SELECT * FROM Country WHERE name=? AND pk=?;"

        }
        public String toString() {
           String str = "Town: name = " + getName()
           + " in " + (Country==null?null:Country.getName());
           return (str);
        }
    }
    TownN {
        String name;
        public void findN_TownList(Strings) throws Exception {

            query = "SELECT * FROM Town WHERE name>$s"

        }
        public void print() {
           for (Town x: this)
           System.out.println("--- "+x);
        }
    }

    

    //////// in the main program //////////

    case State_1: // we assume that country has been created and filled by the Struts mechanism

    if (country.findPK_OfCountry()) {
        country.reload();
    } else {

        country.store(); // or country.update() depending on the situation

    }
    country.addTownN(town); 

    // town.setCountry(country); // either this line or the line above (the second one is called automatically)

    break;

    // both the memory and the database are now uptodate

    // (note that if an attribute is changed, an update

    // must be performed ( country.update(); )

The module is quite similar to the previous ones. The first relationship is in the "old" style, whereas the capital has named relationships. The old style relation has the same access methods as the previous modules, and is currently required for the action forms. The second one uses the names of the relations (see setIsCapitalOf) to access the components of the relationship. If the second relation name is not present, the reverse relation takes the same name as the forward relation. Note that the relation that is opposite to a 1:N is now N:1.

This module extends extends strutsfsm.StateForm. It can thus be used as action form in a Struts.

The statements that appear after the modules may be placed in a Java client or in a struts (because this module extends strutsfsm.StateForm).

The module Town is followed by a module with name TownN, which is optional. This module extends an ArrayList used to store relations 1:N or N:M. It can also be used on its own, to store finders that return collections of objects. It is the class returned by the get1NRelationshipCollection, or getTownN() (old style syntax).

Currently, the Struts require the old style relationships, but we are working on a new version that will have better possibilities to define the forms.

Volatile attributes

Attributes may be preceded by the keyword volatile to specify that they be integrated in the object, but without a correspondance in the database. This is useful to create action forms with some parameters that are used for management purposes, but are not stored in the database.

Finders

The variable parameters of the SQL commands used in the finders may be defined as ?, which is the standard JDBC format or as $name, which is handled by WebLang to insert them in the string in the usual way. The number of ? must correspond to the number of arguments of the method. They are replaced in the order of the arguments. The $parameters can reference both the arguments and the internal attributes. They are automatically embedded in 'apostrophes' if the argument is of type String.

JDBC accesses and transactions

A JDBC connection and an SQL statement object are attached to the thread that is executing the program, within a java.lang.ThreadLocal object, inherited in a weblangUtils.DBConnection object. The source of the later is automatically created in the project by the WebLang compiler. It contains method getConnection(), which returns a JDBC java.sql.Connection object, method getStatement(), which returns an object of class java.sql.Statement and method close(), which closes the connection.

The JDBC java.sql.Connection object offers support for beginning, committing, aborting transactions and setting their levels (TRANSACTION_REPEATABLE_READ...). It can be used by the developer to handle these aspects.

Complete examples

Introduction of countries with towns inside (one can also define capitals, but the Struts require the old style relationships).
hsqldb_Struts.la, mysql_Struts.la: in order to start the application, execute the initDB.xml ant file located in the README folder and then open the file.html in the same folder.

Introduction of students that attend courses (NM relatonships).
Students.la: execute the initDB.xml ant file located in the README folder and then start the Java program Test1DB.java. Here is an auxiliary file that prints the content of a set of tables.

  -> TOP

Lausanne, September 1, 2006