Saturday Jun 13, 2009

Using an integer range field validator annotation (Struts2)

XWork provides several mechanisms for validating form fields as part of its validation framework. One of these is through the use of annotations, and provides internationalization lookup, minimums and maximums, and automatic display of messages when validation fails.[Read More]

Thursday Apr 23, 2009

Implementing a custom LDAP server validator in Struts for SLAMD

Implementing a custom LDAP server validator in the Struts2 framework.[Read More]

Friday Apr 17, 2009

View Job Information software architecture in SLAMD

View job information architecture of SLAMD3 explained in brief.[Read More]

Wednesday Apr 15, 2009

"View All Jobs" page in SLAMD

A new page in SLAMD: "View All Jobs".[Read More]

Friday Mar 20, 2009

Locales and Resource bundles introduced to SLAMD

Locales added to SLAMD.[Read More]

Thursday Mar 12, 2009

SLAMD: job class lists using Struts tags & OGNL

SLAMD version 2 constructs HTML in a huge Java servlet (com.slamd.admin.AdminServlet). One of my first tasks for SLAMD3 is to de-couple the HTML generation from the Java code so that SLAMD will be more flexible in HTML generation. The method I use is based on MVC using Struts2. Hopefully, I'll be able to detail my progress as an ongoing series of blog entries - in my spare time.

The first page I converted was the "Schedule a New Job" page. The way this page works in SLAMD2 is as follows: the com.slamd.admin.AdminServlet class examines parameters passed in the servlet request. If the "job_class" parameter (the String is a static final in com.slamd.common.Constants) is not present in the URL, the servlet constructs a categorized list of jobs and displays them as links in an un-ordered list. The underlying code creates a two dimensional array of com.slamd.job.JobClass objects and constructs HTML from the array:

public class AdminServlet
{
...
  void handleScheduleJob(...)
  {
    JobClass[][] categorizedJobClasses =
      slamdServer.getCategorizedJobClasses();
    // construct HTML
  }
}

To convert this to an MVC model in Struts2, I constructed a JSP called scheduleJob.jsp and mapped it to an Action in the struts.xml file like so:

    <action name="ScheduleJob"
	    class="com.slamd.struts2.actions.ScheduleJobAction">
      <interceptor-ref name="slamdStack"/>
      <result name="success">/jsps/scheduleJob.jsp</result>
      <result name="error">/jsps/error.jsp</result>
    </action>
The action definition for "ScheduleJob" causes the dispatcher to route a URL like

http://hostname:port/slamd/ScheduleJob.action
through the interceptor stack "slamdStack", and then to the com.slamd.struts2.action.ScheduleJobAction object. the method "execute()" in actions must return a String indicating the result of the action execution. If the String returned by "execute()" is "success", then the view "scheduleJob.jsp" is the view, if "execute()" returns the String "error", then "error.jsp" is the view. ScheduleJobAction sets a field "jobClassName" to "noJob" (com.slamd.common.Constants.NO_JOB_PARAM) if no job parameter was present in the URL, or the job class name otherwise. This is the first use case: create the job class list when the "job_class" parameter is not part of the URL that is sent to the com.slamd.struts2.actions.ScheduleJobAction action.

A simple way to make construction of the job class list a little easier was to map the two dimensional array of JobClass objects to a Collection (java.util.Map) like so:


  public class JobClassArrayHelper<T extends JobClass>
  {
    public Map<String,List<T>> convert2dArrayToMap(T[][] a)
    {
      Map<String,List<T>> map = null;
      if(a != null)
      {
	map = new HashMap<String,List<T>>();
	for(int i = 0; i < a.length; ++i)
	{
	  List<T> list = Arrays.asList(a[i]);
	  map.put(a[i][0].getJobCategoryName(),list);
	}
      }
      return map;
    }
  }

  /\*\*
   \* Returns a {@link Map} of job classes that have been
   \* loaded into the database
   \*
   \* @return a map of job classes that have been loaded into the database
   \*/
  public Map<String,List<JobClass>> getJobClassList()
  {
    JobClass[][] categorizedClasses = common.getSlamdServer().getCategorizedJobClasses();
    JobClassArrayHelper<JobClass> helper = new JobClassArrayHelper<JobClass>();
    return helper.convert2dArrayToMap(categorizedClasses);
  }


The JobClassArrayHelper converts the two-dimensional array of JobClass objects into a Map where the key is the job class category and the value is a java.util.List of JobClass objects. The Map is very easy to access in the JSP, let's take a look at that next.

The scheduleJob.jsp code


<%-- -\*- html -\*- --%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
          "http://www.w3.org/TR/html4/loose.dtd">

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html lang="en" id="scheduleJob">

  <head>
    <%-- each action should generate its own title --%>
    <title><s:property value="title"/></title>
    <link rel="stylesheet" href="css/slamd.css">
  </head>

  <body id="scheduleJobBody">

    <%-- include the JSP for the sidebar navigation --%>
    <s:include value="navigation.jsp" />

    <%-- include the JSP for the page header --%>
    <s:include value="header.jsp" />

    <div id="content">

      <%-- if no job name was provided to the Action display the list
	   of jobs that have been loaded into the database --%>
      <s:if test="%{jobClassName == @com.slamd.common.Constants@NO_JOB_PARAM}">

	<h2>Schedule a New Job</h2>
	Choose the type of job to schedule from the following list:

	<div class="categorizedJobClassList">

	  <%-- set a variable to the job class param --%>
	  <s:set name="jobClassParam"
		 value="@com.slamd.common.Constants@SERVLET_PARAM_JOB_CLASS"/>

	  <%-- iterate over the keys in the map "jobClassList" --%>
	  <s:iterator value="jobClassList" status="jobClassItStatus">

	    <%-- the key in the map is the name of the job category --%>
	    <s:set name="jobClassCategory" value="key"/>

	    <h3><s:property value="jobClassCategory"/> Job Classes</h3>

	    <%-- The value in the map is the list of job classes.
		 Each element is of type com.slamd.jobs.JobClass. --%>
	    <s:set name="listOfJobs" value="value"/>

	    <div class="noBullets">
	      <ul>

		<%-- iterate of the list of job classes --%>
		<s:iterator value="listOfJobs" status="listItStatus">

		  <%-- set the class name of the job class --%>
		  <s:set name="className"
			 value="getClass().getName()"/>

		  <%-- Construct the link that will cause a new job --
		    -- class to be scheduled. The link consists of the --
		    -- ScheduleJob.action, a title that might be
		    -- rendered as a tooltip, or spoken, and a job
		    -- class parameter with the parameter value set to --
		    -- the job class name. --%>
		   <li class="appended">
		     <a title='<s:property value="shortDescription"/>'
		       href='ScheduleJob.action?<s:property value="jobClassParam"/>=<s:property value="className"/>'>
		      <s:property value="jobName"/>
		    </a>
		  </li>
		</s:iterator>
	      </ul>
	    </div>
	  </s:iterator>
	</div>
      </s:if>

    </div>

  </body>

</html>

This is the entire scheduleJob.jsp file that handles the use case of listing all SLAMD jobs in the database. I'll pick it apart piece by piece.

I like emacs

The following snippet causes Emacs to turn on HTML mode (a major mode):

<%-- -\*- html -\*- --%>

Page content and taglibs

Set the DOCTYPE, content type, and make 's' the prefix for the Struts tags:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
          "http://www.w3.org/TR/html4/loose.dtd">

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

Basic page setup

In SLAMD3, each page will have its own id and set its own title. navigation.jsp and header.jsp are the same for each HTML page, so those are included in each page. Divs are used freely to set off sections of the pages like the navigation sidebar and the header from the "content":

<html lang="en" id="scheduleJob">

  <head>
    <%-- each action should generate its own title --%>
    <title><s:property value="title"/></title>
    <link rel="stylesheet" href="css/slamd.css">
  </head>

  <body id="scheduleJobBody">

    <%-- include the JSP for the sidebar navigation --%>
    <s:include value="navigation.jsp" />

    <%-- include the JSP for the page header --%>
    <s:include value="header.jsp" />

    <div id="content">

Categorized job class listing

Here's the interesting part. First, test whether the jobClassName is "noJob" (recall that this value comes from a static final in com.slamd.common.Constants) and if so, print a short message about creating a new job, and create a new div called "categorizedJobClassList". The "job_class" URL parameter string is set to a variable using the "set" Struts tag so it can be referenced later - in the hope that the code becomes easier to read. Next create an iterator with the "iterator" tag where the object to iterate over is "jobClassList". Note that Struts uses the bean convention and actually calls ScheduleJobAction.getJobClassList. Recall that the signature of getJobClassList is:

 public Map<String,List<JobClass>> getJobClassList()
That is, a Map where the key is a String and the value is a List of JobClass objects.

     <%-- if no job name was provided to the Action display the list
	   of jobs that have been loaded into the database --%>
      <s:if test="%{jobClassName == @com.slamd.common.Constants@NO_JOB_PARAM}">

	<h2>Schedule a New Job</h2>
	Choose the type of job to schedule from the following list:

	<div class="categorizedJobClassList">

	  <%-- set a variable to the job class param --%>
	  <s:set name="jobClassParam"
		 value="@com.slamd.common.Constants@SERVLET_PARAM_JOB_CLASS"/>

	  <%-- iterate over the keys in the map "jobClassList" --%>
	  <s:iterator value="jobClassList" status="jobClassItStatus">

iterate over the map

It is not strictly necessary to use the "set" tag as much as is used here, but I think it makes the code easier to read and understand. In this case, the key from the map is assigned to a variable "jobClassCategory", and the value to "listOfJobs".

	    <%-- the key in the map is the name of the job category --%>
	    <s:set name="jobClassCategory" value="key"/>

	    <h3><s:property value="jobClassCategory"/> Job Classes</h3>

	    <%-- The value in the map is the list of job classes.
		 Each element is of type com.slamd.jobs.JobClass. --%>
	    <s:set name="listOfJobs" value="value"/>
This part sets a variable "className" by calling the methods to retrieve the name of the class for each SLAMD job class, then constructs a link of the form "ScheduleJob.action?job_class=com.slamd.jobs.JobClass":

	    <div class="noBullets">
	      <ul>

		<%-- iterate of the list of job classes --%>
		<s:iterator value="listOfJobs" status="listItStatus">

		  <%-- set the class name of the job class --%>
		  <s:set name="className"
			 value="getClass().getName()"/>

		  <%-- Construct the link that will cause a new job --
		    -- class to be scheduled. The link consists of the --
		    -- ScheduleJob.action, a title that might be
		    -- rendered as a tooltip, or spoken, and a job
		    -- class parameter with the parameter value set to --
		    -- the job class name. --%>
		   <li class="appended">
		     <a title='<s:property value="shortDescription"/>'
		       href='ScheduleJob.action?<s:property value="jobClassParam"/>=<s:property value="className"/>'>
		      <s:property value="jobName"/>
		    </a>
		  </li>
		</s:iterator>
	      </ul>
	    </div>
	  </s:iterator>
	</div>
      </s:if>
And that's it. The next use case - in a separate blog entry - is to present the job class form. This is all baby stuff to hard-core Struts developers, but I had to blog it so I would not forget how to do it, and hopefully others will get some value from this entry also.
About

Sun, LDAP, SLAMD, DSLA, java, Struts, networking, chess, books, cooking, wine, and many other things.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today