You are hereAria User Guide / Section V: Case Studies / Case Study: Mortgage

Case Study: Mortgage


By luano - Posted on 14 January 2009

Introduction

The Mortgage application which is outlined as part of this case study is taken from the tutorial which is available for download from the Aria project on the sourceforge site. The intention of the tutorial is the make the developer familiar with development elements of the Aria/Aria framework. The development of the application is split over several different tutorials which gives the developer a fully working version of the application at the end of each step.

In this case study we are going to examine the elements which make up the application in each tutorial. In summary the tutorials are as follows

Introduction

This step shows how to set up the basic structure of a Aria application and what files and libraries need to be included. It creates some simple pages and shows how to navigate the application. It shows how to add page events, validations, data bindings and dynamic bindings. The application is completely localized in the final step and the model is used to output captured data.

Advanced

This step builds upon the introductory tutorial and provides the user with a more generic navigation which allows new pages to be added very easily. It also shows how library functions can be used to help with the navigation. Custom validations are created which are loaded via a custom ValidationFactory. Framesets are customised and component registration is used to develop and load custom components. Custom dialogs are created which report on the types of errors being generated. Model data is saved from and restored to the model showing how easy it is to create applications where projects can be created and opened.

Aria Editor

This tutorial really replicates what was done in the first step except that the development is carried out with the help of the Aria Editor editor.

Aria

In introducing Aria the application is makes use of the routes which are available in the Aria library. This allows the developer to create applications which can easily connect to a servlet server making use of authentication and session management services. Data can be cached on the client and the application can be used to work with them offline and then synchronise with the server. Custom ServiceProxy classes are created which are used in transforming mode data.

Introduction tutorial

This tutorial begins by showing the project structure for a typical Aria application as shown in the diagram below.

The run.bat file contains the text below.

The run.bat file

  1. java -cp .;lib;AriaRuntimeCommon.jar;images;pages;
  2. resources;build;classes org.formaria.swing.Applet

The application is made up of frames which are defined in the frames.xml file as shown below.

The frames.xml file

  1. <FrameSet>
  2. <Frame name="banner" constraint="NORTH" content="banner" width="640" height="96"/>
  3. <Frame name="content" constraint="CENTER" content="Welcome" width="640"
  4. height="288"/>
  5. <Frame name="navPanel" constraint="SOUTH" content="navPanel"
  6. width="640" height="96"/>
  7. </FrameSet>
Navigation

The project navigation was handled by the navPanel page in the navPanel frame. In order to do this two images where added to the navPanel page as shown below

navPanel.xml page

  1. <Page class="org.formaria.mortgage.NavPanel" style="Heading">
  2. <Components>
  3. <Image name="nextButton" x="564" y="51" w="19" h="18" content="right.gif"/>
  4. <Image name="prevButton" x="60" y="54" w="19" h="18" content="left.gif"/>
  5. </Components>
  6. <Events>
  7. <Event method="nextPage" target="nextButton" type="MouseHandler"/>
  8. <Event method="prevPage" target="prevButton" type="MouseHandler"/>
  9. </Events>
  10. </Page>

Two images are added and each has a MouseHandler event added to them. The nextPage and prevPage functions are defined in the NavPanel class as follows.

The NavPanel class

  1. package org.formaria.mortgage;
  2.  
  3. import org.formaria.aria.*;
  4.  
  5. public class NavPanel extends Page
  6. {
  7.  
  8. public void nextPage()
  9. {
  10. if ( wasMouseClicked())
  11. navigateToPage( "next" );
  12. }
  13.  
  14. protected void navigateToPage( String key )
  15. {
  16. Page target = ( Page )( ( Target ) pageMgr.getTarget( "content" ) ).getComponent( 0 );
  17. String dest = ( String )target.getAttribute( key, null );
  18. if ( dest != null )
  19. pageMgr.showPage( dest, "content" );
  20. }
  21.  
  22. public void prevPage() {
  23. if ( wasMouseClicked() )
  24. pageMgr.showPrevious();
  25. }
  26. }

This navigation depended on the presence of the next attribute in the main content page

Bindings

Dynamic binding was used to address different nodes within the data model. On the personal page the binding are declared as follows.

The bindings on the personal page

  1. ...
  2. <Data>
  3. <Bind target="firstnameText" source="mortapp/${getCustomerID()}/firstname"
  4. output="mortapp/${getCustomerID()}/firstname" />
  5. <Bind target="surnameText" source="mortapp/${getCustomerID()}/surname"
  6. output="mortapp/${getCustomerID()}/surname" />
  7. <Bind target="dobText" source="${getCustomerID()}/dob"
  8. output="mortapp/${getCustomerID()}/dob" />
  9. <Bind target="titleList" source="defaults/titleList" output="mortapp/${getCustomerID()}/title" />
  10. </Data>
  11. ...

These bindings depend on the callback function getCustomerID in the Personal class as follows.

The callback function in the Personal page

  1. public String getCustomerID()
  2. {
  3. return "customer" + ( String )currentCust.get();
  4. }

This class takes care of tracking which customer is being currently processed allowing numerous customers to be added to the model in their own specific nodes.

Saving the model

The model data was then saved within the Finish class pageActivated function.

The Finish class

  1. public class Finish extends Page
  2. {
  3. public void pageActivated() {
  4. try {
  5. String path = System.getProperty( "user.dir" ) + File.separator + "save.xml";
  6. FileOutputStream fos = new FileOutputStream( path );
  7. OutputStreamWriter osw = new OutputStreamWriter( fos, "UTF8" );
  8. BufferedWriter bw = new BufferedWriter( osw );
  9. DataSource.outputModel( bw, ( ( DataModel ) rootModel.get( "aria_state/mortapp" ) ) );
  10. bw.flush();
  11. bw.close();
  12. }
  13. catch (Exception e) {
  14. System.out.println( "error" );
  15. }
  16.  
  17. }
  18. }

This provided us with the following output.

Model data saved from the application

  1. <data id="mortapp">
  2. <data id="customer1">
  3. <data value="Joe" id="firstname" />
  4. <data value="Bloggs" id="surname" />
  5. <data value="14/12/1975" id="dob" />
  6. <data value="Mr" id="title" />
  7. </data>
  8.  
  9. <data id="customer2">
  10. <data value="Josephine" id="firstname" />
  11. <data value="Bloggs" id="surname" />
  12. <data value="01/04/1972" id="dob" />
  13. <data value="Mrs" id="title" />
  14. </data>
  15.  
  16. <data id="finance">
  17. <data value="250000" id="propvalue" />
  18. <data value="200000" id="mortamt" />
  19. </data>
  20. </data>
Localization

The entire application was localized through the use of language specific resource bundles. For example the personal page was localized simply by changing the content attributes for the components.

The localized personal page

  1. <Page next="personal" class="org.formaria.mortgage.Welcome" resource="">
  2. <Components>
  3. <Label x="10" y="5" w="80" h="20" style="prompt" content="WEL_LANG"
  4. alignment="Left" opaque="true"/>
  5. <Combo name="languageList" x="90" y="5" w="120" h="20"/>
  6. <Label x="130" y="84" w="350" h="60" style="Heading" content="WEL_TEXT"
  7. alignment="Left" opaque="true"/>
  8. <Panel x="0" y="150" w="650" h="50" style="banner/prompt">
  9. <RadioButton name="soleRadio" x="226" y="15" w="100" h="20" style="banner/prompt"
  10. content="WEL_SOLE" alignment="Leading"/>
  11. <RadioButton name="jointRadio" x="330" y="15" w="100" h="20" style="banner/prompt"
  12. content="WEL_JOINT" alignment="Leading"/>
  13. </Panel>
  14. </Components>
  15. ...

The resources were entered into two resource bundles for English and French.

Extract from en.properties

  1. WEL_LANG=Language
  2. WEL_TEXT=Welcome to ABC Bank's mortgage application.
  3. Before we proceed please note the following...
  4. WEL_SOLE=Sole
  5. WEL_JOINT=Joint
  6. BAN_TITLE=ABC Bank Mortgage Application
  7. BAN_CLOSE=Close Application
  8. PER_HEADING_1=Personal Details (First Applicant)
  9. PER_HEADING_2=Personal Details (Second Applicant)
  10. ...

And the French translation:

Extract from fr.properties

  1. WEL_LANG=Langue
  2. WEL_TEXT=Bienvenue ? la demande de pr?t hypoth?caire de l'hypoth?que de la banque de ABC.
  3. Avant que nous veuillez proc?der note le suivant...
  4. WEL_SOLE=Unique
  5. WEL_JOINT=Joint
  6. BAN_TITLE=Demande de pr?t hypoth?caire D'Hypoth?que De Banque de ABC
  7. BAN_CLOSE=Application ?troite
  8. PER_HEADING_1=D?tails Personnels (Premier Demandeur)
  9. PER_HEADING_2=D?tails Personnels (Deuxi?me Demandeur)
  10. ...

A combo box was then placed on the welcome page which allowed the user to switch language. The selection of a language invoked the following code in the Welcome class.

Language switching code

  1. public void changeLanguage()
  2. {
  3. if ( langClicked ) {
  4. String lang = ( String )languageList.getSelectedObject();
  5. pageMgr.reset();
  6. String langCode = lang.compareTo( "English" ) == 0 ? "en" : "fr";
  7. project.setStartupParam( "Language", langCode );
  8. pageMgr.loadFrames( "frames", true );
  9. }
  10. }

This completed the introductory tutorial and the resulting application can be seen below

Advanced Tutorial

This tutorial uses the application created in the introductory tutorial and begins by adding custom validations and a custom validation factory. The custom validation calls a function within the Page and returns the result of it. The custom validation is shown below.

The custom FunctionValidation factory

  1. public class FunctionValidation extends BaseValidator {
  2.  
  3. public FunctionValidation() {
  4. }
  5.  
  6. public void validate( Object c, boolean forceMandatory ) throws Exception {
  7. Integer ret = ( Integer ) invokeMethod();
  8. if ( ret.intValue() > LEVEL_IGNORE ) {
  9. errorLevel = ret.intValue();
  10. throwException();
  11. }
  12. }
  13.  
  14. public void setup( XmlElement element )
  15. {
  16. message = element.getAttribute( "msg" );
  17. super.setup( element );
  18. }
  19. }

In order to use this validation a custom validation factory need to be created as shown below.

The custom ValidationFactory class

  1. public class ValidationFactory extends ValidationFactory {
  2.  
  3. ...
  4.  
  5. public Validator getValidation( String validationName, Method m, int mask, Object page )
  6. {
  7. XmlElement ele = ( XmlElement ) validations.get( validationName );
  8.  
  9. String type = ele.getAttribute( "type" );
  10.  
  11. if ( type.compareTo( "function" ) == 0 ) {
  12. FunctionValidation validator = new FunctionValidation();
  13. validator.setName( validationName );
  14. validator.setValidationMethod( m, page );
  15. validator.setup( ele );
  16. return validator;
  17. }
  18. else {
  19. BaseValidator validator = (BaseValidator)super.getValidation( validationName,
  20. mask, page );
  21. validator.setup( ele );
  22. return validator;
  23. }
  24. }
  25.  
  26. }

The getValidation function checks to see if the type being created is function . If it is it creates a new instance of the FunctionValidation class and returns it. If it isn't the super getValidation function is called in order to retrieve the validation.

In order for Aria to use this validation factory the following line needed to be added to the startup properties.

Startup property for the custom validation factory

  1. ValidationFactory=org.formaria.mortgage.validation.ValidationFactory

Now the welcome page was amended to use the new validation.

XML declaration for the function validation

  1. <Validation rule="createApp" target="jointRadio" method="checkCreateSetup"/>

And the welcome class was amended to include the checkCreateSetup function

The checkCreateSetup function

  1. ...
  2. public Integer checkCreateSetup()
  3. {
  4. int level = BaseValidator.LEVEL_IGNORE;
  5. if ( !jointRadio.isSelected() &amp;&amp; !soleRadio.isSelected() )
  6. level = BaseValidator.LEVEL_ERROR;
  7.  
  8. return new Integer( level );
  9. }
  10. ...

This function checks to see that at least one of the radio buttons on the welcome page has been selected. If everything is OK then LEVEL_IGNORE is returned otherwise LEVEL_ERROR is returned and the error message is displayed.

Bindings

The tutorial now goes on to show how the data model can be reopened once saved. This required the use of a custom data binding which was added to a list component on the welcome page.

Binding to the list component on the welcome page

  1. ...
  2. <List name="fileList" x="0" y="0" w="200" h="100" style="data"/>
  3. ...
  4. <Bind target="fileList" source="applications" output="temp/filename"/>
  5. ...

Now the saved files needed to be listed when the pageCreated function was called.

Loading files into the model

  1. public void pageCreated()
  2. {
  3. ...
  4. loadFiles();
  5. }
  6. ...
  7. private void loadFiles()
  8. {
  9. String dir = System.getProperty( "user.dir" );
  10. appsMdl = ( BaseModel )rootModel.get( "applications" );
  11. File folder = new File( dir + ";;files" );
  12. File files[] = folder.listFiles();
  13. for ( int i = 0; i < files.length; i++ ) {
  14. String name = files[ i ].getName();
  15. BaseModel filMdl = new BaseModel ( appsMdl, name, name );
  16. }
  17. updateBindings();
  18. }
  19. ...

Child nodes containing the filenames are added to the applications model node and the updateBindings function of the page is called so that the list will refresh. Now the selected file could be opened and restored into the model as follows.

Opening and restoring the model state

  1. ...
  2. public void openFile()
  3. {
  4. String filename = ( String )fileList.getSelectedObject();
  5.  
  6. // Not shown here but simply reads the file into a string.
  7. String contents = getFileContents( filename );
  8. restoreState( contents );
  9.  
  10. updateBindings();
  11. }
  12.  
  13. private void restoreState( String contents )
  14. {
  15. ( ( BaseModel )rootModel.get( "aria_state/mortapp" ) ).clear();
  16. ( ( BaseModel )rootModel.get( "mortapp" ) ).clear();
  17.  
  18. StringReader sr = new StringReader( contents );
  19. XmlElement ele = XmlSource.read( sr );
  20.  
  21. if ( ele != null ) {
  22. OptionalDataSource ds = new OptionalDataSource();
  23. ds.loadTable( ele, rootModel );
  24. ds.loadTable( ele, ( ( BaseModel )rootModel.get( "aria_state" ) ) );
  25. }
  26.  
  27. }
  28. ...

The restoreState function is really of most importance here. It initialises the aria_state and mortapp nodes. It then loads the model into both of these. Again, updateBindings is called so as to refresh the state of the radio buttons on the welcome page.

The list can be seen on the welcome page below.

Navigation

The navigation of the applicaton was improved by making use of the library functions which are part of Aria. To do this the navPanel page was changed to reference functions in the Navigator class as follows.

The amended MouseHandler events in the navPanel page

  1. <Events>
  2. <Event method="org.formaria.mortgage.navigate.Navigator.next"
  3. target="nextButton" type="MouseHandler"/>
  4. <Event method="org.formaria.mortgage.navigate.Navigator.previous"
  5. target="prevButton" type="MouseHandler"/>
  6. </Events>

The Navigator page was created as a Singleton instance class and included the static functions next and previous . The pages to be included in the navigation were defined in a new navigation dataset as follows.

The navigation.xml file

  1. <dataset id="navigation">
  2. <page id="welcome" value="welcome" />
  3. <page id="personal" value="personal" startrepeat="true" title="PER_HEADING"/>
  4. <page id="bank" value="bank" endrepeat="true" title="BANK_HEADING"/>
  5. <page id="finance" value="finance" title="FINANCE_TITLE"/>
  6. <page id="finish" value="finish" />
  7. </dataset>

The Navigator class took care of keeping track of where the application was in the navigation sequence and of repeating the relevant pages where for the amount of customers being processed. Now each of the pages in the navigation could use the same class to return the correct customer id.

The generic CustomerPage class

  1. public class CustomerPage extends Page
  2. {
  3. public String getCustomerID()
  4. {
  5. return "customer" + Navigator.getInstance().getCurrentCust();
  6. }
  7.  
  8. }

With this mechanism the navigation was greatly simplified and pages could be added very easily by referencing them in the navigation file. The amount of customers which could be process is now endless and all taken care of by the very simple Navigation class.

Framesets and component registration

The framesets in were amended to include a status frame to show the user exactly where they were in the application navigation. A new frame declaration was added to the frames.xml file as follows.

The new declaration in frames.xml

  1. <FrameSet>
  2. <Frame name="banner" constraint="NORTH" content="banner"
  3. width="640" height="96"/>
  4. <Frame name="appstatus" constraint="WEST" content="appstatus"
  5. width="150" height="500" />
  6. <Frame name="content" constraint="CENTER" content="welcome"
  7. width="490" height="288" />
  8. <Frame name="navPanel" constraint="SOUTH" content="navPanel"
  9. width="640" height="96"/>
  10. </FrameSet>

This frame was made invisible on the welcome page and visible on subsequent pages. It's visibilty was controlled by the Navigation class.

A custom component was then created which listed all of the pages to be processed and highlighted the current one. It was declared in the components.xml file as follows.

The components.xml file with the declaration for NavViewer

  1. <Components>
  2. <Component name="NavViewer" class="org.formaria.mortgage.comp.NavViewer">
  3. <Property name="Style" mode="Normal" type="both"/>
  4. <Property name="DefaultStyle" mode="Normal" type="both"/>
  5. <Property name="CurrentStyle" mode="Normal" type="both"/>
  6. <Property name="DoneStyle" mode="Normal" type="both"/>
  7. <Property name="NavModelPath" mode="Normal" type="both"/>
  8. </Component>
  9. </Components>

Now a NavViewer instance was added to the navPanel page as follows.

The AppStatus.xml file

  1. <Page class="org.formaria.mortgage.AppStatus" style="Heading" resource="">
  2. <Components>
  3. <NavViewer name="navViewer" x="0" y="0" w="145" h="400" Style="status"
  4. DefaultStyle="status/default" CurrentStyle="status/current"
  5. DoneStyle="status/done" NavModelPath="navigation"/>
  6. <Label x="149" y="0" w="1" h="500" style="banner" opaque="true"/>
  7. </Components>
  8. </Page>

Now the NavViewer class could take care of displaying navigation information as below.

Dialogs

A custom dialog was introduced to display errors and warnings with different icons. This new dialog also allowed the user to proceed if only warnings were generated. The exception handler needed to be amended to store the errors and warnings in their own respective vector objects as below.

The ExceptionHandler class

  1. public boolean handleException( Object comp, Exception ex, Object xvalidator )
  2. {
  3. ...
  4. if ( validator.getLevel() == validator.LEVEL_ERROR )
  5. errors.add( currentPage.translate( msg ) );
  6. else
  7. warnings.add( currentPage.translate( msg ) );
  8.  
  9. return true;
  10. }
  11.  
  12. public int accumulateMessages( boolean start, int level )
  13. {
  14. pageValidation = start;
  15. if ( pageValidation ){
  16. errors = new Vector();
  17. warnings = new Vector();
  18. } else {
  19. if ( errors.size() > 0 || warnings.size() > 0 )
  20. return getDialogResult( level );
  21. }
  22. return level;
  23. }
  24.  
  25.  
  26.  
  27. private int getDialogResult( int level )
  28. {
  29. ErrorMessage dlg =
  30. (ErrorMessage)ProjectManager.getPageManager().loadPage( "ErrorDlg", false );
  31. dlg.setErrorMessages( errors, warnings );
  32. int result = dlg.showDialog( currentPage );
  33. if ( level == Validator.LEVEL_WARNING )
  34. return result == Validator.LEVEL_IGNORE ? Validator.LEVEL_IGNORE : level;
  35. else
  36. return Validator.LEVEL_ERROR;
  37. }

The getDialogResult function creates the new ErrorMessage dialog and passes in the two vectors. The return value determines whether the page can proceed or not depending on what button the user clicked.

In the ErrorMessage class the relevant return value is set when one of the buttons is clicked as follows.

The ErrorMessage class

  1. ...
  2. public int showDialog( Container owner )
  3. {
  4. super.showDialog( owner );
  5. return ret;
  6. }
  7.  
  8. public void cancelClicked()
  9. {
  10. ret = BaseValidator.LEVEL_WARNING;
  11. super.closeDlg();
  12. }
  13.  
  14. public void okClicked()
  15. {
  16. ret = BaseValidator.LEVEL_IGNORE;
  17. super.closeDlg();
  18. }
  19. ...