Categories
Coding

Ajax and Struts

The current project that I am working on makes very heavy use of Ajax. In fact, the application UI has developed to the point where the logical flow of the application has begun to resemble a thick-client (e.g. Swing) application, with the various JSP UI elements firing off asynchronous notifications from within event handlers and updating the UI when data becomes available. This has made a huge difference to the usability of the UI from the end user’s point of view. The application makes heavy use of Prototype, the Javascript framework, and we have had great success with its XmlHttpRequest handling capabilities. The controller tier is built on Struts, which delegates to a Spring-managed service layer. For instance, a sample request on the client might be invoked via Javascript as follows:

/**
* Update the list of unit names, given a handle to a <select> element with a currently
* selected unit type value
*/
fireUnitNameUpdate: function(sel) {
var unit = sel.options[sel.selectedIndex].value;
var url = Utils.contextRoot() + actionPath;
var pars = "action=showApprovalTree&type=" + unit;

if (unit == '' || unit == null) {
return;
}

var myAjax = new Ajax.Request (
url,
{
method: 'post',
postBody: pars,
onComplete: updateApprovalTree
});
}

The contextRoot() and actionPath variables construct a path to a Struts handler, which picks up the request and unpacks the parameters (Note that extra validation could be done on the client at this point). It then calls the service layer to create an XML document (constructed using Dom4J) and transforms it via an XSLT stylesheet:

public ActionForward showApprovalTree(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
String xml = approvalChainService.buildXmlApprovalTree(new Event());

// Transform the xml
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(new File(Constants.APPROVAL_TREE_XSL)));
response.setContentType(Constants.HTML);
transformer.transform(new StreamSource(new StringReader(xml)), new StreamResult(response.getWriter()));
response.getWriter().flush();
response.getWriter().close();
return null;
}

The Struts action returns an XML tree which is deserialized in the Ajax handler callback method on the client:

updateApprovalTree: function(originalRequest) {
var dom = XMLUtil.createXMLDom(originalRequest.responseText);
var nodes = XMLUtil.parseXPath(dom, "//unit");
UIUtils.updateDropDownWithXML('approvalTree', nodes);
}

This approach has proven to be very flexible and allows us to easily construct simple event handlers for UI event triggers. Building reusable libraries of cross-browser JavaScript code is now (with the help of Prototype and Behaviors) very easy. The only downside is the reduced debgging capabilities on the client side.

Categories
Coding

Was the java.util.regex package based on Jakarta-ORO?

A lot of Java libraries that need regex functionality, but also need to retain compatability across 1.3 and earlier VMs use the famous Jakarta ORO library. This is a great (and fully-featured) regex implementation. An example of a project that uses this is one of the projects that I am a committer on, Jakarta Commons-Net. This is a very popular library which is mainly used for its FTP functionality, e.g. Ant’s FTP task uses it as its FTP engine.
However, it is designed to be compatible with 1.3 and earlier VMs. One of the implications of this is that its regex parsing is done by ORO, which necessitates extra “baggage” in the form of a separate jar download. As an exercise, I decided to see how easy it would be to convert the existing ORO code to a JDK-based approach. I first created a branch in SVN (here), and started to work on the ORO code.
It turned out to be trivial to make the changes, in fact the APIs were so similar I have a strong feeling that the Sun engineers who implemented the java.util.regex package may have been heavily influenced by the design of the ORO interface.
First, look at the ORO version:


private Pattern pattern = null;
    private MatchResult result = null;
    protected PatternMatcher _matcher_ = null;

    public RegexFTPFileEntryParserImpl(String regex)
    {
        super();
        try
        {
            _matcher_ = new Perl5Matcher();
            pattern   = new Perl5Compiler().compile(regex);
        }
        catch (MalformedPatternException e)
        {
            throw new IllegalArgumentException (
               "Unparseable regex supplied:  " + regex);
        }
    }

    /**
     * Convenience method delegates to the internal MatchResult's matches()
     * method.
     *
     @param s the String to be matched
     @return true if s matches this object's regular expression.
     */

    public boolean matches(String s)
    {
        this.result = null;
        if (_matcher_.matches(s.trim()this.pattern))
        {
            this.result = _matcher_.getMatch();
        }
        return null != this.result;
    }



    /**
     * Convenience method delegates to the internal MatchResult's groups()
     * method.
     *
     @return the number of groups() in the internal MatchResult.
     */

    public int getGroupCnt()
    {
        if (this.result == null)
        {
            return 0;
        }
        return this.result.groups();
    }

And now look at the equivalent Java 1.4+ version:


private Pattern pattern = null;
    private MatchResult result = null;
    protected Matcher _matcher_ = null;

    public RegexFTPFileEntryParserImpl(String regex)
    {
        super();
        try
        {
            pattern   = Pattern.compile(regex);
        }
        catch (PatternSyntaxException pse)
        {
            throw new IllegalArgumentException (
               "Unparseable regex supplied:  " + regex);
        }
    }

    /**
     * Convenience method delegates to the internal MatchResult's matches()
     * method.
     *
     @param s the String to be matched
     @return true if s matches this object's regular expression.
     */

    public boolean matches(String s)
    {
        this.result = null;
        _matcher_ = pattern.matcher(s);
        if (_matcher_.matches())
        {
            this.result = _matcher_.toMatchResult();
        }
        return null != this.result;
    }



    /**
     * Convenience method delegates to the internal MatchResult's groups()
     * method.
     *
     @return the number of groups() in the internal MatchResult.
     */

    public int getGroupCnt()
    {
        if (this.result == null)
        {
            return 0;
        }
        return this.result.groupCount();
    }

Categories
Coding

Hibernate, java.util.Date and java.sql.Date Issues

One issue that can crop up during unit testing is problems with date mapping. In SQL terms dates to a “day” level of precision can be specified using a DATE column and dates with a “time” element can be mapped using a TIMESTAMP (or MySQL DATETIME) column. These map respectively to the Java SQL types java.sql.Date and java.sql.Timestamp. Note that a java.util.Date object can specify times, up to a millisecond level of precision, whereas a java.sql.Timestamp object can handle nanosecond-level precision.

If we are using Hibernate to map a persistent class, and one of the fields is a day-level-precision Date field, we could map it like so:

In a unit test to exercise the persistence mapping, it could go something like the following:

Foo f = new Foo();
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
f.setStartDate(now);

and then test the retrieved persistent entity like so:

fooEntity = session.load(Foo.class, id);
assertEquals(f.getStartDate(), fooEntity.getStartDate());

The assertion will fail. The reason for this is that the call to fooEntity.getStartDate() returns an instance of java.sql.Date, whereas we are comparing against a java.util.Date. Since java.sql.Date is a subclass of java.util.Date, our tests will run without any ClassCastExceptions, but any equality checks will fail. The reason for this is apparent from the java.sql.Date javadoc:

To conform with the definition of SQL DATE, the millisecond values wrapped by a java.sql.Date instance must be ‘normalized’ by setting the hours, minutes, seconds, and milliseconds to zero in the particular time zone with which the instance is associated.

This means that any Date objects we retrieve from our Hibernate-mapped objects will have an effective time of midnight , i.e.something like 25-Jan-2006 00:00:00. We cannot just use the getTime() method to retrieve a long equaivalent of the date value either, as the they will ave the same problem.

What we can do is “normalize” the Date object before we persist it, like so:

private Date normalize(Calendar cal) {
cal.set(Calendar.MILLISECOND, 0);
cal.set(2006,0,25,0,0,0);
return cal.getTime();
}

...
Date now = normalize(cal);
event.setStartDate(now);

Now our asserts will pass without a problem.

There’s an entertaining overview of the bigger picture here.