home
products
education
support
partners
company

introduction

get started

articles

manual

tutorials

reference docs

case studies

knowledge base

goodies

screenshots

demos

echoes

Carousel zone


SourceForge.net Logo

home

forums

downloads

bugs

mailing list
XUI Zone - articles

number of ratings 0
average rating 0.00

Introducing Routes

23-May-2005

Routes and services within Carousel offer an easy to use way of integrating enterprise level functionality into an applications. Typically routes and services are used to add client-server communications and backend integration to an application. The concept of a service within Carousel is designed to make it easy to embed a piece of functionality into the data model. A function can be evaluated and the data it returns can be considered to be part of the model as though it were any other data node. In this way the location of the function and the details of the invocation can be hidden from the programmer. The service concept is sufficiently flexible to allow a wide variety of functional components to be embedded in the model.

Taking this abstraction a little futher we can chain such services together to build more complex functionality. Each service node within the hierarchy can do one specific task and delegate to the next service node for additional features. Because the role of each service node is clear cut it helps simplify implementation of that service.

Frequently services are used to transform, encode or somehow process data. The location of the original data can be hidden within a service and an individual service can be replaced so that high level logic, such as your business logic is freed from low level details that might otherwise give unwanted bindings or even hard coding of platform specific details.

In an enterprise level application these services play a key role, allowing client-server communications to take place. For example, a commonly used service is the HttpRequestService, this service allows that application to make a request to a webserver. The packaged services in Carousel thus provide much of the infrastructure needed to build powerful applications with placing lots of demands on the application programmer. Routes then are simple a layering together of a number of services so as to enable communications and data retrieval. A route might well consist of services for communications, security, authentication and so on. The difference from a pure collection of services is that a route is intended to be connected to endpoints for implementation and consumption of the service. The route provides the common functionality needed to connect the endpoints. Thus one route might well be used for a number of different services.

Routes in Carousel are used to control the flow, manipulation and transformation of data. A route is made up of one or several ServiceProxy objects which can end up at a webserver on a filesystem or wherever the developer sees fit.

In Carousel, the routes are specified in a routing file which is referenced from the datasets.xml file.

Listing 1 - datasets.xml

  
  

The reference to the routing file is the last entry in the datasets.xml file. Next, the routing file needs to be created.

Listing 2 - routes.xml

  
    

  

This file specifies a single route with the name ‘FileSave’ which will be used when saving information to a file. The services.xml file needs to define services which will refer to the routes defined in the routes.xml file.

Listing 3 - services.xml

    


The services.xml file needs to be referred to from the datasets.xml file as follows...

Listing 4 - datasets.xml

  
  
  



Saving a file using the FileSave route

In this example the FileSave route will be used to save the state of the ‘mortapp’ node in the model. The following is a page which is updating some personal information about a mortgage applicant with component bindings.

Listing 5 - A sample page

    ...
    
    
    

    
    ...
    

 >
 
 

  


Listing 6 - The Java class for the page
package net.xoetrope.mortgage;

import java.io.StringWriter;
import javax.swing.JFileChooser;
import net.xoetrope.builder.NavigationHelper;
import net.xoetrope.data.XDataSource;
import net.xoetrope.optional.service.ServiceContext;
import net.xoetrope.optional.service.ServiceProxyArgs;
import net.xoetrope.optional.service.XServiceModelNode;
import net.xoetrope.xui.XProjectManager;
import net.xoetrope.xui.data.*;
import com.xoetrope.service.file.FileSave;

public class Finish extends NavigationHelper
{
  
  public void save()
  {
    String filename = getFileName();
    if ( filename != null ) {
      XBaseModel model = ( XBaseModel ) XProjectManager
                .getModel().get( "FileSaveService" );
      XServiceModelNode node = ( XServiceModelNode ) model.get();
      ServiceContext context = new ServiceContext();
      ServiceProxyArgs args = context.getArgs();

      args.setPassParam( FileSave.ARG_NAME_MODE, FileSave.ARG_VALUE_SAVE );
      args.setPassParam( FileSave.ARG_NAME_CONTENTS, getAppContent() );
      args.setPassParam( FileSave.ARG_NAME_FILENAME, filename );
      Object result = node.get( context );
    }
  }
  
  private String getAppContent()
  {
    XBaseModel mdl = ( XBaseModel )XProjectManager.getModel().get( "mortapp" );
    StringWriter sw = new StringWriter();
    XDataSource.outputModel( sw, mdl );
    return "" + sw.toString() + "";
  }
  
  private String getFileName()
  {
    JFileChooser chooser = new JFileChooser();
    if ( chooser.showSaveDialog( this ) == JFileChooser.APPROVE_OPTION ) {
      return chooser.getSelectedFile().getAbsolutePath();
    }
    return null;
  }
}

The save function retrieves the location and name of the file which is to be saved by calling the getFileName() function. Next it retrieves the ‘FileSaveService’ model node from the model after which it casts its value to a XServiceModelNode object. Next a ServiceContext object is created. This object is used to pass and return parameters through the route. In this case the FileSave arguments need to be set. These are set by using the public static String values of the FileSave class. The contents of the file are specified by making a call to the getAppContent() function. The getAppContent() function retrieves the ‘mortapp’ node from the model and outputs it to a StringWriter using the XDataSource.outputModel static function. It then wraps the content in a ‘Datasets’ node so that it can be reopened. When this code is invoked the resulting file will look something like the following...

Listing 7 - The saved datafile


  
    
      
        
        

        
        
      
    
    
      

      
    
  

This file contains information from bindings on other pages, but it’s possible to see the paths which were specified in the page declaration earlier.


Opening a file using the FileSave route

Now that the file is saved, the developer may wish to allow it to be opened again. The same route will be used to carry out this functionality but with some different parameters.

Listing 8 - Reopening the saved datafile
  public void open()
  {
    String filename = getFileName();
    if ( filename != null ) {
      ( ( XBaseModel )rootMdl.get( "xui_state" ) ).clear();
      ( ( XBaseModel )rootMdl.get( "mortapp" ) ).clear();

      XOptionalDataSource ds = new XOptionalDataSource();

      XBaseModel model = ( XBaseModel ) XProjectManager.getModel()
                .get( "FileSaveService" );
      XServiceModelNode node = ( XServiceModelNode ) model.get();
      ServiceContext context = new ServiceContext();
      ServiceProxyArgs args = context.getArgs();

      args.setPassParam( FileSave.ARG_NAME_MODE, FileSave.ARG_VALUE_OPEN );
      args.setPassParam( FileSave.ARG_NAME_FILENAME, filename );
      Object result = node.get( context );
      StringReader sr = new StringReader( ( String ) 
                args.getReturnParam( FileSave.ARG_NAME_CONTENTS ) );
      XmlElement ele = XmlSource.read( sr );
      
      if ( ele != null )
        ds.loadTable( ele, rootMdl );

      updateBindings();
    }
  }

  private String getFileName()
  {
    JFileChooser chooser = new JFileChooser();
    if ( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) {
      return chooser.getSelectedFile().getAbsolutePath();
    }
    return null;
  }

Again, the name of the file to be opened is specified by using the file chooser in the getFileName() function. The ‘mortapp’ node will be populated so it is a good idea to first clear it using the clear() function. The same should be done with the ‘xui_state’ node. The ServiceContext is constructed again except this time the contents parameter is not set and the mode is open instead of save. When the call returns it is now possible to get the contents of the file from the return parameters of the ServiceContext object. Once retrieved, the XML is used to populate the model. The application will now bind once more to the data in the file.


Creating a custom ServiceProxy class

The FileSave ServiceProxy is a fairly useful way of saving to a file in a consistent way but does not provide the developer with anything that they could not have done quite easily themselves. In order to gain some real benefit from routing several ServiceProxy layers are usually employed to manipulate or transform the passed data. In the following example the work which was done in moving the data in and out of the model will be taken care of by a custom ServiceProxy class.

Listing 9 - The basic custom ServiceProxy
package net.xoetrope.mortgage.service;

import net.xoetrope.optional.service.ServiceContext;
import net.xoetrope.optional.service.ServiceProxy;
import net.xoetrope.optional.service.ServiceProxyException;

public class StateManager extends ServiceProxy {
  
  /**
   * The overloaded call for the ServiceProxy
   * @param method the name of the service
   * @param args the service arguments
   */
  public Object call( String method, ServiceContext context ) {
    try { 
      return callNextProxy( method, context, null );
    } catch ( ServiceProxyException e ){
      return null;
    }
  }
  
}

This class extends the ServiceProxy class and by doing so also needs to overload the abstract call method. This call receives the name of the service as its first parameter and the ServiceContext which was constructed from the calling code as its second parameter. The code above does no more than pass the call on to the next ServiceProxy in the route and return its outcome. In order for this class to be invoked when the FileSaveService is called the routes.xml file needs to be amended.

Listing 10 - The modified routes.xml file

  
    
    
  

The StateManager Class is called before the FileSave ServiceProxy whenever the FileSaveService is called. Now the model manipulation code can be moved out of the calling classes and into this ServiceProxy layer.

Listing 11 - The complete StateManager class
public class StateManager extends ServiceProxy {

  public static final String ARG_NAME_MODELPATH = "modelpath";
  private XModel rootMdl = XProjectManager.getModel();
  /**
   * The overloaded call for the ServiceProxy
   * @param method the name of the service
   * @param args the service arguments
   */
  public Object call( String method, ServiceContext context ) {
    ServiceProxyArgs args = context.getArgs();
    String mode = ( String )args.getPassParam( FileSave.ARG_NAME_MODE );
    String modelPath = ( String )args.getPassParam( ARG_NAME_MODELPATH );
    try { 
      Object ret = null;
      if ( mode.compareTo( FileSave.ARG_VALUE_OPEN ) == 0 ) {
        // The file is being opened so the returned content needs 
                // to populate the model
        ( ( XBaseModel )rootMdl.get( "xui_state" ) ).clear();
        ( ( XBaseModel )rootMdl.get( modelPath ) ).clear();

        ret = callNextProxy( method, context, null );

        StringReader sr = new StringReader( ( String ) args
                        .getReturnParam( FileSave.ARG_NAME_CONTENTS ) );
        XmlElement ele = XmlSource.read( sr );

        if ( ele != null ) {
          XOptionalDataSource ds = new XOptionalDataSource();
          ds.loadTable( ele, rootMdl );
        }
      } else if ( mode.compareTo( FileSave.ARG_VALUE_SAVE ) == 0 ) {
        // The file is being saved and the contents of the 
                // target model need to be output
         XBaseModel mdl = ( XBaseModel )XProjectManager
                        .getModel().get( modelPath );
        StringWriter sw = new StringWriter();
        XDataSource.outputModel( sw, mdl );
         args.setPassParam( FileSave.ARG_NAME_CONTENTS
                        , "" + sw.toString() + "" );
        ret = callNextProxy( method, context, null );
      }
      return ret;
    } catch ( ServiceProxyException e ){
      return null;
    }
  }
  
}

The StateManager class now takes care of retrieving and setting the model data. The final static String ARG_NAME_MODELPATH is defined for convenience when setting the appropriate argument in the calling code. The first thing that is done in the call method is that the context is checked for the mode of the call. The FileSave static String is used for this so as to ensure consistency. If the mode is to open the file the target model node is cleared as well as the ‘xui_state’ model node. The call is then passed over to the next proxy which is the FileSave ServiceProxy in this case as defined in the routes.xml file. Once returned the contents return parameter is used to populate the model. If the mode is to save the file target model is output to a StringWriter which is used to set the FileSave.ARG_NAME_CONTENTS pass parameter. The next proxy is called and control returns to the calling code. The open and save code can now be cleaned up by removing the model manipulation code.

Listing 12 - The modified save function
  public void save()
  {
    String filename = getFileName();
    if ( filename != null ) {
      XBaseModel model = ( XBaseModel ) XProjectManager
                .getModel().get( "FileSaveService" );
      XServiceModelNode node = ( XServiceModelNode ) model.get();
      ServiceContext context = new ServiceContext();
      ServiceProxyArgs args = context.getArgs();

      args.setPassParam( FileSave.ARG_NAME_MODE, FileSave.ARG_VALUE_SAVE );
      args.setPassParam( FileSave.ARG_NAME_FILENAME, filename );
      args.setPassParam( StateManager.ARG_NAME_MODELPATH, "mortapp" );
      node.get( context );
    }
  }

All that is left in this code is the setting up of the XServiceProxyNode, the ServiceContext and its parameters. An extra line has been added which sets the StateManager.ARG_NAME_MODELPATH parameter which is used to address the correct part of the model.

Listing 13 - The modified open function
  public void open()
  {
    String filename = getFileName();
    if ( filename != null ) {
      XBaseModel model = ( XBaseModel ) XProjectManager
                .getModel().get( "FileSaveService" );
      XServiceModelNode node = ( XServiceModelNode ) model.get();
      ServiceContext context = new ServiceContext();
      ServiceProxyArgs args = context.getArgs();

      args.setPassParam( FileSave.ARG_NAME_MODE, FileSave.ARG_VALUE_OPEN );
      args.setPassParam( FileSave.ARG_NAME_FILENAME, filename );
      args.setPassParam( StateManager.ARG_NAME_MODELPATH, "mortapp" );
      node.get( context );

      updateBindings();
    }
  }

Again, the code is much simplified. The code sets up the XServiceModelNode, creates the ServiceContext and its parameters. The updateBindings method of the XPage is called in case any components on the page are bound to the affected part of the model.


Summary

Moving data manipulation code out of project code and into ServiceProxy layers cleans up the project code and creates a consistent way of persisting and retrieving data. These layers can be built into a set of libraries depending on the requirements of the projects being developed. It is then much easier to replace functionality and to add other layers. A simple encryption layer could, for example, be added between the StateManager and FileSave ServiceProxy classes to encrypt data as it is passed back and forth.

comments


If you were logged in you could rate this article or add a comment.