POJOs


By luano - Posted on 14 January 2009

As we have seen earlier Aria supports a number of ways of working with POJOs, Hibernate and other data structures either within the editor using drag and drop or through the use of customized builders. Aria also supports the dynamic creation of forms as runtime with little or no special configuration

Introducing PojoPanel

PojoPanel is a panel that takes a reference to a POJO and constructs a user interface for that component based upon the POJO's public properties and methods. The panel configures the data bindings and event handlers for the UI automatically. In declaring an instance of the panel it is possible to provide layout hints for things like the number of columns, spacing, field order, exclusion of fields and more.

The PojoPanel can be used with POJOs from both Hibernate and Spring as well as other sources.

For example the following code binds to a POJO:

A sample page using a PojoPanel

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Page w="1016" h="734" class="com.myproject.MyController">
  3. <Components>
  4. <Panel title="My Form" border="0" layout="border">
  5. <PojoPanel
  6. name="myForm"
  7. constraint="center"
  8. style="base"
  9. pad="6"
  10. spacing="4"
  11. numCols="2"
  12. dataPath="pojo/myModel/myDataObject"
  13. methods="save"
  14. class="com.myproject.transfer.MyDataObject"
  15. fieldOrder="a,c,d,b,f"/>
  16. <Panel constraint="south" layout="flow" align="center" style="base"
  17. hgap="6" vgap="6">
  18. <Button name="saveBtn" content="Save"/>
  19. </Panel>
  20. </Panel>
  21. </Components>
  22. <Events>
  23. <Event target="saveBtn"
  24. method="${pojo/myModel.saveStuff(pojo/model/myDataObject)}"
  25. type="org.formaria.pojo.SaveBeforeActionHandler"/>
  26. </Events>
  27. </Page>

where the page is setup as normal and the class com.myproject.MyController is the normal Page implementation. The key component is the PojoPanel declaration. Here the fields have the following roles:

PojoPanel attributes

Attribute

Role

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

class

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

fieldOrder

an override for the order in which the fields are displayed

fieldValidationRule

is the name of the validation rule for the input fields

excludes

(not shown above) a list of fields not to include in the display

The event handling follows a similar rationale with the method being declared as an evaluated attribute. Aria 4.0 introduces new evaluated attribute support whereby the data model nodes can be referenced and the methods of those objects invoked. In the above setup the saveStuff method of the data model object at the path pojo/myModel is invoked. The method takes one argument and again this is a data model object - in the example it corresponds to the POJO used by the PojoPanel. The event adapter SaveBeforeActionHandler simply calls saveBoundComponentValues beofre the POJO's method is invoked.

In populating the panel the PojoPanel inspects the POJO looking for properties (as defined by the Java Bean specification) and methods (like the save method above). If a method with just a getter is found then it is assumed to be read-only.

When a property is found the PojoPanel sets up the data bindings to the appropriate POJO getter and setter methods. In addition the PojoPanel checks to see if a vocabulary has been defined for the field by checking for the existence of a model node at vocab/<<[field name]>>. If such a vocabulary is found it is assumed that the field value is part of a pick list and a drop down list/combo is displayed for that component. The PojoPanel also inspects the type of each property and tries to assign input fields of the appropriate type, for example a DateEdit input is created for a date property.

Generally the field or property name is not appropriate for display as a label or prompt and therefore the PojoPanel looks up the language files for a translation of the property name.

The above example is rendered as:

Later we will add an additional configuration file to control how the panel is rendered. The configuration will be based upon the options available within the Aria editor when creating panels from POJOs with drag and drop.

Building the Panel

In populating the panel the PojoPanel inspects the POJO looking for properties (as defined by the Java Bean specification) and methods (like the save method above). If a method with just a getter is found then it is assumed to be read-only.

Customizing the View

In some case not all the POJO fields will be required for a form, or it may be desireble to present the POJO in different ways to different users and therefore it is possible to customize the behavior with a view. The view can be loaded in two ways, either through and XML configuration file or through a data structure held within the data model.

The file structure used by the view is as follows:

Basic view file format

  1. <View>
  2. <Properties enabledByDefault="true" includeByDefault="false"
  3. fieldValidationRule="pojoFields"/>
  4. <Field name="fieldname1" visible="true" enable="true" exclude="false" type="Edit" />
  5. <Field name="fieldname2" visible="true" enable="true" exclude="false" type="Edit" />
  6. ...
  7. <Field name="fieldnameN" visible="true" enable="true" exclude="false" type="Edit" />
  8. </View>

The fields are declared in the order in which they should appear on the form and the Field element can include an arbitrary number of attributes.

Alternatively the data can be stored in an XmlElement within the data model. When stored in this way an adapter can be used to construct the data structure, adapting the field values as needed, for example

Adapting a model element to the internal view format

  1. public class ViewAdapter
  2. {
  3. public static XmlElement adapt( List<ViewDTOElement> view )
  4. {
  5. Hashtable<String,XmlElement> viewFields = new Hashtable<String,XmlElement>();
  6. int numFields = view.size();
  7.  
  8. NanoXmlElement parentNode = new NanoXmlElement( "View" );
  9. NanoXmlElement[] children = new NanoXmlElement[ numFields ];
  10.  
  11. int idx = 0;
  12. for ( ViewDTOElement entry : view ) {
  13. nanoXmlElement element = new NanoXmlElement( "field" );
  14. children[ idx ] = element;
  15. element.setAttribute( "name", entry.getId());
  16. String visibility = entry.getEvisibability();
  17. boolean enabled = !"readonly".equalsIgnoreCase( visibility );
  18. boolean visible = "editable".equalsIgnoreCase( visibility ) ||
  19. !enabled;
  20. element.setAttribute( "visible", Boolean.toString( visible ));
  21. element.setAttribute( "type", entry.getEtype() );
  22.  
  23. element.setAttribute( "label", entry.getId() );
  24. element.setAttribute( "enabled", Boolean.toString( enabled ));
  25. element.setAttribute( "exclude",
  26. Boolean.toString( "private".equalsIgnoreCase( visibility )));
  27. element.setAttribute( "order", Integer.toString( idx++ ));
  28. }
  29. for ( NanoXmlElement child : children )
  30. parentNode.addChild( child );
  31.  
  32. return parentNode;
  33. }
  34. }

PojoPanels and Dialogs

PojoPanels can be nested within dialogs using the PojoDialog class. For example:

Create a dialog from the Generic PojoDialog

  1. EditAccountDetails accDetailsDialog =
  2. (EditAccountDetails)PojoDialog.createDialog(
  3. EditAccountDetails.class,
  4. PojoDialog.READ_ACTION,
  5. "pojo/currentAccountDTO",
  6. "Account details" );
  7.  
  8. accDetailsDialog.setLogo( "hands.gif" );
  9. accDetailsDialog.setViewFile( "views/accounts" );
  10. accDetailsDialog.showDialog( this );
  11.  
  12. int status = Dialog.getLastReturnValue();
  13. if ( status == Dialog.OK_CLICKED ) {
  14. ...
  15. }

The above creates a dialog for the pojo, nesting a panel within the dialog and providing some basic CRUD operation support. The dialog uses a PojoDialogTemplate.xml file so that the appearance of the dialog can be fine tuned for individual applications.

Data Binding

When a property is found the PojoPanel sets up the data bindings to the appropriate POJO getter and setter methods. In addition the PojoPanel checks to see if a vocabulary has been defined for the field by checking for the existence of a model node at vocab/<<[field name]>>. If such a vocabulary is found it is assumed that the field value is part of a pick list and a drop down list/combo is displayed for that component. The PojoPanel also inspects the type of each property and tries to assign input fields of the appropriate type, for example a DateEdit input is created for a date property.

The alwaysDirty attribute controls how the paths are evaluated. Normally the PojoModel nodes cache their data and do not invoke the POJO method each time they are accessed. If you are trying to binding to a value that might change, such as a current user or a details node in a master-detail relationship then the underlying object might change and therefore the cache becomes out of sync. Setting the alwaysDirty attribute means that the POJO method is always evaluated and it should therefore return the correct value in such a case.

Event Handling

The event handling follows a similar rationale with the method being declared as an evaluated attribute. Aria 4.0 introduces new evaluated attribute support whereby the data model nodes can be referenced and the methods of those objects invoked. In the above setup the saveStuff method of the data model object at the path pojo/myModel is invoked. The method takes one argument and again this is a data model object - in the example it corresponds to the POJO used by the PojoPanel. The event adapter SaveBeforeActionHandler simply calls saveBoundComponentValues beofre the POJO's method is invoked.

Prior to Aria 4.0 it was not possible to access the methods of a POJO via Aria's event handling and this meant that the page class had to contain a proxy method to invoke method on the POJO. As of version 4.0 POJO methods can be invoked by specifying them as any other event

Adding a POJO event handler

  1. <Events>
  2. <Event target="saveBtn"
  3. method="${pojo/myModel.saveStuff(pojo/model/myDataObject)}"
  4. type="org.formaria.pojo.SaveBeforeActionHandler"/>
  5. </Events>

when the event handler finds a slash (/) in the method specification it assumes it is dealing with a model node. Using this approach the path pojo/myModel is assumed to be a model node wrapping a POJO which contains the method saveStuff. The method may take arguments which in turn can point to model nodes. In the above example the argument pojo/model/myDataObject is also assumed to be a model node.

As the event handling declarations are processed when the page is loaded the event handler stores a reference to the model model implementing the method and also the model nodes providing the arguments.

During the life of an application the content of the model nodes may change and therefore the event handler does not normally get the content of the node till it needs to invoke the target method. However during setup the event handler gets the POJO wrapped by the model node so as to determine the class and locate the method to be used in handling the event. Therefore the object/POJO contained within the model node should remain consistent throughout the life of the application, or at least whenever the event handler is invoked.

If you want to fix the POJO used by the event handler then prefix the path with a / to make it an absolute node reference. In such a case the POJO is accessed immediately that the event handler declaration is processed and the POJO reference is accessed and retained.

Localization

Generally the field or property name is not appropriate for display as a label or prompt and therefore the PojoPanel looks up the language files for a translation of the property name.

Validation

The input fields can be validated on a field by field basis, or for the POJO as whole. If the # fieldValidation attribute of the panel is defined then a field validation is added for each input field and the user input is validated when the input loses focus. If the appropriate styles are configured then the background colour for the inputs will reflect the validation status of the fields.

If a validation rule is specified for the panel as a validation element then the POJO is validated as a whole, that is all fields are validated. It is possible to have both styles or validations or one or the other.

The validation rule used for POJOs should map to a validation that implements the PojoValidator interface. The interface provides two methods

POJO Validation interface

  1. /**
  2.   * Validate the pojo
  3.   * @param obj
  4.   * @return the validation result
  5.   */
  6. public Object validate( Object obj );
  7.  
  8. /**
  9.   * Validate a field/property of the pojo
  10.   * @param obj
  11.   * @param fieldName
  12.   * @return the validation result
  13.   */
  14. public Object validate( Object obj, String fieldName );

The PojoValidator is a basic implementation of this interface. Using this object is possible to wrap third party validation frameworks, as during the validator setup the PojoValidator instance is passed the dataPath attribute, and using the attribute value the validator looks up the POJO upon which it will operate prior to performing the validations.

Spring support

Leveraging the POJO support and runtime form generation enables Aria to used effectively for Spring applications. Spring's remoting technology can expose beans to a remote client and Aria can pick these beans up and create forms and tables for the beans. The PojoPanel described above makes it possible to quickly and easily configure the UI for these beans with little or no coding.

Going one step further it is also possible to provide hints to the panel generation to control various aspects of the UI including styling other attributes of the appearance and behavior. At the core of the support for Spring is the use of reflection and bean inspectors to determine the field types and bean methods. However additional glue logic is provided on the basis that the beans and their attributes are named and handled consistently. Therefore a bean can be bound to UI component and validated against other Spring beans provided that this use of property names is consistent.

With so much provided by Aria creating a Spring based application requires little other than wiring the beans together. The most important requirement in this process is to wire the Spring beans into Aria's data model. Normally an application will have some class of root access point, service facade or some object that ties the various beans together into a session or context of some sort. Aria needs this object to be mapped into the Aria data model, and this can be done as follows:

Setting up Spring support

  1. public class MyPojoRoot extends PojoContext implements PojoValidator
  2. {
  3. ...
  4.  
  5. /**
  6.   * Instantiated by the POJO bindings at startup.
  7.   */
  8. public MyPojoRoot()
  9. {
  10. try {
  11. applicationContext = new ClassPathXmlApplicationContext( new String[] {
  12. "spring-config-file-A.xml",
  13. "spring-config-file-B.xml" }
  14. );
  15.  
  16. serviceInterface = (SomeBean)applicationContext.getBean( "someBean" );
  17. }
  18. catch ( BeansException be ) {
  19. be.printStackTrace();
  20. }
  21. }
  22.  
  23. public static MyPojoRoot getInstance( Project project )
  24. {
  25. MyPojoRoot rootPojo = (MyPojoRoot)project.getObject( "MyPojoRoot" );
  26. if ( rootPojo == null )
  27. rootPojo = new MyPojoRoot();
  28.  
  29. rootPojo.setProject( project );
  30.  
  31. return rootPojo;
  32. }
  33.  
  34. public Object getRoot()
  35. {
  36. return getInstance( project );
  37. }
  38.  
  39. public void setProject( Project p )
  40. {
  41. project = p;
  42. pageMgr = project.getPageManager();
  43. project.setObject( "MyPojoRoot", this );
  44. }
  45.  
  46. public void configure( URL configFileURL )
  47. {
  48. rootModel = project.getModel();
  49. }
  50.  
  51. public void modelLoaded()
  52. {
  53. }
  54.  
  55. /**
  56.   * Lookup some other bean
  57.   */
  58. public IOtherBean getOther()
  59. {
  60. if ( other == null )
  61. other = (IOtherBean)serviceInterface.getOtherBean();
  62.  
  63. return other;
  64. }
  65.  
  66. //-Validation----------------------------------------------------------------
  67. /**
  68.   * Validate the pojo
  69.   * @param obj
  70.   * @return the validation result
  71.   */
  72. public Object validate( Object obj )
  73. {
  74. return serviceInterface.validate( obj );
  75. }
  76.  
  77. /**
  78.   * Validate a field/property of the pojo
  79.   * @param obj
  80.   * @param fieldName
  81.   * @return the validation result
  82.   */
  83. public Object validate( Object obj, String fieldName )
  84. {
  85. return serviceInterface.validate( obj, fieldName );
  86. }
  87. }

The above code also handles the interface to the validation of POJOs.

Once the root of the POJO model has been added it may be convenient to wrap the root POJO with some additional access functions so that various nodes can be more eassily accessed. For example an application often uses the concept of a current node or current object and this may not be provided explicitly by the POJOs. The root node or model nodes hanging of the root might be a good point to wrap the POJOs and add additional functionality. The root node is also a good point to load Spring's configuration files