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:
<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>
Send me an email if you have any questions!
Hi Aaron,
realy interessting, cause we also like to use the concept of Fluent Interfaces in out applications. But never guessed to use it in this way.
But, I thought for configuration purposes it would be nice, not to hardcode this in the source. So the idea arose to use scripting support in Spring Framework 2 and I used your idea to impelement a proof of concept with Groovy ….
And guess what … it works. Think it’s a nice example for what to use scripting beans in Spring
just a liitle example:
the script:
—————————–
import net.cephas.dwr.FluentConfiguration
import com.trivadis.eurojourney.scripting.GroovyFluentConfiguration
class GroovyFluentConfigurationImpl implements GroovyFluentConfiguration {
public void init( FluentConfiguration configuration ) {
FluentConfiguration fluentconfig = (FluentConfiguration)configuration;
fluentconfig
.withConverterType(“jodaConverter”, “com.trivadis.JodaTimeDateConverter”)
.withCreator(“spring”, “CustomerService”)
.addParam(“beanName”, “custService”)
.withConverter(“bean”, “com.trivadis.eurojourney.Customer”)
.withConverter(“jodaConverter”, “org.joda.time.DateTime”)
.finished();
}
}
the servlet:
———————————–
….
if (configuration instanceof FluentConfiguration) {
ApplicationContext applicationContext = (ApplicationContext)this.getServletContext().getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
GroovyFluentConfiguration fluentconfig =
(GroovyFluentConfiguration)applicationContext
.getBean(“config”);
fluentconfig.init( (FluentConfiguration)configuration );
thankx a lot for your fantastic blog and the inspiratiion you gave me with it.
kind regards
peter