HierarchicalMap Tutorial - Advanced Level

HierarchicalMap is an interface. So anyone can implement it. This tutorial is based on BasicHierarchicalmap, a reference implementation of HierarchicalMap.

Following subjects are covered in this tutorial:

    Basic Level
  1. Getting Started
  2. Creating Structure
  3. Recovering the Data
  4. Interacting with Collections
  5. Restructuring the Map

  6. Intermediate Level
  7. Working with Stream
  8. Working with XML
  9. Handling Large XML
  10. Accessing Data Base
  11. Accessing Preferences

  12. Advanced Level
  13. Template Filling
  14. .NET Interoperability
  15. Runtime schema validation
  16. Writing Application

  17. Planned for Future
  18. IDE's plugin for coding-time schema validation and code completion

(Javascript must be enabled to allow syntax highlighting for source code snippets in this tutorial)

Advanced Level

14. Writing Application

Now, let us mix everything up. In this chapter, we will create a web application based on Tomcat (could be any other Servlet Container).

We assume that there is model like below, presented at "Accessing Data Base" tutorial, implemented on your data base server.

First, create a web project using your prefered IDE.

Using Eclipse:

or Using Netbeans:

Then, start creating an html form as shown:

Book

ISBN:

Title:

Price:

To do so, create a file like below, name it "BookForm.html" and place it under WebContent/template on your project.

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xmap="http://www.dhmp.org/xsd/StylesheetScript">
<head>
  <meta http-equiv="content-type" 
    content="text/html; charset=UTF-8" />
  <title>BookForm</title>
</head>

<body>
<h1>Book</h1>

<form action="BookForm">
  <div>
  <p>ISBN:<input type="text" name="book/isbn" /></p>

  <p>Title:<input type="text" name="book/title" /></p>

  <p>Price:<input type="text" name="book/price" /></p>

  <input type="hidden" name="book/id"/> 
  <input type="submit" name="buttom" value="Ok" />
  <input type="submit" name="buttom" value="Cancel" />
  </div>
</form>
</body>
</html>

Remember to use same charset all over your project. On the example above, we are using UTF-8, but it could be any other charset like ISO-8559-1.

Now, create a Servlet named BookForm and add the following code:

private ScriptCollection scripts;
private Script script;
       
/**
 * @see HttpServlet#HttpServlet()
 */
public BookForm() {
    super();
}
    
public void init() {
    String url = this.getServletContext()
        .getRealPath("/template");
    scripts = new ScriptCollection(new File(url));
    script = scripts.get("BookForm.html", 
                 Charset.forName("UTF-8"));
}

/**
 * @see HttpServlet#doGet(HttpServletRequest request,
          HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, 
  HttpServletResponse response) 
  throws ServletException, IOException {
    doPost(request, response);
}

/**
 * @see HttpServlet#doPost(HttpServletRequest request, 
          HttpServletResponse response)
 */
protected void doPost(HttpServletRequest request, 
  HttpServletResponse response) 
  throws ServletException, IOException {
    script.eval(response.getWriter(), null);
}

Note that ScriptCollection and Script are both from org.dhmp.util.stylesheet.

The content of WebContent/WEB-INF/web.xml should be like following:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" 
  xmlns="http://java.sun.com/xml/ns/j2ee" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Application</display-name>
    <servlet>
        <servlet-name>BookForm</servlet-name>
        <servlet-class>BookForm</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>BookForm</servlet-name>
        <url-pattern>/BookForm</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

Finally, make sure that dhmp.jar (containing HierarchicalMap classes) and xercesImpl.jar are visible to Tomcat.

Place them under WebContent/WEB-INF/lib, or $CATALINA_HOME/lib, or some other directory and reference the latter directory using shared.loader entry on $CATALINA_HOME/conf/catalina.properties.
(e.g. shared.loader=${catalina.home}/lib/ext/*.jar)

Launch Tomcat under debug option, or standalone after deploying the war file, and check the result, calling http://localhost:8080/Application/BookForm.

Getting Parameter

Now, add a method, which is shown next, to convert parametetr to a HierarchicalMap and check the value returned when the BookForm is submitted.

protected void doPost(HttpServletRequest request, 
      HttpServletResponse response) 
      throws ServletException, IOException {
   // convert parameter to HierarchicalMap
   HierarchicalMap param = parseParameter(request);
   script.eval(response.getWriter(), null);
}

public HierarchicalMap parseParameter(HttpServletRequest 
     request) {
   HierarchicalMap param = new BasicHierarchicalMap();

   for (Map.Entry e: request.getParameterMap().entrySet()) {
      String[] parameter = (String[]) e.getValue();
      for (int i = 0; i < parameter.length; i++) {
         param.add(e.getKey(), parameter[i]);
      }
   }

   return param;
}

Validating Parameter

Before proceeding, the application must check the input parameter provided by user. Put in mind that the form's parameter will be used to insert records into database table. Therefore, we will define all the constraints using xml schema as follows: (review the Runtime Schema Validation tutorial)

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:dhmp=
       "http://www.dhmp.org/xsd/SchemaErrorMessage.xsd" 
    elementFormDefault="qualified">
    <xs:element name="book">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="isbn" type="xs:string"/>
                <xs:element name="title" type="xs:string"/>
                <xs:element name="price">
                    <xs:annotation>
                        <xs:appinfo>
                            <dhmp:message
       text="price must be decimal with 2 fraction digits 
            and can not be 0."/>
                        </xs:appinfo>
                    </xs:annotation>
                    <xs:simpleType>
                        <xs:restriction base="xs:decimal">
                            <xs:minExclusive value="0"/>
                            <xs:fractionDigits value="2"/>
                        </xs:restriction>
                    </xs:simpleType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Create the insert_book.xsd with above content and place it under WebContent/xsd. Note that the elements are presented in exactly same order and type as defined in stored procedure's parameters.

Now, change the servlet code as follows. Run it and see the returning HierarchicalMap value (sqlparam).

First, add two more static attrributes. One is SchemaCollection schemas, and the other is Schema schema. Then, alter init() method as code shown next:

script = scripts.get("BookForm.html",
             Charset.forName("UTF-8"));
schemas = new SchemaCollection();
try {
   insert_book_schema = schemas.get(this.getServletContext()
      .getResource("/xsd/insert_book.xsd"));
   // instruct schema to add error message during 
   // transformation
   insert_book_schema.setAddErrorMessage(true);
} catch (SchemaException e) {
   Logger.getLogger(BookForm.class.getName())
      .log(Level.SEVERE, null,e);
} catch (MalformedURLException e) {
   Logger.getLogger(BookForm.class.getName())
      .log(Level.SEVERE, null,e);
}

Also change the doPost() method as follows:

// convert parameter to HierarchicalMap
HierarchicalMap param = parseParameter(request);

if ("Ok".equals(param.get("buttom"))) {
   // transform input parameter to correct sequence 
   // required by insert_book stored procedure
   HierarchicalMap sqlparam = insert_book_schema
      .transform(param);
   if(sqlparam.get("_error") != null) {
      // validation error
   }
}

script.eval(response.getWriter(), null);

Try several combination of input parameter for the form, submit them and see the content of sqlparam HierarchicalMap. Note the difference when form content attend all the requirement defined in schema document, and when it does not fullfill the constraints.

The rule behind this functionality is: when an element does not meet the constraint, an error node "_error" with the error message is appended at the node that contains the element. And "_focus" leaf with key associated to the first element, which does not satisfy the constraint, is added to the root. Ok, it is bit confusing, but we could not find an easier solution at the moment. We are looking forward to receive your suggestion to make this simpler.

Presenting Error Message

Next step is to present the validation error message on the form.

Alter the BookForm.html adding some substitution elements. Basically, we are adding a place to present error message and also defining a value for elements.

One more thing, there is also a tip to handle focus setting. Unfortunately, html's id can not have "/", so it should be a simple name and must be identical to the last portion of element's name.

<body onload=
"document.forms.bookform.<xmap:substitution 
name='_focus' default='isbn' />.focus()" >

<h1>Book</h1>

<form action="BookForm" id="bookform">

  <div>
  <span style="color:#ff0000;"><xmap:substitution 
    name="book/_error/isbn" /></span> 
  <p>ISBN:<input type="text" name="book/isbn" id="isbn"
  value="<xmap:substitution name='book/isbn' />"/></p>

  <span style="color:#ff0000;"><xmap:substitution 
    name="book/_error/title" /></span> 
  <p>Title:<input type="text" name="book/title" id="title"
  value="<xmap:substitution name='book/title' />"/></p>

  <span style="color:#ff0000;"><xmap:substitution 
    name="book/_error/price" /></span> 
  <p>Price:<input type="text" name="book/price" id="price"
  value="<xmap:substitution name='book/price' />"/></p>

To present the error message after validation, modify the code on doPost() method as below:

HierarchicalMap sqlparam = insert_book_schema
   .transform(param);
if(sqlparam.get("_focus") != null) {
   // validation error
   script.eval(response.getWriter(), sqlparam);
   return;
}

Inserting Book

So far, the Servlet is producing a ordinal html file. We will improve its functionality to insert new record on database when fillout the form and submit it.

Make sure that stored procedure insert_book, of Accessing Data Base tutorial has created on your database. Then, configure your Tomcat to recognize the database. In this example, we are using c3p0 as connection pool and Postgres as database Place the c3p0's jar file and Postgres's jdbc jar file on the same directory where xercesImpl.jar is placed. Then, create a WebContent/META-INF/context.xml with following content:

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/iMob">
  <!-- Default set of monitored resources -->
  <WatchedResource>WEB-INF/web.xml</WatchedResource>
  <Resource acquireIncrement="1" auth="Container" 
     description="DB Connection" 
     driverClass="org.postgresql.Driver" 
     factory="org.apache.naming.factory.BeanFactory" 
     jdbcUrl="jdbc:postgresql://localhost:5432/postgres" 
     maxPoolSize="5" minPoolSize="3" name="jdbc/postgres" 
     password="dhmp.org" 
     type="com.mchange.v2.c3p0.ComboPooledDataSource" 
     user="postgres"/>
</Context>

Then, alter the WebContent/WEB-INF/web.xml adding resource-ref as follow:

<display-name>Application</display-name>
    <resource-ref>
        <description>DataSource Reference</description>
        <res-ref-name>jdbc/postgres</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
    <servlet>
        <servlet-name>BookForm</servlet-name>
        <servlet-class>BookForm</servlet-class>
    </servlet>

At last, change the doPost() method as described below:

if (sqlparam.get("_focus") != null) {
   // validation error
   script.eval(response.getWriter(), sqlparam);
   return;
}
try {
   // get the database connection
   Context ctx;
   ctx = new InitialContext();
   DataSource ds = (DataSource) ctx
      .lookup("java:comp/env/jdbc/postgres");
   Connection con = ds.getConnection();

   MapSQLStatement msql = new MapSQLStatement(con);
   msql.setStatement("{ call insert_book(?, ?, ?) }");
   msql.execute(sqlparam);
   msql.close();

} catch (NamingException e) {
   Logger.getLogger(BookForm.class.getName()).log(
      Level.SEVERE, null, e);
} catch (SQLException e) {
   Logger.getLogger(BookForm.class.getName()).log(
      Level.SEVERE, null, e);
}

Listing Books

For this part, we will use the list_book stored procedure mentioned at "Acessing DataBase" tutorial.

Prepare an html template as below and put it at WebContent/template/BookListFrom.html.

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xmap="http://www.dhmp.org/xsd/StylesheetScript">
<head>
  <meta http-equiv="content-type" 
    content="text/html; charset=UTF-8" />
  <title>BookForm</title>
</head>

<body>
<h1>Book List</h1>
  <div>
  <table border="1">
  <tr>
  <th>ISBN</th> <th>Title</th> <th>Price</th>
  </tr>
  <xmap:foreach in="book">
  <tr>
  <td><xmap:substitution 
    name="isbn">123-1234567890</xmap:substitution></td>
  <td><xmap:substitution 
    name="title">Title</xmap:substitution></td>
  <td><xmap:substitution 
    name="price">99.99</xmap:substitution></td>
  </tr>
  </xmap:foreach>
  </table>
  </div>
</body>
</html>

Create a new Servlet named BookListForm, with following methods:

public void init() {
   String url = this.getServletContext()
      .getRealPath("/template");
   scripts = new ScriptCollection(new File(url));
   script = scripts.get("BookListForm.html", 
      Charset.forName("UTF-8"));
}

protected void doGet(HttpServletRequest request,
      HttpServletResponse response) 
      throws ServletException, IOException {
   doPost(request, response);
}

protected void doPost(HttpServletRequest request,
      HttpServletResponse response) 
      throws ServletException, IOException {
   try {
      // get the database connection
      Context ctx;
      ctx = new InitialContext();
      DataSource ds = (DataSource) ctx
         .lookup("java:comp/env/jdbc/postgres");
      Connection con = ds.getConnection();

      MapSQLStatement msql = new MapSQLStatement(con);
      msql.setStatement("{ ? = call list_book() }");

      HierarchicalMap param = new BasicHierarchicalMap(); 
      param.put("book", new MapSQLStatement.
              OutParameter(java.sql.Types.OTHER));

      //Disable auto commit to preserve cursor 
      con.setAutoCommit(false);
      HierarchicalMap result = msql.execute(param);
      msql.close();
      script.eval(response.getWriter(), result);

   } catch (NamingException e) {
      Logger.getLogger(BookForm.class.getName())
         .log(Level.SEVERE, null, e);
   } catch (SQLException e) {
      Logger.getLogger(BookForm.class.getName())
         .log(Level.SEVERE, null, e);
   }

}

Now, call http://localhost:8080/Application/BookListForm and see the result.

Everything is working fine, though we want to remove all the stored procedure's specific treatment out of our java code. For that, we will introduce a new class: SQLSchema.

Prepare a new XML Schema as follows:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:dhmp="http://www.dhmp.org/xsd/SchemaErrorMessage.xsd"
xmlns:ext="http://www.dhmp.org/xsd/SchemaExtension.xsd"
elementFormDefault="qualified">
  <xs:annotation>
    <xs:documentation>List all 
       book records</xs:documentation>
    <xs:appinfo>
      <ext:sql statement="{ ? = call list_book() }" />
    </xs:appinfo>
  </xs:annotation>
  <xs:element name="book">
    <xs:annotation>
      <xs:appinfo>
        <ext:sql direction="out" typevalue="1111" />
      </xs:appinfo>
    </xs:annotation>
  </xs:element>
</xs:schema>

Note that there is a new element "ext:sql". This is an extension for defining SQL specific information.

Placed at the top level, right under schema element, we can define the SQL statement, which can be retrieved using SQLSchema class.

Placed at the element, we can define the direction of the parameter, that can be "out", "inout" or "in" ("in" is default and can be omitted). And also the SQL type of the "out" parameter. The type must be defined using the integer value as defined in java.sql.Types (1111 of above XML schema corresponds to java.sql.Types.OTHER).

The BookListForm servlet, can be changed as below:

public class BookListForm extends HttpServlet {
  private ScriptCollection scripts;
  private Script script;
  private SchemaCollection schemas;
  private SQLSchema list_book_schema;

  public void init() {
    String url = this.getServletContext()
      .getRealPath("/template");
    scripts = new ScriptCollection(new File(url));
    script = scripts.get("BookListForm.html",
      Charset.forName("UTF-8"));
    schemas = new SchemaCollection();
    try {
      list_book_schema = new SQLSchema(
      schemas.get(this.getServletContext()
        .getResource("/xsd/list_book.xsd")));
    } catch (SchemaException e) {
      Logger.getLogger(BookForm.class.getName())
        .log(Level.SEVERE, null, e);
    } catch (MalformedURLException e) {
      Logger.getLogger(BookForm.class.getName())
        .log(Level.SEVERE, null, e);
    }
  }


  protected void doGet(HttpServletRequest request,
    HttpServletResponse response) 
      throws ServletException, IOException {
    doPost(request, response);
  }

  protected void doPost(HttpServletRequest request,
      HttpServletResponse response) 
      throws ServletException, IOException {
    try {
      // get the database connection
      Context ctx;
      ctx = new InitialContext();
      DataSource ds = (DataSource) ctx
        .lookup("java:comp/env/jdbc/postgres");
      Connection con = ds.getConnection();

      MapSQLStatement msql = new MapSQLStatement(con);
      msql.setStatement(list_book_schema.getStatement());
      HierarchicalMap param = list_book_schema
        .transform(null);

      //Disable auto commit to preserve cursor 
      con.setAutoCommit(false);
      HierarchicalMap result = msql.execute(param);
      msql.close();
      script.eval(response.getWriter(), result);

    } catch (NamingException e) {
      Logger.getLogger(BookForm.class.getName())
        .log(Level.SEVERE, null, e);
    } catch (SQLException e) {
      Logger.getLogger(BookForm.class.getName())
        .log(Level.SEVERE, null, e);
    }
  }
}

Well, see next the brief explanation of what is happening with the code above.

In the init() method, we are loading the XML schema document into list_book_schema. Note that this time, we are instantiating SQLSchema passing the retrieved Schema to its constructor. Also observe that we are not calling setAddErrorMessage method. This is because the stored procedure does not have input parameters, thus we are no longer interested in validating the input parameter.

Look at doPost() method. Now, we are retrieving SQL Statement defined in XML schema and passing it to MapSQLStatement, and just calling schema's transform() method to create a HierarchicalMap containing the output parameter as specified by schema document.

Summary

Most of things presented here are ordinal servlet programming under Tomcat.

In essence, following steps should be taken for writing application using HierarchicalMap: