You are hereSpring Integration

Spring Integration


By luano - Posted on 25 June 2008

DRAFT/PREVIEW

 

Spring is one of the most popular frameworks for building web applications, and as it comes with many extensions and integration capabilities it is an obvious choice for building Rich Internet Applications. Aria's strength is in building client side applications and rich user interfaces, so combining Spring and Aria offers a nice combination.

Aria has strong support for generating User Interface (UI) elements such as forms and dialogs directly from POJOs, including validation and data binding, and Spring provides a number of remoting technologies that make it possible to expose parts of a back end to a client application. Indeed, many Spring applications may already be ripe for skinning with an Aria frontend and in this article we will see how Aria adds support for the type of interaction that is needed for building Spring RIAs.

Background

Spring's inversion of control and dependancy injection also ties in nicely with the pluggable and extensible nature of the Aria Framework and in the forthcoming release Aria will adopt more and more of the conventions of the Spring Framework, ultimately with the objective of providing a seamless and easy to use integration. In contrast, prior to this integration Aria's precursor (the XUI framework) lacked strong support for backend technologies and had weak communications support. Aria drops XUI homegrown comms in favour of Spring support and thereby gains much more power for building enterprise level applications, not to mention giving the new project a better focus.

So, while Aria builds upon XUI in many ways, it is a major departure in focus and implementation. Aria still targets several legacy JDKs, but it does not seek to provide support for all features in these versions and hence making maintainence easier and the build system is a little cleaner.

MetroBank

The MetroBank application is a custom built application designed to showcase both Aria and its Spring integration. The application contains very little by way of client side logic as one would expect in this type of application, but that is not to say that the application is dumb, rather much of the logic is provided by Spring and its services.

On the server side much of the Spring infrastructure is based upon the examples provided by Craig Wall's Spring in Action book and the road rantz sample.

Spring Context

The Aria framework comes with various application stubs for differnt widget sets and different applications styles (SDI, MDI, Docking etc...). Each stubs relies on a common application core and a common project structure. The project provides storage and a central access facility for various application objects so that singletons can be avoided.

During the application startup or initialization (depending on application and security concerns) the Spring context needs to be initialized, but rather than doing this in isolation the Spring context is wrapped in an access object that also provides some of the object marshalling needed by the client application. However since we want to bind teh Spring POJOs to UI components we need to integrate the wrapper with the data model.

Aria supports multiple data sources and a source for the POJO model can be added by inserting an entry in the datasources.xml file:

    <Datasources>
        <Datasource type="database" filename="dbtables.xml"/>
        <Datasource filename="staticdata.xml"/>
        <DataSource filename="pojos.xml" name="pojos" type="org.formaria.data.pojo.PojoDataSource"/>
    </Datasources>

The pojos.xml file then configures how the root POJO is loaded and initialized:

    <Pojos>
      <context class="org.formaria.metrobank.model.SpringObjectAccessService"/>
    </Pojos>

the SpringObjectAccessService extends PojoContext and the necessary methods, the most important of which is the getRoot method. The root is then mapped to the path pojo in any binding path. Sub paths are then mapped to method calls on the root object, so a path pojo/accountdto would map to the method SpringObjectAccessService.getAccountDto(). One side effect of this loading via the model is that no remote access occurs till the model is loaded and some of the POJO nodes are accessed, if early loading is required then accessing any of the Spring nodes will cause the remote access to be initiated. (facilities to support this may be added later).

Most of the wrapper will e familiar to Spring users, but the wrapper object provides an easy way to access various remote objects. By convention the objects are accessed by ID and the wrapper provides some lookup facilities for this purpose plus a client side cache integration.

package org.formaria.metrobank.model;

import org.formaria.cache.Cache;
import org.formaria.cache.ObjectAccessService;
import org.formaria.metrobank.service.Account;
import org.formaria.metrobank.service.AccountService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 * @author luano
 */
public class SpringObjectAccessService implements ObjectAccessService
{
  private ClassPathXmlApplicationContext springContext;
  private Cache objectCache;
    
  public SpringObjectAccessService()
  {
    SpringCachedObjectFactory.getInstance();
    objectCache = new Cache();
  }
  
  public void init()
  {
    springContext = new ClassPathXmlApplicationContext( "metrobank-hessian-client.xml" );
    objectCache.setRemoteService( this );
    
    AccountService accService = (AccountService)springContext.getBean( "accountService" );  
    Account[] accounts = accService.getTransactionsForAccount( "MOR", "1231231" );
    objectCache.putList( accounts );
    
    if ( accounts != null ) {
      for ( Account acc : accounts ) {
        System.out.println( acc.getAccountName());
        objectCache.get( Account.class, acc.getId());
      }
    }
  }

  @Override
  public Object read( Class cls, Object id )
  {
    throw new UnsupportedOperationException( "Not supported yet." );
  }

  @Override
  public void write( Object o )
  {
    throw new UnsupportedOperationException( "Not supported yet." );
  }
}

Remoting

The MetroBank application relies heavily on Spring remoting technology. In the case of the sample application the Hessian protocol is used, but any of Spring other remoting technologies such as the HttpInvoker could be used. The choice largely depends upon the type and range of application being used. If an Ajax or browser based client was also being provided then the HttpInvoker might make a better choice.

The remoting follows the form of the RoadRantz example, and is configurd as follows:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="metrobank-remoting.properties" />
  </bean>
        
  <!-- 
    HessianProxyFactoryBean to wire in a Hessian service.
    
    From section 8.3.1, page 317. Notice, that I'm using the short-hand
    value attribute here, while the book shows the long-hand <value>
    element. I had intended for the book to use the short-hand form, as
    shown below, but didn't catch this until the book was already typeset
    and it was too late to change.
   -->  
  <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="http://${serverName}/${contextPath}/account.service" />
    <property name="serviceInterface" value="org.formaria.metrobank.service.AccountService" />
  </bean>

</beans>

Data Transfer Objects

The Data Transfer Object (DTO) pattern is also used in the MetroBank example so as to provide coarse grained access and to decouple the client application from the lifecycle of the domain objects.

Since Aria makes heavy use of the DTO pattern it provides a factory for obtaining DTOs directly from the domain objects. Such a factory was provided as part of the Java EE blueprints, however rather than relying on the usual manual construction of DTO and interfaces matching the DTOs, Aria works of annotation of the domain objects. Thus for example the Account object can be annotated as follows:

package org.formaria.metrobank.domain;

import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.formaria.dto.DtoField;

@Entity
@Table(name="AccountDetails")
public class Account implements Serializable 
{
  @DtoField
  private Integer id;
  
  @DtoField( value="customer.id" )
  private Customer customer;
  
  @DtoField( value="accountType.id" )
  private AccountType accountType;
  
  @DtoField
  private String accountNumber;
  
  @DtoField
  private String accountName;
  
  @DtoField
  private BigDecimal creditLimit;

  @DtoField
  private String iban;
  
  public Account() 
  {
  }
  
  ...
}

Those fields marked above with the @DtoField are exported to a DTO object synthesized at runtime using the BCEL framework. Once the DTO fctory has a reference to the domain object and the new DTO object it can use the beans API to copy the values from the domain object to the new DTO object ready for remoting and use in the remote application. Similarly the factory maintains class property information (the annotated fields must have both getter and setter methods conforming to the Java Beans specification), and can copy data back from a returned DTO to the original domain object. For example accounts can be returned from the server as follows:

package org.formaria.metrobank.service;

import java.math.BigDecimal;
import org.formaria.metrobank.domain.Account;

import org.apache.log4j.Logger;
import org.formaria.dto.DtoFactory;

/**
 * A POJO implementation of a Account service.
 * 
 * From Listing 8.1
 * 
 * @author wallsc
 */
public class AccountServiceImpl implements AccountService 
{
   private static final Logger LOGGER = Logger
                     .getLogger(AccountServiceImpl.class);

   public AccountServiceImpl() {}

   public Object[] getTransactionsForAccount( String accountType, String accountNumber ) 
   {
     DtoFactory dtoFactory = DtoFactory.getInstance();
     
      LOGGER.info("In getTransactionsForAccount()");

      Object[] citations = new Account[2];
      // TODO - implement real citation lookup. For now, return dummy values
      Account citation = new Account();
      citation.setIban( "BOI578EI9798798"); 
      citation.setCreditLimit( new BigDecimal( 10000 ));
      citation.setId( 6 );
      citation.setAccountName( "John Doe Ltd.");
      citations[ 0 ] = dtoFactory.createTransferObject( citation );
      
      citation = new Account();
      citation.setIban( "BOI578EI2344338"); 
      citation.setCreditLimit( new BigDecimal( 100 ));
      citation.setId( 7 );
      citation.setAccountName( "Acme Corp.");
      citations[ 1 ] = dtoFactory.createTransferObject( citation );

      return citations;
   }
}

Pojo Panels, Forms and Dialogs

@todo

Aria includes extensive support for POJOs and within the editor plugins panels or forms can be generated for the POJOs semi automatically. Aria also provides this support at runtime, for example the following XML

 


<?xml version="1.0" encoding="UTF-8"?>
<XPage w="1016" h="734" class="com.myproject.MyController">
<Components>
<Panel title="My Form" border="0" layout="border">
<PojoPanel
name="myForm"
constraint="center"
style="base"
pad="6"
spacing="4"
numCols="2"
dataPath="pojo/myModel/myDataObject"
alwaysDirty="true"
methods="save"
fieldValidationRule="MyValidationRule"
class="com.myproject.transfer.MyDataObject"
fieldOrder="a,c,d,b,f"/>
<Panel constraint="south" layout="flow"
align="center" style="base"
hgap="6" vgap="6">
<Button name="saveBtn" content="Save"/>
</Panel>
</Panel>
</Components>
<Events>
<Event target="saveBtn"
method="${pojo/myModel.saveStuff(pojo/model/myDataObject)}"
type="org.formaria.pojo.SaveBeforeActionHandler"/>
</Events>
<Validations>
<Validation rule="MyValidationRule"
target="myForm"
dataPath="pojo/myModel/myDataObject"/>
</Validations>
</XPage>

 

takes a POJO and creates a form, binding the UI components to the input fields and validates inputs, where

  • pad gives the spacing around the content/columnar data
  • spacing sets the spacing between the items that are placed on the panel
  • numCols sets the number of columns (labels and input) used by the panel
  • dataPath refers to the object instance that is used as the source for the data bindings
  • alwaysDirty set to true to always evaluate the binding paths when displaying the page
  • class is the class whose fields and methods are used in the panel
  • methods corresponds to the names of the methods on the class referred to by the class attribute that are linked to the panel
  • fieldValidationRule is the name of the validation rule for the input fields
  • fieldOrder an override for the order in which the fields are displayed
  • excludes (not shown above) a list of fields not to include in the display
  • includeByDefault (not shown above) determines if all the pojo fields (minus the excluded fields) are listed on the panel, or if set to false only the explicitly included fields are listed.
  • rowHeight the height of the rows on the pojo panel
  • colWidth the total width of the individual columns
  • inputEnabled true to enable the input fields by default (to make an editable panel)
  • enabledByDefault as above
  • useColumnOrder set to true to change the focus traversal order to go down the leftmost column(s) before starting atthe top of the next column. False to traverse the focus across the rows.

The above xml gets rendered as:

To enable the PojoPanel support add a reference to the component in your components.xml file:

 

    <Component name="PojoPanel" class="org.formaria.swing.pojo.PojoPanel" />

 

Other properties can be added or instead the properties can be stored in a view file (obtained from a remote service) so that different views of the POJO/object can be served up to different clients, or the form can be generated using defaults.

Validation kicks in when the form is saved or when focus changes and error badges and colour coding can be applied to fields that fail validation. Validations can use Aria's built in validation mechanism or they can run off something like the Apache Commons validation library.

When performing CRUD operations it would be best if your POJO was cloneable as you could then create and discard versions of the object to backup edits when using this PojoPanel as the PojoPanel will write to the backing object when validating and saving, possibly giving you partial dirty writes if you cancel an edit. One workaround for this is to call:

 

    pojoPanel.backupInputs();

 

when begining an edit and

 

    pojoPanel.restoreInputs();

 

if that edit is cancelled. The

 

    pojoPanel.enableInputs( enableBtns );

 

can also be used to disable the inputs when not in edit mode.

Working with tables

In the same manner as the POJOs are inspected and rendered list or tables of POJOs can be added to tables and list. In the case of a table the fields from the POJO are mapped to table fields and the POJOs are listed in the order in which they are encountered. The only difference from setting up a normal table is the binding adapter and option view definition parameter (more of which later). For example

  <Bind target="summaryTable" 
        viewPath="views/my_summary"
        source="pojo/myList" 
        adapter="org.formaria.swing.table.XTablePojoModelAdapter" />

sets up a table of POJOs stored in the data model at the pojo/myList path.

View definitions

Rather than including all the information about field inclusion, validation etc... so instead this can be off-loaded to a view definition file. (If both are specified then the two definitions are merged). having a separate view definition means that different views can be server up to different clients, perhaps with differing access rights.

The view def is an XML file with an element for each field, including a name, type, visible, exclude, label, and enabled attributes. The view can contain extra attributes if desireable. The fields are added to the table or PojoPanel in the order they are listed. The field control:

 

  • name - matches to the POJO/bean property (required)
  • type - the widget type to display the data (optional)
  • visible - if set to false the field is added to teh panel but not displayed, similar to teh behavior of hidden fields on HTML forms (optional)
  • exclude - if set to true the field is not added to the panel(optional)
  • label - an over ride for the caption, normally a key into a language properties file. If omitted the caption is derived from the bean property name with spaces inserted between words where camel case has been used for those names (optional)
  • enabled - if set to false the input field is disabled (optional)

 

Caching

@todo: complete

For performance reasons it is not always desireable for a client side application to pull all the data it needs from the server at startup as this would make for slow initialization. Furthermore a graph of objects can often be loaded lazily by a background thread as the data contained in parts of the object hierarchy may not be immediately visible. However such lazy loading can cause considerable headaches for a client application as it will need to do lots of extra checking to ensure that valid objects are available and this can greatly complication the application as a whole. Furthermore modern GUIs often present different views or aspects of the same data in different ways, for example summaries, detailed views, editable views and so on and when you throw in the various updates caused by navigation within the application then management of data can begin to outweigh the application specific logic that should be the focus of development.

Aria includes a caching layer that can help detach the application from the data retrieval mechanism so that the deverloper need not worry about how and when the data is retrieved. However the first step in caching data is to obtain the initial instance and save that in the cache. Aria uses a wrapper to help identify and manage cached objects.

    Cache objectCache = new Cache();
    objectCache.setRemoteService( (ObjectAccessService)this );
    
    AccountService accService = (AccountService)springContext.getBean( "accountService" );  
    /*List<Object>*/ accounts = accService.getAccountsForCustomer( 123 );
    objectCache.putList( accounts );
    
    // Test the cache
    if ( accounts != null ) {
      for ( Object acc : accounts ) {
        System.out.println( acc.getClass().getName());
        System.out.println( objectCache.get( acc.getClass(), ((DtoIdentifier)acc).getId()));
        //Object cacheObj = objectCache.getByName( subPath, null );
      }
    }

The cache can also take lists of objects and lists of lists so that an extensive object graph can be returned and cached in one go. Accessing the objects by ID allows for fine grained access to the objects so that dialogs and CRUD operations can be mapped to individual objects.

Client side caching also provides considerable performance benefits as most applications are predominently read-only and therefore storing data locally can save many remote calls, reducing the server workload and providing the UI with quicker data access as a result. However even if an individual client is acting in a read-only mode a parallel client may update the data and this should be reflected in all the active clients where possible (some may be off-line). Therefore the cache needs to be updateable via a server driven notification mechanism...

Binding to Cached Objects

The PojoContext (or any other POJO) may implement the PojoLookup interface which allows the POJO to translate a binding path to a child object. Normally the PojoModel nodes translate subpaths to bean properties via the getter and setter accessors, but for cached objects this would represent a duplication of access methods and an unnecessary overhead. Adding the lookup interface therefore allows the cache to be queried and in this way the UI can be bound to almost any cached object without having to burden the PojoContext with heaps of accessor methods.

The cache itself maintains a table of simple class names and these are used to lookup the full class references. In the case of the accounts summary page the binding path is pojo/accountDto which is translated to a lookup of the AccountDTO class instances in the cache.

Trees and other advanced widget types

@todo

 

Nested/Composite POJOs

Using the view definition properties from nested or composite POJOs can be include simply by using the normal dot notation for the property name, for example an attribute

    name="customer.phone"

would refer to a the customer member within the POJO and to the phone member of the customer object.

Validation and Error badges

@todo

 

Rule driven UIs

@todo