The talk at Øredev in Malmö 2007 was based on a number of small code examples to demonstrate the basic functionality of DWR and techniques used to handle the complexity of an application that heavily uses both the basic remoting and reverse ajax functionality of DWR.
Attached to this post is a file containing a Maven project to build and run the examples.
I used Maven to keep the initial download size down (that's my bandwidth) but it'll bring down all needed dependency's once you build it for the first time (that's your bandwidth ;) ).
I'm using a quite cool maven plugin for Jetty (which accidentally is an excellent choice to use for your DWR applications) to bootstrap the demo.
So, what would you need to run this?
1. Install maven (http://maven.apache.org/) and unpack the attached file to a directory of your choice.
2. Fire up your favorite shell (that's the cmd for you windows people out there).
3. Enter mvn jetty:run
4. Point your web browser to http://localhost:8080/oredev
5. Try out the examples...
And what are these examples meant to show (you didn't feel like looking at the actual video)?
Well, let's walk through them shall we..
Example 1 - Basic call
A simple form that doubles any number posted.
Calls the Java method com.bv10.demo.service.SimpleService.doSomeThingWithAnAmount() by clicking on the html button and triggering the following JavaScript:
SimpleService.doSomeThingWithAnAmount($('amount').value, handleResult)
The basic Java code is the following:
...
public class SimpleService {
public String doSomeThingWithAnAmount(int amount) {
return "I took your amount and doubled it : " + amount + " * 2 = " + (amount * 2);
}
...
}
The result is handled by the client side callback javascript function handleResult.
<script type="text/javascript">
function handleResult(value) {
dwr.util.setValue('result', value);
}
</script>
This example is meant to show the simplest form of javascript remoting, using a simple type as input and marshalling a simple object back as the result.
Example 2 - Basic call with Java bean marshalling
So, let's take the first example to the next level. Let us use a slightly more complex object.
public class JavaBeanResult {
private int result;
private String calculation;
private String description;
// getters & setters
}
and our method on the service looks like:
...
public class SimpleService {
...
public JavaBeanResult doSomeThingWithAnAmountGetBeanResult(int amount) {
JavaBeanResult bean = new JavaBeanResult();
bean.setDescription("I took your amount and doubled it.");
bean.setCalculation(amount + " * 2");
bean.setResult(amount * 2);
return bean;
}
}
Here's were it starts to get interesting, remember we're only using a very "simple" JavaBean for marshalling. DWR is capable of marshalling quite a number of complex types including Hibernate Objects and DOMs. Keep it simple though, no need to marshall your whole object model just because you can, there's always a price to pay in network transfer.
So, this time when the button is clicked:
SimpleService.doSomeThingWithAnAmountGetBeanResult($('amount').value, handleResult);
the JavaScript callback function looks a bit different:
function handleResult(bean) {
dwr.util.setValue('description', bean.description);
dwr.util.setValue('calculation', bean.calculation);
dwr.util.setValue('result', bean.result);
}
Hey, look at that. The javascript code is actually manageable and human readable. This becomes even more obvious if start to mange lists and more complex objects.
Push Example 1a - Bind push response to function
Up until now we've only tried to most basic functionality of DWR. Starting with version two something called Reverse Ajax entered the DWR landscape. This is the ability to push data to "listening" (comet, pull, piggy back) clients.
In this example I'm using a spring bean as the remoted service (the first examples used a standard java class).
Let's have a look on the client side first. In the onload event on html page you'll find the following:
dwr.engine.setActiveReverseAjax(true);
This tells the DWR machinery to hook this page up as a listener for Reverse Ajax pushes.
This html page is really simple and only contains one button to click and a div tag that will contain the result from the server pushes.
When clicking the button the following JavaScript is run:
AjaxService.doPush()
Please note that this time there is no callback method specified.
But... check out the code on server side:
public void doPush() {
ServerContext sctx = ServerContextFactory.get(servletContext);
Collection<ScriptSession> sessions = sctx.getScriptSessionsByPage(servletContextName + "/pushexample1a.html");
Util util = new Util(sessions);
util.addFunctionCall("recieveServerPush", new Date());
}
This little piece of code will get you a list of all clients listening to this actual page and invoke the locally defined JavaScript function recieveServerPush with the server's timestamp.
function recieveServerPush(msg) {
dwr.util.setValue('message', msg);
}
HIT ÄR JAG
Push Example 1b - Serverside generated javascript calls
Using a variant of the first push example we'll skip the definition of the locally defined javascript function but keep the exact functionality of the previous example.
This time when we press the button the following code will run:
public void doDwrPush() {
ServerContext sctx = ServerContextFactory.get(servletContext);
Collection sessions = sctx.getScriptSessionsByPage(servletContextName + "/pushexample1b.html");
Util util = new Util(sessions);
util.setValue("message", new Date().toString());
}
DWR comes with a number of server side javascript utilities for your convenience and it's quite easy to implement your own library's as well. We'll see another variation of this in the next example.
Push Example 2 - Fancy effects push with quartz integration
What other goodies do you get with the basic DWR package?
Well, they've been nice enough to implement the Effects library from http://script.aculo.us/. So let's try to add the highlight effect to the previous example:
public void doFancyPush() {
ServerContext sctx = ServerContextFactory.get(servletContext);
Collection sessions = sctx.getScriptSessionsByPage(servletContextName + "/pushexample2.html");
Util util = new Util(sessions);
util.setValue("message", new Date().toString());
Effect effect = new Effect(sessions);
effect.highlight("message");
}
This means that you'll get the standard highlighting effect when the message is pushed out to the listening clients.
So how much work is it to get this little webapp to automatically push this message say once every 15 seconds? Since we are using Spring for this example let us integrate Quartz and schedule this...
In our applicationContext.xml:
<bean id="myServerPush2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="myAjaxService" />
<property name="targetMethod" value="doFancyPush" />
<property name="concurrent" value="false" />
</bean>
<bean id="myTrigger2" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="myServerPush2" />
<property name="startDelay" value="15000" />
<property name="repeatInterval" value="5000" />
</bean>
<bean id="mySchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="myTrigger2" />
</list>
</property>
</bean>
Hmm, wasn't this Java stuff supposed to hard? Guess not.
Push Example 3 - Push with ActiveMQ integration
Here's something I didn't show in Malmö.
The scenario is the following: your site is so utterly cool that it gets a huge increase in traffic. You have used Reverse Ajax extensively without ever thinking about what happens if you need to scale and synchronize over a number of servers. Your sysadmin tells you that your database can take a whole lot more but your servlet container is maxed out. What to do?
Let's use ActiveMQ as a message bus to synchronize messages over a number of different JVMs.
And yes, I know there's a lot of different ways to do this. This is only to show that the task need not be that advanced or cumbersome to accomplice.
And in that spirit I'll just integrate the message broker in our JVM (you'll need to run that separately to get the synchronizing over multiple JVM of course, the code stays the same though).
In our applicationContext.xml:
<!-- Using example from http://activemq.apache.org/how-do-i-embed-a-broker-inside-a-connection.h... -->
<!-- lets create an embedded ActiveMQ Broker -->
<amq:broker useJmx="false" persistent="false">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://localhost:0" />
</amq:transportConnectors>
</amq:broker>
<amq:topic id="destination" physicalName="com.bv10.dwr.push.topic"/>
<!-- JMS ConnectionFactory to use, configuring the embedded broker using XML -->
<amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost"/>
<!-- Spring JMS Template -->
<bean id="myJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<!-- lets wrap in a pool to avoid creating a connection per send -->
<bean class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory">
<ref local="jmsFactory" />
</property>
</bean>
</property>
</bean>
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="com.bv10.demo.jms.consumer.DWRTopicConsumer">
<property name="ajaxService" ref="myAjaxService" />
</bean>
<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener" />
</bean>
Ok, that probably needs to be explained a bit. In J2EE there's someting called a Message Driven Bean, it sits and listens to incoming messages from a Topic or a Queue. We'll be using a Message Driven POJO since we not using the whole J2EE stack.
Let's have a look at it:
public class DWRTopicConsumer implements MessageListener {
private AjaxService ajaxService;
public void setAjaxService(AjaxService ajaxService) {
this.ajaxService = ajaxService;
}
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
TextMessage textMessage = (TextMessage) message;
String text = textMessage.getText();
if(Const.DO_PUSH.equals(text)) {
ajaxService.doPushTriggeredByJMS();
}
} catch (JMSException ex) {
throw new RuntimeException(ex);
}
} else {
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
}
So this listener will sit and wait for incoming messages and use our ajaxService to trigger a push.
The button on the HTML page will call the following code:
public void createMessage() {
this.jmsTemplate.send(this.topic, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(Const.DO_PUSH);
}
});
}
And if everything is set up correctly you've now using a message broker as a bus to synchronize your specific events over a number of different servlet containers. Your sysadmin is happy again at least for a while until your database grinds to a halt.
What I'd hope to show with this example is the rich variety of components out there to help you create that next big web site. You'll get a long way with these kind of tools and they are not complex to use whatever you'll hear in blogosphere from competing technologies.
And on that note, don't take my word on it, try them out yourself first. Why not start with these examples and work from there?
Spring MVC Partial Example
Starting 5 minutes crash course on Ajax spiced Spring MVC.
This next example is not DWR based, though a crucial building block I've used to facilitate partial updates on a view.
Lets be clear about that you can certainly do complex updates client wise with marshalled data and some JavaScript coding or you can use the technique described at http://getahead.org/dwr/server/generic.
I've chosen a different path for really complex updates (loads of rendering that is), using reverse ajax as the trigger mechanism of partial updates. So which approach suites your project best? Depends on your mileage I guess. There's no golden hammer.
We are just going to look at a simple example for partial updates with no reverse ajax going on whatsoever. But with this example your toolbox should be quite sufficiently filled.
Coding Over Configuration, buzzword bingo huh?
I think it makes sense though, you can quickly get up and running using Spring MVC and their MultiActionController, jsp tag library and Prototype.
Here's the spring setup:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="demoController" class="com.bv10.demo.controller.DemoController">
<property name="personService" ref="myPersonService" />
</bean>
</beans>
Lets have a look on a form that'll do an ajax post and update using Prototype and the Spring multiaction controller.
public class DemoController extends MultiActionController {
private PersonService personService;
public void setPersonService(PersonService personService) {
this.personService = personService;
}
public ModelAndView index(HttpServletRequest request, HttpServletResponse response) {
ModelAndView mav = new ModelAndView("demo");
FilterCommand command = new FilterCommand();
mav.addObject("command", command);
List<Person> persons = personService.find(command);
mav.addObject("persons", persons);
return mav;
}
public ModelAndView filter(HttpServletRequest request, HttpServletResponse response, FilterCommand command) {
ModelAndView mav = new ModelAndView("_partial");
List<Person> persons = personService.find(command);
mav.addObject("persons", persons);
return mav;
}
}
This essentially means that the mappings is on the following pattern: server/controller/action.
In our example: http://localhost:8080/oredev/demo/index maps against our DemoController:s method index. You get the basic idea.
Lets have a look at the view then, note the jsp include (partial) and the small prototype javascript at the top.
demo.jsp
<html>
<head>
<title>Spring MVC Partial Example</title>
<script type="text/javascript" src="../js/prototype.js"></script>
<script type="text/javascript">
function doFilter() {
new Ajax.Updater('result','filter.htm', {evalScripts:true, parameters: Form.serialize($('filterForm'))});
}
</script>
</head>
<body>
<a href="../index.html">Back</a>
<p>Filter list of persons using a Spring MVC to provide a partial list from com.bv10.demo.controller.DemoController
through a Prototype Ajax call.</p>
<form:form id="filterForm" commandName="command">
<div>FirstName: <form:input path="firstName" /></div>
<div>LastName: <form:input path="lastName" /></div>
<div>Description: <form:input path="description" /></div>
<input type="button" onclick="doFilter();" value="Filter" />
</form:form>
<div id="result">
<jsp:include page="_partial.jsp" />
</div>
</body>
</html>
And here is what the small partial looks like and yes, the example is dead simple. Try to imagine a much more complex view.
_partial.jsp
<dl>
<c:forEach var="person" items="${persons}" >
<dt>${person.description}</dt>
<dd>${person.firstName} ${person.lastName}</dd>
</c:forEach>
</dl>
So, have another look at the javascript in the main view. It uses prototypes Ajax.Updater and Form.serialize to package our form, send it to the filter method in our controller (note that spring converts the form into a java object) and finally switches the content in the div tag containing the result.
That should have taking you a maximum of 5 minutes to grok eh? If you haven't done anything like this before please download the source code and hook it up with your favorite debug environment and step through it. Also if you haven't done this already (why is that?) install Firefox with the excellent addon Firebug. With these tools at your hand you can watch what happens in the background of your ajax application and it's your salvation when you find yourself in need of debugging.
| Attachment | Size |
|---|---|
| dwr-simple-demo-maven.zip | 69.93 KB |