Viewer Framework for Financial Math Framework

Table of Contents


Overview

The purpose of this framework is to provide a simple mechanism to execute financial applications through either a stand-alone Swing client application or a servlet and also to illustrate the following programming principles:


Directory Structure

The framework includes the following files and directories:


Running the Swing Application

  1. Open a command window.

  2. Change to the top level framework directory; i.e., the directory above the config subdirectory.

  3. Type the following:
      java -jar lib/qcf.jar
    
  4. Pick something from the menu. If no input is required the process will be executed immediately. Otherwise, press the "Run" button after entering your data.


    Accessing the Servlet

    A servlet container like Jakarta Tomcat is required to run a servlet. You can obtain the latest version from the Apache web site (
    www.apache.org). You can also use the following test web site:
    
       http://www.yorkieworks.com:8180/qcf/viewer
    
    
    This will display a page with the list of available processes. Click on one of the hyperlinks to execute the process.


    Model-View-Controller Paradigm

    When writing gui applications, the Model-View-Controller Paradigm is the generally accepted programming practice. Under this technique, the model or data is separate from its view or gui representation. The advantage of this approach is that the same data can be represented in different ways. In the case of the financial framework, both a Swing gui and servlet HTML view are provided for the same data. Both of these views use the same calculation and flow control classes.

    In order for this approach to work, it is important to separate the calculation logic from the code which handles the gui display. In the case of the framework, the calculation logic is stored in the qcf.modules and qcf.examples packages; the calculation controller in the package, edu.gatech.math.qcf. The Swing gui code is in the package, edu.gatech.math.qcf.gui and the servlet code is in the package, edu.gatech.math.qcf.servlet.


    Java Interface Facilitates a Plug-in Architecture

    Through a Java interface, you define a set of methods and these methods specify the standard behavior of any object which implements the interface. For example, the Java interface Runnable defines a single method run. Any class which implements Runnable can be executed in a separate thread. In the case of the financial framework, all calculation modules must implement the Module interface. This interface also has a single method, perform. This method has the following method signature:
    
      DataTable [] perform(Properties props, DataTable [] data) 
         throws ModuleException;
    
    
    This signature indicates that the method must accept a Java properties object and also an array of DataTable objects. Further, it must return an array of DataTable objects. If it encounters any serious problemm, then it should throw a ModuleException.

    The calculation controller knows that all calculation modules implement this interface; therefore, it can execute them individually and chain them together without any knowledge of the inner workings of the modules themselves. Further, new modules can be created and just plugged into the framework by specifying their class name through an an XML configuration file. The "How-To" section later in this document shows you how to build your own module and add it to the framework.


    Java Properties for Configuration Data

    Java Properties represent a "key-value" mapping which is a convenient tool for storing configuration information and simple data. The Java Properties class is an extension of the Java Hashtable class with methods tuned to read files and work with Strings rather than objects. Here is a the properties file used to control one of the modules in the framework:
    
    #
    # Properties for FileTableReader module
    #
    filePrefix=/usr/local/qcf/data/
    fileSuffix=.csv
    
    
    
    The value on the left of the equals is the "key" and the value on the right of the equals is the "value". So, in the above example, the key "fileSuffix" has the value ".csv". You can create a Java Properties object by loading data from a file or by storing entries manually. See the JDK JavaDoc available from Sun for further details.


    XML for Data and Process Control

    XML is similar to HTML in that it is a tag language. However, unlike HTML, XML has no predefined tags. XML is an acronym for "Extensible Mark-up Language"; it is standard of the W3C (World-Wide Web Consortium). XML originated from the work of Jon Bosak of Sun Microsystems working with the W3C.

    Since XML has no pre-defined tags, you define the tags that you need to fit the problem that you are trying to solve. A set of tags can be constrained through your own Document Type Definition (DTD) and then applied to a set of XML documents. XML Schema is a newer and better technique for editing XML; however, not all tools currently support Schema. Through XSL, you can define style sheets to format your XML content in various ways.

    There are a few rules that you must follow when building XML documents in order to make them well-formed:

    • All documents must start with:
        <?xml version="1.0"?>
      

    • Every document must contain a single "root" node.

    • Every opening tag (<XXX>) must have a closing tag (</XXX>). Alternatively, a single tag can both open and close (<XXX/>).

    • Attribute values must be enclosed in either a pair of single quotes or double quotes.

    In addition to the flexibility of being able to define your own tags and editing criteria, XML takes a tree-structured approach to the representation of data. Thus, it fits best when the data that you are trying to represent is also tree structured.

    Lets consider the XML used to control the Financial Framework as a concrete example. Here is a skeleton of the actual control file (viewer.xml):

    
    <?xml version="1.0"?>
    <!DOCTYPE Viewer SYSTEM "viewer.dtd">
    
    <Viewer>
    
    <!-- This element defines the menu of processes which the user can execute -->
    <Menu>
    
      <MenuItem label="Average List" description="Average numbers in a list"
         process = "average1" />
      <MenuItem label="Average Two" description="Averages Two Numbers"
         process="average2" />
      
      ...
    
    
    </Menu>
    
    <!-- This section defines a series of display layouts for use by processes -->
    
    <DisplayLayout id="1" columns="free">
       <Input id="data1" control="JTextField" label="First: " 
       description="Enter the first field" type="numeric"  width="10"/>
        <Input id="data2" control="JTextField" label="Second: " 
       description="Enter the second field" type="numeric" width="10"/>
    </DisplayLayout>
    
    <DisplayLayout id="2" columns="free">
      <Input id="tdata" control="JTable" rows="4">
        <Column label="" editable = "false">
           <Row value="Value #1" />
           <Row value="Value #2" />
           <Row value="Value #3" />
           <Row value="Value #4" />
        </Column>
        <Column label="Number" type="numeric" editable="true" />
      </Input>
    </DisplayLayout>
    
    ...
    
    <Process id="average1" description = "Average of A List">
      <Input type="gui" layout="2" />
      <Module class="qcf.examples.AverageTable" />
    </Process>
    
    <Process id="average2" description="Average Two Numbers" >
      <Input type="gui" layout="1" />
      <Module class="qcf.examples.AverageTwo" />
    </Process>
    
    ...
    
    </Viewer>
    
    

    Here are some things to note about this file:

    • Editing constraints are provided by "viewer.dtd".
    • The root node is Viewer.
    • Under the Viewer root node, there are the following child nodes:
      • Menu - one only
      • DisplayLayout - one or more
      • Process - one or more
    • The Menu node contains one or more MenuItem nodes.
    • Each MenuItem node defines its data through the following three attributes:
      • label
      • description
      • process
    • The DisplayLayout node includes one or more Input nodes.
    • Each Input node may define its data through both attributes and child nodes.
    • The Process node includes one Input node which identifies the type of input and a layout id which matches the id of one of the display layout items.
    • The Process node also contains one or more Module nodes which specify the name of the class which will perform the processing.

    In order to read an XML file, you need an XML parser. Various XML parsers are available and most of these support the two different approaches to XML parsing:

    • DOM parser - This type of parser reads in the entire document and stores the result in a document tree.
    • SAX parser - This triggers an event as each XML component is read and it is up to the user to store the data in a suitable data model.

    In addition to the parser, there are various add-ons which make XML parsing more Java friendly. For JDK 1.4 and later, XML parsing will become a core part of the API. For JDK 1.3 and earlier, the following jar files provide you what you need to get started with XML:

    • crimson.jar - basic XML parser from Sun
    • jaxp.jar - XML parsing framework form Sun
    • xalan.jar - XSL (stylesheet processor) from Apache-Jakarta but frequently packaged with crimson.jar and jaxp.jar.
    • jdom.jar - Simplified, java friendly API for accessing XML.


    Building and Installing A Module

    Sample Source Code

    The package, "qcf.examples", includes some simple examples of how to build a module. The package "qcf.modules" contains more sophisticated calculations. This section will focus first on the "AverageTwo" class. As its name implies, it takes two input numbers and divides them by 2 in order to calculate the average. Below is the source code:

    
    package qcf.examples;
    
    import edu.gatech.math.qcf.*;
    
    import java.util.*;
    
    
    /**
     * This class illustrates how to write a simple calculation module
     * that just computes the average of two numbers which are provided
     * by a Java properties file.
     */
    public class AverageTwo implements Module {
    
       /** First number to average */
       private double data1;
    
       /** Second number to average */
       private double data2;
    
       /** Default label */
       private String label = "Average";
    
    
       /**
        * This method performs the calculation which involves
        * computing the average of the two numbers provided
        * through the properties object.  It returns the input data
        * as the first output DataTable and the result as the
        * second output DataTable.
        *
        * @param   props   Properties with data and optional header.
        * @param   table   Data table (not used).
        *
        * @return          Input data plus calculated result.
        */
       public synchronized DataTable [] perform(Properties props,  
                                                DataTable [] table) 
          throws ModuleException {
    
          // Get the data from the properties.
          parseProperties(props);
    
          // Store the input in a DataTable using this class name as the ID.
          // The DataTable has two rows and one column.  This is not required
          // but might be useful if you want to chain modules together.
          String cid = getClass().getName();
          DataTable input = new DataTable(cid+":input", 2, 1);
       
          input.setColumnName("Input", 0);
          input.setValueAt(data1, 0, 0);
          input.setValueAt(data2, 1, 0);
         
          // Compute the average.
          double avg = (data1+data2)/2;
    
          // Store the result in the output table.  
          DataTable results = new DataTable(cid+":output", 1, 1);
          results.setColumnName(label, 0);
          results.setValueAt(avg, 0, 0);
          
          // Return both the input and the output.
          DataTable [] all = {input, results};
          return all; 
       }
    
    
       /**
        * This method extracts the numbers to average and the
        * optional output label.
        *
        * @param   props   Properties file.
        */
       private void parseProperties(Properties props) throws ModuleException {
    
          String tmp;
    
          // Get and edit the first number.
          tmp = props.getProperty("data1");
          if (tmp == null)
             throw new ModuleException("Missing first number.");
          try {
             data1 = Double.parseDouble(tmp);
          }
          catch(Exception e) {
             throw new ModuleException("Invalid first number: "+e.getMessage());
          }
    
          // Get and edit the second number.
          tmp = props.getProperty("data2");
          if (tmp == null)
             throw new ModuleException("Missing second number.");
          try {
             data2 = Double.parseDouble(tmp);
          }
          catch(Exception e) {
             throw new ModuleException("Invalid second number: "+e.getMessage());
          }
    
          // Get the optional header.
          tmp = props.getProperty("header");
          if (tmp != null)
             label = tmp;
    
       }
    }
    
    


    Explanation of the Example

    Our AverageTwo class implements the Module interface so that it can be plugged into the Financial Framework. Therefore, it must provide a perform method. The first thing that the perform method does is to extract the input data from the Java Properties object. This is accomplished by invoking the parseProperties private method. As is typical when getting data, most of the work is related to making sure that the data is valid. If the data is missing or invalid, then the method throws a ModuleException.

    Next the input data is packaged into the DataTable object. This part is optional; however, it may be useful to pass along the input data if modules are being chained together.

    The actual work is done through this one line of code:

    
       double avg = (data1+data2)/2;
    
    
    Then this result must be stored in a results DataTable object, which is what the remainder of the code does.


    DataTable Class

    The DataTable object is a something like a two-dimensional array but it is capable of storing any type of object by row and column. It also stores the column label for each column plus an "id" and "description" data element that are useful in visual displays. To create a new DataTable, you must specify the "id", number of rows, number of columns. For example, to create data table "Puff" with with 2 rows and 1 column:
    
       DataTable dataTable = new DataTable("Puff", 2, 1);
    
    
    Indexing in Java is zero-based. To add an element to row 1 and column 0:
    
       dataTable.setValueAt(myValue, 1, 0);
    
    
    "myValue" may be a double or long primitive or any object.

    To retrieve the same element:

    
       Object obj = dataTable.getValueAt(1, 0);
    
    
    If your table contains double primitives, then the result of this call will be a Double object. In this case, it would be more convenient to use the following:
     
      double d = dataTable.getValueAsDouble(1, 0);
    
    
    The DataTable class is similar to the AbstractTableModel class provided by the Java Swing package. See the
    JavaDoc for all of the methods available for DataTable.


    Updating "viewer.xml"

    In order to make the framework aware of your module, you must do the following:

    1. Add a new MenuItem for your module. Here is the MenuItem for the AverageTwo:
      
      <MenuItem label="Average Two" description="Averages Two Numbers"
           process="average2" />
      
      
    2. Add a Process node for the new module. The id attribute of this node must match the "process" attribute of the MenuItem. Any string may be used.
      
      <Process id="average2" description="Average Two Numbers" >
        <Input type="gui" layout="1" />
        <Module class="qcf.examples.AverageTwo" />
      </Process>
      
      
    3. If your module requires interactive user input, then define the DisplayLayout node. The "id" in the DisplayLayout node must match the "layout" attribute for the Input node. These id's may be any String value.
       
      <DisplayLayout id="1" columns="free">
         <Input id="data1" control="JTextField" label="First: " 
         description="Enter the first field" type="numeric"  width="10"/>
          <Input id="data2" control="JTextField" label="Second: " 
         description="Enter the second field" type="numeric" width="10"/>
      </DisplayLayout>
      
      


    Testing Your Module

    As soon as viewer.xml has been updated, you should be able to test your new module. The simplest way to test is to use the stand-alone Swing client as described in an earlier section. See the next section for some screen shots from the Swing client using "AverageTwo".

    In order to test your change through the servlet, you may need to recycle the servlet container. Sample servlet screen shots are also included in the next section.


    Swing Client: Screenshot #1


    Swing Client: Screenshot #2


    Swing Client: Screenshot #3


    Servlet: Screenshot #1


    Servlet: Screenshot #2


    Servlet: Screenshot #3