If you go to the address specified here, you may notice that there are three things you can download. One is the dwr.jaritself. The other is the complete source code for DWR. The third is named dwr.war, and if you have a few moments, you may want to spend them grabbing that too. Drop it into Tomcat’s webappsdirec- tory and fire it up. This webapp is a little demo app demonstrating a number of neat DWR functionalities. It actually contains a number of example applications as well, including a chat application, a quick address entry form where address details are populated automatically based on postal code, and a real-time stock quote demo using something called General Interface (GI), which is an open source UI library with DWR at its core.
3. In 480 B.C., the Battle of Thermopylae took place. This military engagement pitted King Leonidas of Sparta, a city in Greece, with 300 of his finest soldiers (and 700 Thespian volunteers, which many people forget . . . Thespian here does not refer to actors, but to the Greek city Thespiae) in a last-stand battle against the invading Persian Empire, lead by King Xerxes, at the pass of Thermopylae, a very narrow valley pass. Leonidas and his troops held off the Persian army, which reportedly numbered upwards of 80,000 eventually (in three waves of attack actually: 10,000 the first day, 20,000 the second day, and 50,000 the final day). Xerxes’ troops were finally victorious, but not without heavy losses.
This battle is often cited as the best example of how well-trained, disciplined troops using natural resources to their fullest can overcome seemingly unbeatable odds (the fact that they lost notwith- standing). Oh yeah, the recent movie 300 was based on this battle. Pretty good flick if you don’t mind lots of violence and men with abs you and I will never have!
Now for the first round of changes required. First, we need to remove the MathServlet, since we won’t need it (DWR will essentially take its place), and we therefore also need to add an entry for the DWRServletinto web.xml. Listing 2-6 shows the modified web.xmlcode.
Listing 2-6.The Modified web.xmlFile, All DWR-ified
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app id="firstdwr" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- The DWR servlet. -->
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>crossDomainSessionSecurity</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
<!-- Default page to load in context. -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Don’t worry too much about the details of the DWRServletsetup; we’ll get into that in the next section. For now, just note that the servlet is mapped to all paths beginning with /dwr.
This will be important when we look at the updated JSP.
Speaking of the JSP, we have to modify that as well. Listing 2-7 shows those updates.
Listing 2-7.The Updatedindex.jspFile
<html>
<head>
<title>firstdwr</title>
<script type="text/javascript" src="dwr/interface/MathDelegate.js"></script>
<script type="text/javascript" src="dwr/engine.js"></script>
<script>
var a = 0;
var b = 0;
var op = "";
function doMath() {
a = document.getElementById("numA").value;
b = document.getElementById("numB").value;
op = document.getElementById("op").value;
if (op == "add") {
MathDelegate.add(a, b, doMathCallback);
op = "+";
} else if (op == "subtract") {
MathDelegate.subtract(a, b, doMathCallback);
op = "-";
} else if (op == "multiply") {
MathDelegate.multiply(a, b, doMathCallback);
op = "*";
} else if (op == "divide") {
MathDelegate.divide(a, b, doMathCallback);
op = "/";
} }
var doMathCallback = function(answer) {
document.getElementById("resultDiv").innerHTML = "<h1>" +
"Result: " + a + " " + op + " " + b + " = " + answer + "</h1>";
}
</script>
</head>
<body>
<span id="resultDiv"></span>
Please enter two numbers, select an operation, and click the equals button:
<br><br>
<input type="text" id="numA" size="4">
<select id="op">
<option value="add">+</option>
<option value="subtract">-</option>
<option value="multiply">*</option>
<option value="divide">/</option>
</select>
<input type="text" id="numB" size="4">
<input type="button" value="=" onClick="doMath();">
</body>
</html>
A couple of changes have been made, beginning with the removal of the JSP scriplet block and the HTML form (the elements of the form are still there, but notice they are no longer contained in a <form>element). The form isn’t really necessary at this point because we’re going to be grabbing the field values manually and using them as parameters to a method call.
Also note that the fields no longer have nameattributes, they now have idattributes instead.
This is so that the JavaScript code that has been added can access them directly by id.
Next, note that the submit button has been changed to a plain old button element. When the button is clicked, the JavaScript doMath()function will be called, which is a new addition, as is the entire <script>block up top, and the two <script>imports before it.
Speaking of the imports, there are two of them. One of them, the one that pulls in the file engine.js, is the actual DWR client-side code. The other, dwr/interface/MathDelegate.js, is the client-side proxy stub for the MathDelegateclass. Notice that both of these URIs begin with dwr. Remember that all URIs beginning with dwrwill be handled by the DWRServlet. Hence, the two pieces of JavaScript that are being imported are being generated by DWRServlet, one of them (the proxy stub) dynamically.
Now, let’s move on to the JavaScript that has been added after those two imports. This in effect takes the place of the JSP scriplet from the original version. First, we have three variables in global scope, two for the numbers the user enters (aand b) and one for the operation the user performs (op). When the equals button is clicked, doMath()is called, and the first thing it does is to retrieve those three pieces of information. It then branches based on the operation.
In each branch, a call to the appropriate method of MathDelegateis made, much like the MathServletdid in the original version. The MathDelegateobject that the method is being called on is the proxy stub object that DWR generated to represent the server-side MathDelegate. The code in the four branches also sets the opvariable to the applicable math symbol, so we can display the equation properly as in the original version.
Did you notice anything suspicious about the four calls to the methods of MathDelegate?
What’s up with that third parameter? It doesn’t appear in the method signature of the actual MathDelegateclass. As it turns out, this parameter specifies a JavaScript function that will be
“called back” when the call to the real server-side MathDelegateobject returns. This is because JavaScript by nature is an asynchronous language, while Java is synchronous. This means there is no mechanism to make the remote call via Ajax and wait for it to return (other than specifying the call is to be synchronous, which you can do at a low level with the XMLHttpRequestobject; however, it blocks everything in the browser, meaning it becomes unresponsive while awaiting the request’s completion, so it’s generally a bad idea and not quite what we want). Therefore, the callback mechanism is the logical answer.
Once the request completes, the callback function, doMathCallback(), is called. Notice the parameter to the method. This is a representation of the return type of the method called, which DWR has marshaled for us. This can be virtually any data type, intrinsic or custom, and in this case it’s just a simple integer. Once we have the value, the code simply constructs the appropriate markup to display the equation, and inserts it into a new <span>element named resultDiv.
There is one new piece to the puzzle, and that’s the dwr.xmlfile that we have to create to tell DWR a few things. Create the dwr.xmlfile from the code in Listing 2-8 and save it to firstdwr/WEB-INF.
Listing 2-8.The New dwr.xmlFile
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
"http://getahead.org/dwr/dwr20.dtd">
<dwr>
<allow>
<create creator="new" javascript="MathDelegate">
<param name="class" value="app.MathDelegate" />
</create>
</allow>
</dwr>
The dwr.xmlfile’s only job here (although it can do more, as we’ll see later) is to tell DWR what classes it is allowed to remote, in this case just the MathDelegateclass (convenient, since that’s the only one in the app!). You can specify the name of the JavaScript object representing the server-side object, although most of the time it’s likely to be the same, as it is here. The other attributes and options we’ll go over in the next section.
So, what do we wind up with after all these changes? Well, I’m not going to show a screen- shot here because it would look identical to Figure 2-6. However, running the webapp, you should notice a difference: the page does not refresh when you click the equals button.
Instead, the result appears where it appears in the original version, but without changing any- thing else on the page. It’s also a bit faster because of this, as you can probably perceive (it’s not a huge difference if you’re running these both locally, but you should be able to notice by the absence of page flickering when you click the button due to the refresh not being done).
And with that, you’ve now written your first DWR application, even if an incredibly simplistic one!
The DWR Test/Debug Page
Before we get into the details of DWR configuration, I want to point out another facility that DWR gives you automatically when you use it in your application, and that is the test/debug page. If you access the URL http://localhost:8080/firstdwr/dwr/index.html, you’ll see the page shown in Figure 2-8.
Now granted, that doesn’t look like any big deal. But, go ahead and click the link and you’ll see something infinitely more interesting! Since DWR knows about our MathDelegateclass, it can automatically generate a page that shows us what methods are available on the object, how to access them, and what imports we’ll need, and even provides a method to actually test these methods! Figures 2-9 and 2-10 show what this page looks like (the page scrolls larger
than I could capture at once, that’s why it’s broken into two figures, but understand both of these represent a single page).
Figure 2-8.The DWR test/debug page
Figure 2-9.The MathDelegatetest/debug page, part 1
Figure 2-10.The MathDelegatetest/debug page, part 2
You can even see in Figure 2-9 where I’ve actually tested the add()method. The result is shown next to the Execute button. Any of the methods can be tested in this fashion, without you having to have written a single bit of code!
This page also gives you some helpful notes at the bottom about various situations that you will want to be aware of. For instance, the note about overloaded methods is a good one to keep in mind. As it says, because JavaScript doesn’t support overloaded methods (you can fake it, but it’s not true overloading), this is something you need to be aware of when writing your server-side classes.
All in all, this is a fantastic facility that DWR offers that you will want to keep in mind for sure. It can save you all sorts of time and effort when getting things working.
Configuring DWR Part 1: web.xml
We’ll begin looking at how to configure DWR, and the logical starting point is the web.xmlfile.
The absolute minimum you could add to an existing web.xmlto get DWR up and running is this:
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
While you have the freedom to map the servlet to any path you’d like, or use extension- based mapping even, I very much suggest using this standard mapping since it’s logical and will be one less difference you have to mentally parse when you’re looking for help on the Web.
Beyond this basic setup, there are a number of init parameters you can specify. They are summarized in Table 2-1.
Table 2-1.The DWRServletInit Parameters
Parameter Description
allowGetForSafariButMakeForgeryEasier In Safari 1.x, there is a browser bug that causes the body contents of POSTrequests to be dropped. This is, to put it mildly, a bit of a problem in most cases.
Setting this to truewill make DWR work in that browser regardless. This defaults to false.
crossDomainSessionSecurity Setting this to falsewill enable requests from other domains other than the one that app is hosted on.
This can be a rather significant security risk, so you should change this with caution and be sure you understand all the consequences. The default value is true.
allowScriptTagRemoting Script tag remoting is a method of Ajax where a
<script>tag is dynamically added to the page. The browser then goes and retrieves the specified JavaScript file. This file will be in the form of a function call with data (usually JSON or some similar data structure) as the argument to the function. The function already exists on the page, so what you in effect have set up is a mechanism where you can request a resource, and that resource when inserted into the page causes a JavaScript callback function to be executed, passing it the data you were interested in retrieving. This is a useful approach to Ajax because it is one of the few ways you can do cross-domain Ajax calls (the XMLHttpRequestobject, for instance, has what’s known as a same domain policy, meaning it will only allow requests to the domain the host document was served from).
Setting this to true(which is the default) allows DWR to use this method if necessary.
debug Setting this value to trueenables the test/debug
page previously discussed. By default, this is false.
Parameter Description
scriptSessionTimeout This sets the amount of time in milliseconds before a script session times out. The default is 1800000, or 30 minutes.
maxCallCount This sets the maximum number of requests allowed in a batch. The default is 20. The purpose of this setting is to help reduce the possibility of
overloading the server by mistake, and to help avoid Denial of Service attacks.
activeReverseAjaxEnabled When set to true, the polling and Comet reverse Ajax techniques are enabled. This defaults to false.
maxWaitingThreads This sets how many threads the servlet will keep in an active state for handling requests. The default is 100.
maxPollHitsPerSecond When using the polling reverse Ajax method, this is the maximum number of requests per second allowed. The default value is 40.
preStreamWaitTime This is the maximum amount of time in milliseconds to wait before opening a stream to reply. By default, this is 29000.
postStreamWaitTime This is the maximum amount of time in milliseconds to wait after opening a stream to reply. By default, this is 1000.
[Interface Name] This is used to override parts of DWR without having to build it from source. The default implementation is the default.
ignoreLastModified By default this is false. DWR uses Last-Modified headers to help the client determine when a request for a given resource should be made, thereby reducing those requests due to smart caching.
Setting this to truewill disable this capability.
scriptCompressed DWR has the ability to perform a very simple compression of the returned JavaScript, and setting this parameter to trueenables that compression (by default it’s false). Along with this is an undocumented (although official) parameter namedcompressionLevel. There are three levels of compression: none,normal, and ultra. See the Javadoc for the JavascriptUtilclass for more details.
sessionCookieName To support session, DWR can use URL rewriting to include the session cookie with each request it makes. By default, the standard JSESSIONIDcookie name is used, but this parameter allows you to use a different cookie name if you wish (of course, your container will have to know to use the alternate name as well).
Continued
Table 2-1.Continued
Parameter Description
normalizeIncludesQueryString When using reverse Ajax, pages with differing query strings are considered the same page. However, for some sites, this assumption is not correct. Setting this parameter to true(the default being false) will force DWR’s reverse Ajax implementation to consider the query string when comparing URLs.
overridePath Some servlet containers, when a web server is in front of them, will alter the path for the request, which will cause DWR to set the wrong locations for its Ajax calls. Using this parameter, you can override the path DWR uses to account for this. By default, this is not used.
As you can see, there are quite a few configuration options available on the DWRServlet itself. In most cases you’ll find the defaults to be just what you need, and that will generally be the case in the applications throughout this book. However, as exceptions come up, I’ll explain the reasoning behind the parameters used. The one exception is the debugparameter, which you’ll see used virtually every time. This is just too handy a tool to turn off anywhere but a pro- duction environment!
You should also be aware that there is an alternate controller servlet that you can choose if you wish to use the annotations capabilities DWR offers. Annotations are covered in the next chapter, and I’ll give you the full details about the other servlet when we get there.
You probably noticed a couple of references to something called reverse Ajax, but what exactly is that? We’re going to get into it in detail in Chapter 3, but as a bit of a preview, reverse Ajax is a technique that allows you to asynchronously send data from the serverto the client. In point of fact, it’s not trulydoing that, but it very much gives the appearance that it is, and really for all practical purposes you can treat it as if it were really doing it. Like I said, don’t worry about it much now, we’ll get to this in the next chapter.
Configuring DWR Part 2: dwr.xml
The next topic of conversation is the DWR-specific configuration housed in the dwr.xmlfile.
DWR actually offers two ways of configuring it, through Java 5 (and up) annotations and the dwr.xmlfile. You can in fact do everything you do with dwr.xmlwith annotations instead, or you can supplement dwr.xmlwith annotations, but one step at a time here—let’s look at the configuration file approach first.
The basic structure of the dwr.xmlfile can be shown concisely in image form, and Figure 2-11 is just such an image.