Project Description

The IDRS is a web application development platform designed around the idea of using simple XML style markup with Java/EJB objects and JDBC data sources to generate text based (usually html) content.

The system works by pre-compiling Reporting Markup Language (RML) pages into a Java object tree, which in turn is executed for every page visit. Scripting is pluggable with implementations for BeanShell and Jython.

What I have done is created an integration of the IDRS with AXIS in an attempt to do for Document/Message absed services what Axis has done for Java-RPC over SOAP. Below are some instructions on how to setup the system. It is bundled as a webapp that can be dropped into any servlet container (tested with tomcat 5). The sample is a VERY simple DSMLv2 (Directory Service Markup Language) implementation that allows for searches of a database table.

The advantages of using the IDRS over other forms generating document based services are:

  1. Use of the Java API for XML Binding (JAXB) is used to parse the request XML into a consistent object model based on an XML Schema
  2. Page based development increases maintainability by decreasing custom code
  3. Response documents are generated directly to a Document Object Model, allowing for XSL and other transformations to occur without excess parsing

Step 1: Deploy the web application

The web application in the download contains all needed libraries to get started (including axis) except for JDBC drivers.

Step 2: Configure the IDRS

Inside of the webapp's WEB-INF directory is a file called idrs-config.xml. This file contains all of the initialization parameters for the IDRS. Most of them do not apply for the integration with Axis. Here is a sample, with the applicable configuration options in red.

<idrs-config>
<scriptClass>net.sourceforge.idrs.script.embedable.IDRSBeanShell</scriptClass>
<scriptPoolMin>1</scriptPoolMin>
<scriptPoolMax>5</scriptPoolMax>
<scriptPoolDaysOpen>10</scriptPoolDaysOpen>
<reportsLogPath>/var/log/internships</reportsLogPath>
<accessDeniedPage>denied.html</accessDeniedPage>
<errorPage>error.jsp</errorPage>
<errorLog>/Volumes/Panther/jakarta-tomcat-5.0.27/logs/idrs.log</errorLog>
<logLevel>info</logLevel>
<resetPort>8989</resetPort>
<digestType>MD5</digestType>
<resetAllowedHosts>127.0.0.1</resetAllowedHosts>
<authDb>interns</authDb>
<docsDb>interns</docsDb>
<reportPoolClass>net.sourceforge.idrs.pool.IDRSRepPool</reportPoolClass>
<dbPoolClass>net.sourceforge.idrs.pool.JDBCPool</dbPoolClass>
<dbs>
     <db id="idrs">
	<driver>com.mysql.jdbc.Driver</driver>
	<url>jdbc:mysql://127.0.0.1/test</url>
	<user>root</user>
	<pass></pass>
	<min>1</min>
	<max>10</max>
	<daysOpen>10</daysOpen>
	<logPath></logPath>
     </db>
</dbs>
<vars>
	<var name="docsPath" val="/usr/local/tomcat/webapps/internships/docs" />
	<var name="allowedDocs" val="doc,pdf,txt" />
	<var name="docsWebPath" val="docs" />
</vars>
</idrs-config>
		

The IDRS supports pluggable pools. The default implementation uses jakarta commons. The <dbs< section is where you define database connections that may be used. The <vars> section is where you may define application wide initialization parameters.

Step 3: Configure AXIS

The integration with Axis involves two pieces, a Handler and a Provider. The Handler is responsible for resource management and orchestration, the Provider is responsible for handling requests for a given service. There should only be one instance of the Handler per Axis deployment and one Provider for every service. Below is the provided server-config.wsdd with the relevant configuration in red.

<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <globalConfiguration>
  <parameter name="adminPassword" value="admin"/>
  <parameter name="attachments.Directory" value="/Users/mlb/perforce/octetstring/vde-3x/vde-30-projects/adminService/dist/adminService/WEB-INF/attachments"/>
  <parameter name="sendMultiRefs" value="true"/>
  <parameter name="sendXsiTypes" value="true"/>
  <parameter name="attachments.implementation" value="org.apache.axis.attachments.AttachmentsImpl"/>
  <parameter name="sendXMLDeclaration" value="true"/>
  <parameter name="axis.sendMinimizedElements" value="true"/>
  <requestFlow>
  </requestFlow>
  <responseFlow>
  </responseFlow>
 </globalConfiguration>
 <handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/>
 <handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/>
 <handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>
 <handler name="IDRS" type="java:net.sourceforge.idrs.axis.IdrsHandler" >
  <parameter name="config" value="/Volumes/Panther/jakarta-tomcat-5.0.27/webapps/axis/WEB-INF/idrs-config.xml"/>
  <parameter name="parseClass" value="org.apache.xerces.parsers.SAXParser" />
 </handler>
 <service name="IDRSService" provider="Handler" style="document" use="literal">
   <parameter name="handlerClass" value="net.sourceforge.idrs.axis.IdrsProvider"/>
   <parameter name="rml" value="rml/dsmlv2.rml" />
   <parameter name="ns:urn:oasis:names:tc:DSML:2:0:core" value="org.oasis.dsmlv2" />
 </service>
 <transport name="http">
  <requestFlow>
   <handler type="URLMapper"/>
   <handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>
   <handler type="IDRS" />
  </requestFlow>
  <responseFlow>
    <handler type="IDRS" />
  </responseFlow>
 </transport>
 <transport name="local">
  <responseFlow> 
   <handler type="LocalResponder"/>
  </responseFlow>
 </transport>
</deployment>
		

The handler's configuration has at two parameters. <config> is the full path to idrs-config.xml file. The parse class is what class to use to parse that file. The provider has at least two parameters. <rml> is the relative location of the rml file to use. Any parameter that begins with "ns:" is assumed to be a namespace mapping where the rest of the property name is the namespace and the value is the JAXB generated package for handling the incoming XML.

Step 4: The RML

RML stands for Reporting Markup Language designed to be a simple markup for creating text based content. The goal for RML is to allow for content to be generate from JDBC and EJB sources. Below is the RML used for generating DSMLv2:

<rml>
  <ishtml>false</ishtml>
   <scriptclass>net.sourceforge.idrs.script.embedable.IDRSBeanShell</scriptclass>
   <head>
     <object id="dsmlv2">
      <class>idrs.dsmlv2.BatchRequest</class>
      <constructor>
      </constructor>
      <setProps>false</setProps>
      <method>
        <name>getUsers</name>
        <vartype>Connection</vartype>
        </method>
    </object>
    <db id="data">
        <dbname>idrs</dbname>
        <usemethod objid="dsmlv2">getUsers</useMethod>
    </db>
   </head>
   <body>
      <tag name="batchResponse" ns="urn:oasis:names:tc:DSML:2:0:core">
        <tag name="searchResponse">
          <attribute name="requestID"><$= idrs.getObject("dsmlv2").getRequestID() $></attribute>
          <repeat  id="data">
            <tag name="searchResultEntry">
              <attribute name="dn">cn=<field>data.name</field>,ou=octet,o=join</attribute> 
              <attribute name="requestID"><$= idrs.getObject("dsmlv2").getRequestID() $></attribute>
        
              <tag name="attr">
                <attribute name="name">ou</attribute>
                <tag name="value">octet</tag>
              </tag> 
  
              <tag name="attr">
                <attribute name="name">objectClass</attribute>
                <tag name="value">top</tag>
                <tag name="value">organizationalUnit</tag>
              </tag>
        
              <tag name="attr">
                <attribute name="name">cn</attribute>
                <tag name="value"><field>data.name</field></tag>
              </tag>
        
              <fields db="data">
                <tag name="attr">
                  <attribute name="name"><currentField>data</currentField></attribute>
                  <tag name="value"><field>data.idrsCurrentField</field></tag>
                </tag>
              </fields>
            </tag>
          </repeat>

          <tag name="searchResultDone">
            <attribute name="requestID"><$= idrs.getObject("dsmlv2").getRequestID() $></attribute>
            <tag name="resultCode">
              <attribute name="code">0</attribute>
              <attribute name="descr">Success</attribute>
            </tag>
          </tag>

        </tag>
      </tag>
   </body>
</rml>
		

This example is VERY simple, but shows the basic functionality of the IDRS as a SOAP Part generator. RML is split into two parts, the first being the <head> and the second being the <body>. All data sources are defined in the head section, while the body section outlines the returned content.

Inside of the <head> section there are two components defined, an <object> and an <db>. <object> components allow for a java object to be used for generating data sets and handling the XML object model. In this case, the BatchRequest object is used to extract the filter and request id from the DSMLv2 search request. It is also used to query the database. The <db> tag is used to generate data sources. Data may be generated from an SQL statement, a local object (as is done here) or an EJB.

The <body> section is where the XML for the SOAP part is generated. For each tag we us a <tag> tag to determine when a new tag will be created, and an <attribute> tag will be used for each attribute. Any tags may be used to generate the contet for tags and attributes. We then use the <$= ... $> tag to extract the request id using the built in scripting framework. In this instance we are using the BeanShell language as a scripting language. There is also support for Jython. To iterate over the results, a <repeat> tag is used in conjunction with <field> tags and <fields> tags to generate the DSMLv2 results. The <fields> tag is used to iterate over all of the resulting fields, while the <field> tag is used to extract the data from the results.

Step 5: The Java Source

 1 package idrs.dsmlv2;
 2 
 3 import java.io.Serializable;
 4 import java.sql.Connection;
 5 import java.sql.PreparedStatement;
 6 import java.sql.ResultSet;
 7 
 8 import net.sourceforge.idrs.utils.CleanUp;
 9 
10 /**
11  * 
12  * @author mlb
13  *
14  * Class for handling DSMLv2 Search Requests
15  */
16 public class BatchRequest extends CleanUp implements Serializable {
17 	int requestID;
18 	
19 	/**
20 	 * a "reInit" function is required for each constructor
21 	 */
22 	public void reInit() {
23 		//no need for initialization code
24 	}
25 	
26 	public ResultSet getUsers(Connection con) throws Exception {
27 		// First retrieve the "BatchRequest" 
28 		org.oasis.dsmlv2.BatchRequest batchreq = (org.oasis.dsmlv2.BatchRequest) idrs.getXMLObjects().get(0);
29 		//Retrieve the search request
30 		org.oasis.dsmlv2.SearchRequest searchreq = (org.oasis.dsmlv2.SearchRequest) batchreq.getBatchRequests().get(0);
31 		
32 		//Determine the request ID.  This is used by DSMLv2 to identify a response to a request.
33 		//We will be using this later inside of the RML
34 		this.requestID = Integer.parseInt(searchreq.getRequestID());
35 		
36 		//Create a simple SQL SELECT statement
37 		String sql = "SELECT * FROM tblTest WHERE " + searchreq.getFilter().getEqualityMatch().getName() + "=?";
38 		PreparedStatement ps = con.prepareStatement(sql);
39 		
40 		
41 		//Some basic logic to determine if the value is a string or an integer.
42 		int ival=0;
43 		try {
44 			ps.setInt(1,Integer.parseInt(searchreq.getFilter().getEqualityMatch().getValue().toString()));
45 		} catch (Exception e) {
46 			ps.setString(1,searchreq.getFilter().getEqualityMatch().getValue().toString());
47 		}
48 		
49 		//Statement must be registered so that it may be properly closed
50 		this.regStatement(ps);
51 		//Return the results
52 		return ps.executeQuery();
53 	}
54 	
55 	/**
56 	 * Returns the request ID
57 	 * @return
58 	 */
59 	public int getRequestID() {
60 		return this.requestID;
61 	}
62 	
63 }
		

The above code is called by the RML page to extract the search request information from the DSMLv2 search request. The class implements the net.sourceforge.idrs.utils.CleanUp class. All classes that called by RML pages need to extend this class. This class includes cleanup and error checking code that may be used by your service. The Class also implements java.io.Serailizable. This is also required of all classes called by an RML page because all an instance of this class will only be created once. It will then be searialized and cloned for each instance stored in the pool. Lines 22-24 will be called each time the service is loaded. There must be a "reInit" method for each constructor, since in this case there is only an empty constructor (implicitly) the only reInit method needed has no arguments. Lines 26-53 are the getUsers method we referenced in our RML service. The method is pretty self explanitory; A connection is passed into the method, a SQL statement is created and a result returned. Finally, lines 59-62 retrieve the request id extracted on line 34 and is called by our RML page wherever we need the request id.

Where to go from here

This document is a quick overview of how the IDRS may be used to generate document based web services. From here you can go to the downloads section to download the sample application that we went over here.