All posts by ajohnson

New design

I got really bored with the old design of this site and all the cool kids seem to be using WordPress these days so last weekend I exported all 900 or so entries from Movable Type and imported them into WordPress, installed the ScribbishWP Theme and wrote a servlet filter to map my /blog/year/month/day/entry_name.html Movable Type permalinks to the WordPress style which is /blog/year/month/day/entry-name/. Oh, and comments are back on.

Enjoy!

WebWork and meaningful URLs

Personal pet peeve: meaningful URLs (which tonight I found out go by many names: pretty URLs, RESTian URLs, SES URLs, hackable URLs, etc…). At work, we use WebWork extensively but up until this point we haven’t made an effort to create meaningful URL’s. As with any well designed framework, it turns out that there are a couple of ways you can create meaningful URL’s, with different levels of meaningfulness.

Version 2.2 of WebWork introduced the ActionMapper interface and a class called RestfulActionMapper, which gives you the ability to create URLs that might look something like this:

http://bookstore.com/books/category/java/keyword/webwork

instead of the more common:

http://bookstore.com/books.jspa?category=java&keyword=webwork

The nice thing about the RestfulActionMapper implementation is that you don’t have to write any code to parse the URL: you set up your WebWork actions with the appropriate setters and the RestfulActionMapper handles the rest. The downside is that this still isn’t really a truly hackable URL. For example, although this URL:

http://bookstore.com/books/category/java/keyword

and this URL:

http://bookstore.com/books/category

would probably work, they don’t really make sense. Why are ‘keyword’ and ‘category’ hanging around at the end? Both of the words are extra information required by the implementation that don’t add any value to the user.

The second way you can create meaningful URLs is by creating your own ActionMapper. You can get a good start by checking out the source code for the DefaultActionMapper and the RestfulActionMapper. To set properties on your action instances, you’ll want to create a HashMap,, add the appropriate properties from your URL to the map and then either create and return a new ActionMapping using the action name and map or call the setParams() method on an existing mapping. The end result is that you should be able to create and use meaningful URL that looks like this:

http://bookstore.com/books/java/webwork

Also of note:

Links: 7-31-2006

FluentInterface

A couple of weeks ago on the DWR users list, in the context of needing to wire up DWR without using an XML file, Joe Walker pointed to a blog posting by Martin Fowler. In it, Martin discusses an interface style called a ‘fluent interface’. It’s a little difficult to describe in words (so check it out in action on above mentioned blog post) but I think Piers Cawley described it best when he described the style as “…essentially interfaces that do a good job of removing hoopage.” Update: Geert Bevin uses this style in the RIFE framework and was calling the it “chainable builder methods” before Martin came along with the ‘fluent interface’ term.

Back to DWR.  I spent the last couple days working on a ‘fluent’ way of configuring DWR which obviously then wouldn’t require dwr.xml, the result of which is available here. In short, given an XML configuration file that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting
1.0//EN" "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
  <init>
    <converter id="testbean" class="uk.ltd.getahead.testdwr.TestBean2Converter"/>
  </init>
  <allow>
    <create creator="new" javascript="Test" scope="application">
      <param name="class" value="uk.ltd.getahead.testdwr.Test"/>
    </create>
    <create creator="new" javascript="JDate">
      <param name="class" value="java.util.Date"/>
      <exclude method="getHours"/>
      <auth method="getMinutes" role="admin"/>
      <auth method="getMinutes" role="devel"/>
    </create>
    <convert converter="bean" match="$Proxy*"/>
    <convert converter="testbean" match="uk.ltd.getahead.testdwr.TestBean"/>
    <convert converter="bean" match="uk.ltd.getahead.testdwr.ObjB"/>
    <convert converter="object" match="uk.ltd.getahead.testdwr.ObjA">
      <param name="force" value="true"/>
    </convert>
  </allow>
  <signatures>
  <![CDATA[
  import java.util.*;
  import uk.ltd.getahead.testdwr.*;
  Test.testBeanSetParam(Set<TestBean>);
  Test.testBeanListParam(List<TestBean>);
  Test.charTestBeanMapParam(Map<Character, TestBean>);
  Test.stringStringMapParam(Map<String, String>);
  Test.stringStringHashMapParam(HashMap<String, String>);
  Test.stringStringTreeMapParam(TreeMap<String, String>);
  Test.stringCollectionParam(Collection<String>);
  Test.stringListParam(List<String>);
  Test.stringLinkedListParam(LinkedList<String>);
  Test.stringArrayListParam(ArrayList<String>);
  Test.stringSetParam(Set<String>);
  Test.stringHashSetParam(HashSet<String>);
  Test.stringTreeSetParam(TreeSet<String>);
  ]]>
  </signatures>
</dwr>

you can instead configure DWR using the FluentConfiguration class like this:

FluentConfiguration fluentconfig = (FluentConfiguration)configuration;
fluentconfig
  .withConverterType("testbean", "uk.ltd.getahead.testdwr.TestBean2Converter")
  .withCreator("new", "Test")
    .addParam("scope", "application")
    .addParam("class", "uk.ltd.getahead.testdwr.Test")
  .withCreator("new", "JDate")
    .addParam("class", "java.util.Date")
    .exclude("getHours")
    .withAuth("getMinutes", "admin")
    .withAuth("getMinutes", "devel")
  .withConverter("bean", "$Proxy*")
  .withConverter("testbean", "uk.ltd.getahead.testdwr.TestBean")
  .withConverter("bean", "uk.ltd.getahead.testdwr.ObjB")
  .withConverter("object", "uk.ltd.getahead.testdwr.ObjA")
    .addParam("force", "true")
  .withSignature()
    .addLine("import java.util.*;")
    .addLine("import uk.ltd.getahead.testdwr.*;")
    .addLine("Test.testBeanSetParam(Set);")
    .addLine("Test.testBeanListParam(List);")
    .addLine("Test.charTestBeanMapParam(Map);")
    .addLine("Test.stringStringMapParam(Map);")
    .addLine("Test.stringStringHashMapParam(HashMap);")
    .addLine("Test.stringStringTreeMapParam(TreeMap);")
    .addLine("Test.stringCollectionParam(Collection);")
    .addLine("Test.stringListParam(List);")
    .addLine("Test.stringLinkedListParam(LinkedList);")
    .addLine("Test.stringArrayListParam(ArrayList);")
    .addLine("Test.stringSetParam(Set);")
    .addLine("Test.stringHashSetParam(HashSet);")
    .addLine("Test.stringTreeSetParam(TreeSet);")
  .finished();

If you’re interested in using this in your DWR project, you need only to:

  • create a class that extends DWRServlet (example: check out FluentDWRServlet.java in the zip file) and use that class as your DWR servlet
  • add a configuration param in web.xml called uk.ltd.getahead.dwr.Configuration and set the value to net.cephas.dwr.FluentConfiguration
  • add a configuration param in web.xml called skipDefaultConfig and set the value to true

    <servlet>
      <servlet-name>dwr</servlet-name>
      <servlet-class>net.cephas.dwr.FluentDWRServlet</servlet-class>
        <init-param>
          <param-name>uk.ltd.getahead.dwr.Configuration</param-name>
          <param-value>net.cephas.dwr.FluentConfiguration</param-value>
        </init-param>
        <init-param>
          <param-name>skipDefaultConfig</param-name>
          <param-value>true</param-value>
        </init-param>
    </servlet>

  • and then override the configure method in the servlet and use the fluent style of configuration I used above.

    Send me an email if you have any questions!