[an error occurred while processing this directive]
> developer > web app development
Creating a DataView with Struts and JSP
by Taylor Cowan, Principal Engineer, Novell
Date Created: 2001-05-29 09:38:00.000
  Summary of DataView Functionality
  Replacing the AgiRowCursor
  Creating the Model
  Creating the View
  Creating the Controller
  Deploying the Example DataView

With the advent of J2EE, the SilverStream developer community has been faced with a dilemma: either continue developing applications in the familiar yet obsolete page designer, or move ahead into the often daunting world of J2EE. In spite of the learning curve, you'll find that you can produce the same types and quality of applications using JSP pages. DataView control is one of the more useful features found in the page designer environment. This article demonstrates how to duplicate DataView functionality in the J2EE environment with the help of Struts.

summary of DataView functionality
Before discussing how to create a DataView in J2EE, let's take a look at the necessary underlying functionality. As you know, the SilverStream DataView control sources its data from an AgiRowCursor. Implementers of that interface provide full-record scrolling as well as the ability to update columns. Using that underlying data source, the DataView displays a page of data at a time. The view also applies updates received in the UI back to the AgiRowCursor. Event handling is another functionality that is taken for granted in the page designer environment. The page-submit process and action button handling is abstracted to implement a simple event method provided by the page designer. In order to build DataViews in J2EE, we will need a J2EE solution that enables each of these functions.
replacing the AgiRowCursor
First, we'll need some type of container to hold the data for our view. The container must provide forward and backward scrolling and allow for updates. Because J2EE doesn't define a data model layer similar to an AgpData, we'll have to create one on our own. In standard JDBC, we can access relational data though the java.sql.ResultSet interface. A JDBC 1.0 driver only provides forward scrolling ResultSets and doesn't support updates. In JDBC 2.0, ResultSets become "allow updates" and scroll in both directions. It looks like a JDBC 2.0 ResultSet might be the solution to our data model problem, but let's take a closer look.

A JDBC 2.0 ResultSet certainly meets the basic requirements to act as a data model for our view. The problem lies in the resource requirements of a JDBC ResultSet. Once a ResultSet is created, and up to the time its close() method is called, it is holding an open connection to the database. If our data view uses a JDBC 2.0 ResultSet, each user will need a unique database connection for the entire duration of his or her session. This simply won't work on most sites. What we really need is a type of cached result set, one that may be disconnected from the database to release its connection. In J2EE and with JSP pages in particular, the best way to present data to a UI is through JavaBeans. Among the many benefits of using beans, the following stand out:

  1. JavaBeans as a common Java development technique are familiar to most Java developers.
  2. JavaBean properties may be accessed and modified by reusable components such as JSP tags.
  3. JavaBeans are serializable, and they can be transmitted easily between client and server.


We will use a simple data model that translates each ResultSet row into an individual JavaBean. The resulting beans will then be stored in a special container object that allows us to navigate the collection of beans as an ordered set. Another JavaBean feature we will exploit is the ability to produce property change events. We will need to keep a record of the rows in which our DataView are updated so that the changes can be persisted as a group update. One object in our system will listen for "PropertyChangeEvents" and keep a record of which beans have been modified.

Figure 1: Duplicating an AgiRowCursor with a Collection of Java Beans


Much of the described functionality can be combined into a reusable base class that we will extend for a particular table structure. I have created a base class for this example, which you may reuse in your own projects. It is similar to the ArrayList Collection class, and because it maintains an index or current position pointer into the ArrayList, I called the class "IndexedArrayList". Figure 2 indicates how it imitates some familiar methods of the AgiRowCursor interface. It even improves upon AgiRowCursor with the "gotoRow(int i)" method, which allows us to move to an absolute position within the Collection. It is different from the AgiRowCursor interface in that it lacks both the "setProperty()" and "getProperty()" methods. Instead of using these generic accessors we will be directly accessing bean properties using their "get" and "set" methods. This is an improvement since we will now benefit from strong type checking. To get values from the row in focus, we'll first call "getCurrentBean()". Normally, we would type cast the return value as follows:

EmployeeBean bean = (EmployeeBean)getCurrentBean();

Next, we'll call a get method to access a column, for instance, we would call "getFirstName()" to get the employees first name.

Figure 2: IndexedArrayList Class Diagram
creating the model
Now that we have some basic functionality within a reusable base class, we can create some context-specific classes for our view. The view we are creating will use the Employees table found in the SilverBooks37 example database. To create a view for a particular table, we first must create a JavaBean with properties for each column. Because we are creating a view for employee records, we need to create a bean called "EmployeeBean" with the following properties: firstName, lastName, address1, city, and state. Each of these properties may be represented as a Java String object. To be considered as a true JavaBean each property must have a get and set method. We would also like our bean to produce property change events. The JDK provides a helper class called "PropertyChangeSupport" that helps in producing change events. Listing 1 shows an example method that produces property change events only when the new value is different from the old value.

Listing 1: Example Setter Method with Property Change Support


    /**

     * changes the employees first name.

     */

    public void setFirstName(String name) {

        if ( ! equal( firstName, name ) )

            changeSupport.firePropertyChange("firstName", this.firstName, name);

        this.firstName = name;

    }



Now, we need a class to create and maintain a set of EmployeeBeans for the data view. Part of the work has already been done in the IndexedArrayList class. The new class is called "EmployeeRecordManagerBean". It extends the IndexedArrayList to provide record navigation. The IndexedArrayList lacks the ability to load a set of beans on its own, so we will add that in the EmployeeRecordManagerBean class as shown in Listing 2.

Listing 2: loadAll() from EmployeeRecordManagerBean.java

    /**

     * Retrieves all employee records from database and instantiates

     * one EmployeeBean object per row.

     */

    public void loadAll() throws SQLException {

        String sql = "SELECT * FROM employees ORDER BY employees.lastName";

        Connection con = getConnection();

        Statement stmt = con.createStatement();

        ResultSet rs = stmt.executeQuery(sql);

        while ( rs.next() ) {

            String id = rs.getString("employeeId");

            EmployeeBean bean = new EmployeeBean(id);

            bean.setFirstName(rs.getString("firstName"));

            bean.setLastName(rs.getString("lastName"));

            bean.setAddress1(rs.getString("addressLine1"));

            bean.setAddress2(rs.getString("addressLine2"));

            bean.setCity(rs.getString("city"));

            addBean(bean);

        }

        stmt.close();

        con.close();

    }



Now that we have a data model for our view, we need a way to access the data one page at a time. The EmployeeRecordManagerBean provides each page as a subset of the internal Collection with the getPage() method as follows:


    /**

     * Retrieve a page of data based on the pageSize.

     */

    public Collection getPage() {

        LinkedList list = new LinkedList();

        for ( int i=0; i<pageSize && index+i<beans.size(); i++)

            list.add( beans.get(index+i));

        return list;

    }

creating the view
The view portion of our data view is implemented as a JSP page. It uses a custom JSP tag provided by Struts to iterate over a Collection of JavaBeans returned by the EmployeeRecordManagerBean. The first line of the EmployeeDataView.jsp page declares the Struts logic tag library as follows:

<%@ taglib uri="strutslogic" prefix="struts" %>

This allows the struts tags to be used throughout the rest of the page. Next, the page exposes the EmployeeRecordManangerBean to the page with a "useBean" tag. This tag finds an existing instance of the class in session scope, and makes it available as a scripting variable to the page as follows:

<jsp:useBean id="recordManager" class="com.sssw.summit01.EmployeeRecordManagerBean" scope="session" />

If we view the page within a WYSIWYG HTML designer like Dreamweaver, we'll see that it looks similar to the Page Designer's DataView control, having a static header row with a repeating data row below as follows:



The data row is made active by surrounding it with an "iterate" tag. This tag takes a Java Collection object and iterates once per item in the Collection. During each iteration it duplicates its body section and exposes the current object as a scripting variable. For our example page, the iterate tag is as follows:


  <struts:iterate id="employee"

                  type="com.sssw.summit01.EmployeeBean"

                  name="recordManager"

                  property="page" >



The "name" attribute specifies the bean that will provide the Collection for the iterations. The "property" attribute specifies the property of the bean, which will provide access to the Collection. The "type" attribute specifies the type of the objects contained within the Collection. Finally, the "id" attribute specifies the scripting variable name of the Collection object in the current iteration. Within the body of the iterate tag we now have access to an employee bean. Each text field is bound to the data model by setting the value attribute of each text field to its corresponding bean property. To do this we would use the standard JSP useBean tag as follows:


<input type="text" name='firstName_<%=i%>'

 value='<jsp:getProperty name="employee" property="firstName"/>' />



Another problem that we need to solve is how to differentiate each individual text field in a column. As you can see, the property name concatenated with an index becomes the name for each field. For a page size of 10, we would end up with ten text fields named firstName_0 through firstName_9.
creating the controller
We have a model and a view, and now we are going to look at the controller. The controller is the software component that will initialize the model, handle event processing, and perform updates to the model. In J2EE, servlets provide the best support for the controller piece. Between applications the controller piece tends to perform the same types of tasks. This common functionality has been provided for us as a J2EE framework called Struts. Struts provides a wealth of features for JSP developers. At its core it is an action handler servlet that maps specific URL requests to "actions". The URL to action mappings are specified in a configuration file. (The Struts framework can be downloaded from http://jakarta.apache.org/struts/index.html.)

Figure 3: Struts Actions


To service the data view we will need two separate actions. The DataViewInitAction class will handle the initial request. This action will be responsible for creating the model, and if necessary, refreshing the model on request. The second action, DataViewUpdateAction, will handle page navigation as well as updates. The simple process for initializing the model and providing it to the JSP page in DataViewInitAction.java is as follows:


    EmployeeRecordManagerBean recordManager = new EmployeeRecordManagerBean();

    recordManager.loadAll();

    session.setAttribute("recordManager", recordManager);



By placing the EmployeeRecordManagerBean in the session, it remains available to the JSP page for the duration of the user's session. Action handling requires a bit of Java code as well. In the Page Designer, button actions are mapped to an event method automatically for us. With J2EE, we will need to check the request to discover which button was pressed. In DataViewUpdateAction.java, the request is checked for each submit button as follows:


    // process navigation

    if ( request.getParameter("Next") != null ) {

        if ( ! recordManager.move(PAGE_SIZE) )

            recordManager.gotoRow( recordManager.getRows() - (PAGE_SIZE+1) );



Because submit buttons are mutually exclusive, we only need to check whether the request contains a value for the button's key. By calling move(), the EmployeeRecordManagerBean will now return the next page of data to the view.

The other responsibility of the DataViewUpdateAction class is to perform updates. Updates will be posted from the EmployeeDataView.jsp page when any navigation commands are selected. The logic is fairly straightforward and requires only that we loop from 0 to one less than the page size, then retrieve the post values for each row. After each loop, we move the EmployeeRecordManagerBean to the next row as follows:


       for ( int i=0; i<PAGE_SIZE; i++) {

            String firstName = request.getParameter("firstName_" + i);

            String lastName = request.getParameter("lastName_" + i);



            bean = recordManager.getBean();

            bean.setFirstName(firstName);

            bean.setLastName(lastName);



            if ( !  recordManager.gotoNext() )

                break;

        }



Individual actions are mapped to a URL by way of a configuration file. Struts will read this configuration file when the server starts.
deploying the example DataView
To deploy our example DataView, we will first need to download the example source files from http://ftp1.novell.com/pub/dev_articles/dataview.ZIP. After we have unpacked the zip file cd into the "dataview" directory. The deploy.bat command will deploy the application against our SilverStream server on localhost. We may need to edit the SilverStream37/bin path to match our environment. The deploy.bat command takes one argument: the database to which we want to deploy. The example also assumes that we have the SilverBooks37 database available on our server. (If you don't, simply use the SilverStream designer to add the SilverBooks37 database.) Once the application is deployed we can reference it with the following URL:

http://localhost/<database>/dataView