13.2. DWR

Direct Web Remoting (DWR) is an Ajax framework for Java/JSP available for download at http://getahead.ltd.uk/dwr. DWR works similarly to JPSpan in that it uses Java's version of reflection to examine Java bean classes and then create JavaScript wrappers to call the various methods. Also like JPSpan, DWR includes its own JavaScript library for cross-browser Ajax communication, freeing the developer from worrying about browser incompatibilities. DWR assumes the use of Apache Tomcat (http://tomcat.apache.org).

NOTE

DWR expects that the classes you use will be Java beans, meaning that they can be created without passing any information to the constructor. This is important because the server-side objects don't persist from request to request and need to be created from scratch each time.

13.2.1. Using DWR

Setting up DWR in your web application is very simple. The first step is to place the dwr.jar file into the WEB-INF/lib directory. The next step is to edit the web.xml file contained in WEB-INF. There are two sections that need to be added.

  • The first section describes the DWR invoker servlet:

    <servlet>
      <servlet-name>dwr-invoker</servlet-name>
      <display-name>DWR Servlet</display-name>
      <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
      <init-param>
         <param-name>debug</param-name>
         <param-value>true</param-value>
      </init-param>
    </servlet>

    This code needs to go with the other <servlet/> tags inside of web.xml.

  • The second section that needs to be added is as follows:

    <servlet-mapping>
      <servlet-name>dwr-invoker</servlet-name>
      <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>

    This information provides the URL that can be used to call the DWR invoker and must be located alongside any other <servlet-mapping/> tags in the file.

The final step is to create a file called dwr.xml in the WEB-INF directory. This file specifies the Java classes that should be wrapped by DWR for use on the client:

<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
    "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
  <allow>
    <create creator="new" javascript="JDate">
      <param name="class" value="java.util.Date"/>
    </create>
    <create creator="new" javascript="Demo">
      <param name="class" value="your.java.Bean"/>
    </create>
  </allow>
</dwr>

This is the example file suggested on DWR's web site. The root element, <dwr/>, contains an <allow/> element. Each of the <create/> elements contained within specify objects that are allowed to be created by JavaScript. In each <create/> element, the javascript attribute specifies the name that must be used in JavaScript to invoke the Java bean. Since there is already a Date object in JavaScript, this example specifies the constructor name JDate for using the Java Date object. The <param/> element specifies the complete class name for the bean to use. You can add any number of <create/> elements to allow JavaScript wrappers for your custom beans.

After making these changes, you can go to the following URL to test the installation:

http://hostname/webappName/dwr/

In this URL, hostname should be your machine name and webappName should be the name of your web application (the folder name in which the files are contained). This redirects you to a test suite page that displays all of the Java beans that are available for remote invocation. Clicking on one of these class names brings up a test suite page that allows direct invocation of certain methods (see Figure 13-1).

Figure 13-1 displays the test page for the Java Date object. It displays helpful information such as what JavaScript URLs to include if you want to use the object, as well as warnings about using overloaded methods.

If you see an error message when going to the test page that seems to originate from one of the Java XML classes, it's possible that you have two copies of the XML API on your system. To remedy this situation, go to the installation folder for Tomcat, which is normally named jakarta-tomcat-5.x.x, and open the folder commonendorsed. Rename the file xml-apis.jar to xml-apis.jar.bak. If this doesn't fix the problem, try restarting Tomcat.

To use a Java bean from JavaScript, you need to include at least two files. The first needs to be a URL that tells DWR what bean you want to use. That URL is in the following format:

<script type="text/javascript" src="/webappName/dwr/interface/bean.js"></script>

Figure 13.1. Figure 13-1

In this example, webappName is once again the directory in which your web application resides and bean is the name used in the javascript attribute of the <create/> element relating to the specific bean you want to use. For example, to use the Java Date object from the example dwr.xml file, include this in your page:

<script type="text/javascript" src="/webappName/dwr/interface/JDate.js"></script>

There should be one of these <script/> elements for each Java bean that is to be used on the client. After that, you need to include the DWR JavaScript engine:

<script type="text/javascript" src="/webappName/dwr/engine.js"></script>

It's this file that's responsible for handling the cross-browser communication between client and server.

On the client side, each bean is encapsulated in an object with methods identical to those that exist on the server. For example, the Java Date object is represented through a JavaScript object called JDate, which has all of the methods of the Java Date object. Unlike using JPSpan, DWR doesn't require you to create a new object for each call; every method is treated as static on the client. Additionally, each method accepts one extra argument: a callback function that accepts the return value from the method call. To call the Date object's toGMTString() method, for instance, the code looks like this:

function handleGMTStringResponse(sResponse) {
    alert(sResponse);
}

JDate.toGMTString(handleGMTStringResponse);

Note again that no objects need to be created; the toGMTString() method is called directly off of the JDate object. Since toGMTString() doesn't accept any arguments, the only argument passed in is the callback function, handleGMTStringResponse(). If the method did accept arguments, then the callback function would be the last argument.

13.2.2. DWR Example

Since DWR works in a similar fashion as JPSpan, it makes sense to recreate the same example. Once again, you'll be pulling information out of the database about a specific customer. In order to access a MySQL database from a Java, you'll need to download MySQL Connector/J from www.mysql.com/products/connector/j. Install the most recent version using the instructions provided with the download.

Next, create a new directory under webapps called DwrExample. Under that directory, create a WEB-INF directory with two subdirectories: classes and lib. Copy the web.xml file from another application or create one using the code in the previous section and place it in the WEB-INF directory. Also copy the dwr.xml file from the previous section into this directory.

All of this code is available for downloading at www.wrox.com. You may download this example and simply copy it into your webapps directory.

13.2.2.1. The CustomerInfo Class

As with the previous example, the CustomerInfo class handles most of the work:

package wrox;
import java.sql.*;

public class CustomerInfo {

    public String getCustomerInfo(int id) {

         //more code here
    }
}

In order to use databases, the java.sql package is included in this file. These objects will be used in the getCustomerInfo() method to retrieve the information from the database. The very first step in that method is to attempt to create the MySQL driver for database access:

package wrox;
import java.sql.*;

public class CustomerInfo {

    public String getCustomerInfo(int id) {
        try {

            Class.forName("com.mysql.jdbc.Driver").newInstance();

            //more code here

        } catch (Exception e){

return "An error occurred while trying to get customer info.";
        }
    }
}

This is the standard way of creating database drivers in Java, passing in the fully qualified class name to Class.forName(). Calling newInstance() is necessary due to some quirkiness in Java implementations. This effectively loads the database driver (or causes an error, which is caught and an error message is returned).

Next, a connection must be made to the database server. This is done using a URL in the following format:

jdbc:mysql://dbservername/dbname?user=your_user_name&password=your_password

The dbservername may be localhost but may also be a full domain name for the database server; the dbname should be the name of the database being accessed. Of course, you need to provide the appropriate username and password for that database as well. This all comes together as an argument to getConnection() method for DriverManager:

package wrox;
import java.sql.*;

public class CustomerInfo {

    public String getCustomerInfo(int id) {
        try {

            Class.forName("com.mysql.jdbc.Driver").newInstance();


            String dbservername = "localhost";
            String dbname = "your_db_name";
            String username = "your_user_name";
            String password = "your_password";
            String url = "jdbc:mysql://" + dbservername + "/" + dbname + "?user="
                         + username + "&password=" + password;

            Connection conn = DriverManager.getConnection(url);

            //more code here

        } catch (Exception e){
            return "An error occurred while trying to get customer info.";
        }
    }
}

This new section of code creates a connection to the database and stores it in the variable conn, which now can be used to run queries:

package wrox;
import java.sql.*;

public class CustomerInfo {

public String getCustomerInfo(int id) {
        try {

            Class.forName("com.mysql.jdbc.Driver").newInstance();

            String dbservername = "localhost";
            String dbname = "your_db_name";
            String username = "your_user_name";
            String password = "your_password";
            String url = "jdbc:mysql://" + dbservername + "/" + dbname + "?user="
                         + username + "&password=" + password;

            Connection conn = DriverManager.getConnection(url);

            String sql = "Select * from Customers where CustomerId=" + id;
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);
            boolean found = rs.next();

            //more code here

        } catch (Exception e){
            return "An error occurred while trying to get customer info.";
        }
    }
}

Here, a Statement object is created using the createStatement() method. This object can then be used to execute the query by passing the SQL query into executeQuery(), which returns a ResultSet object. The next() method on a ResultSet object returns false if there are no matching records or true if there's at least one (in this case, there should be only one). So if found is true, that means the data is available and can be used. All that's left is to return the data formatted appropriately:

package wrox;
import java.sql.*;

public class CustomerInfo {

    public String getCustomerInfo(int id) {
        try {

            Class.forName("com.mysql.jdbc.Driver").newInstance();

            String dbservername = "localhost";
            String dbname = "your_db_name";
            String username = "your_user_name";
            String password = "your_password";
            String url = "jdbc:mysql://" + dbservername + "/" + dbname + "?user="
                         + username + "&password=" + password;

            Connection conn = DriverManager.getConnection(url);

            String sql = "Select * from Customers where CustomerId=" + id;
            Statement stmt = conn.createStatement();

ResultSet rs = stmt.executeQuery(sql);
            boolean found = rs.next();


            StringBuffer message = new StringBuffer();

            if (found) {
                message.append(rs.getString("Name"));
                message.append("<br />");
                message.append(rs.getString("Address"));
                message.append("<br />");
                message.append(rs.getString("City"));
                message.append("<br />");
                message.append(rs.getString("State"));
                message.append("<br />");
                message.append(rs.getString("Zip"));
                message.append("<br /><br />");
                message.append("Phone: " + rs.getString("Phone"));
                message.append("<br /><a href="mailto:");
                message.append(rs.getString("Email"));
                message.append("">");
                message.append(rs.getString("Email"));
                message.append("</a>");
            } else {
                message.append("Customer with ID ");
                message.append(id);
                message.append(" could not be found.");
            }

            rs.close();
            conn.close();

            return message.toString();
        } catch (Exception e){
            return "An error occurred while trying to get customer info.";
        }
    }
}

In this final section of the code, a StringBuffer object is created to hold the response that will be sent back to the client. If a record is found, then a block of text with HTML formatting is returned, which is done by using the getString() method of the RecordSet, passing in the name of each column. If a record is not found, an error message is built up in the message object. Then, the RecordSet and Connection are both closed and the message is returned by calling toString().

Save this class in CustomerInfo.java. You'll need to compile the class and place it into the WEB-INF/classes/wrox directory to be accessible to DWR.

13.2.2.2. Updating dwr.xml

In order for DWR to know that you need this class on the client, some additions to dwr.xml are necessary. The following code must be added inside of the <allow/> element:

<create creator="new" javascript="CustomerInfo">
  <param name="class" value="wrox.CustomerInfo" />
</create>

After making this change, restart Tomcat to ensure that the new settings have been picked up. Now, you can go to http://localhost/DwrExample/dwr to verify that the CustomerInfo class is being handled properly. If it is, you should see it listed on the test page and be able to call getCustomerInfo().

13.2.2.3. Creating the Client Page

The client page is the same as the JPSpan example, with the obvious difference being the inclusion of the DWR files:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>JPSpan Example</title>
    <script type="text/javascript"
            src="/DwrExample/dwr/interface/CustomerInfo.js"></script>
    <script type="text/javascript"src="/DwrExample/dwr/engine.js"></script>
    <script type="text/javascript"src="DwrExample.js"></script>
</head>
<body>
    <p>Enter customer ID number to retrieve information:</p>
    <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>
    <p><input type="button" value="Get Customer Info"
              onclick="requestCustomerInfo()" /></p>
    <div id="divCustomerInfo"></div>
</body>
</html>

There are three JavaScript files included in this page. The first is the JavaScript wrapper for the CustomerInfo class, the second is the DWR JavaScript engine, and the third is the code to tie it all together:

function handleGetCustomerInfoResponse(sInfo) {
    displayCustomerInfo(sInfo);
}

function requestCustomerInfo() {
    var sId = document.getElementById("txtCustomerId").value;
    CustomerInfo.getCustomerInfo(parseInt(sId), handleGetCustomerInfoResponse);
}

function displayCustomerInfo(sText) {
    var divCustomerInfo = document.getElementById("divCustomerInfo");
    divCustomerInfo.innerHTML = sText;
}

As with the JPSpan example, you can see the advantage of using DWR for Ajax communication: the code becomes much simpler. By using only a callback function and the generated CustomerInfo object, all of the client-server communication is hidden from the developer.

13.2.3. More about dwr.xml

The dwr.xml file used in the previous example was fairly simple, including a <create/> element for the CustomerInfo object with basic settings:

<create creator="new" javascript="CustomerInfo">
  <param name="class" value="wrox.CustomerInfo" />
</create>

This code tells DWR to create a CustomerInfo object using the Java new operator and allow the client to call any of the methods on that object. Calling getCustomerInfo() from the client performs the following on the server:

CustomerInfo info = new CustomerInfo();
String result = info.getCustomerInfo(id);

For many cases, this approach is all that is needed. However, DWR provides several customization options for more complicated use cases.

13.2.3.1. Excluding Methods

Suppose that there are methods on a Java bean that should not be called by the client. This is possible when a bean is used both for client-side and server-side execution. You can exclude a method from the JavaScript wrapper by creating an <exclude/> element inside of the <create/> element. For example, the following excludes the toGMTString() method from being called on the Date class:

<create creator="new" javascript="JDate">
  <param name="class" value="java.util.Date"/>
  <exclude method="toGMTString"/>
</create>

If an attempt is made to call this method on the client, a JavaScript runtime error will occur.

13.2.3.2. The Script Creator

As mentioned previously, DWR creates beans by using the Java new operator by default. However, there are some objects that cannot be created by simply using a constructor; they may be accessible only via the methods on another class or from a call to a static getInstance() method. An example of this is the EmailValidator class from the org.apache.commons.validator package, which does not have a public constructor. In order to create an instance of EmailValidator, it's necessary to use the static method getInstance(). To provide for this, DWR allows you to create a small script section for the creation of an object.

Using BeanShell, one of the Bean Scripted Framework (BSF) languages, you can provide code to create the instance of the class. To do so, the creator attribute of the <create/> element must be set to "script". The <param/> element that previously indicated the Java class to instantiate now must specify the scripting language as BeanShell. Then, a second <param/> element is necessary, with its name attribute set to "script". Inside of this element is where the BeanShell code must import the necessary Java packages and return the object instance. For example, to create an instance of EmailValidator, use the following version of the <create> element:

<create creator="script" javascript="EmailValidator">
  <param name="language" value="beanshell"/>
  <param name="script">
    import org.apache.commons.validator.EmailValidator;
    return EmailValidator.getInstance();
  </param>
</create>

Note that there is no mention of a Java class outside of the BeanShell code. Any class of object may be created and returned through the script.

NOTE

You can read more on BeanShell at www.beanshell.org/ and see other languages supported by BSF at http://jakarta.apache.org/bsf/index.html.

13.2.3.3. The Spring Creator

The Spring framework (www.springframework.org) is designed to bring together a number of different Java technologies as well as enable practical code reuse and sophisticated management of Java beans. It is a lightweight replacement for parts of the Java 2 Enterprise Edition (J2EE), which is used to create highly scalable applications where high numbers of users are expected to use the system simultaneously. In these situations, resources such as memory need to be carefully managed. DWR provides hooks to allow usage of the Spring framework.

To access an enterprise bean from the client, you need to specify the creator attribute as "spring". Then, provide <param/> elements specifying the name of the bean and the location of the Spring configuration file, such as:

<create creator="spring" javascript="MyBean">
  <param name="beanName" value="MyBean"/>
  <param name="location" value="beans.xml"/>
</create>

It is beyond the scope of this book to discuss the full extent of the Spring framework. For more information on the Spring framework, visit www.springframework.org; for more information on the Spring integration in DWR, visit http://getahead.ltd.uk/dwr/server/spring.

13.2.3.4. The scope Attribute

The <create/> element can also accept an attribute named scope, which defines the scope in which the object should be created and stored. This value can be any one of standard Java bean scopes:

  • request: The object is created at the time of a request and destroyed after a response has been sent. This is the default if not otherwise specified.

  • page: For the purposes of DWR, essentially the same as request.

  • session: The object is created and then stored and reused for all requests during the session. In this way, you can set object values and access them in later requests.

  • application: The object is created and used by every session connecting to the web application.

If you want to use the CustomerInfo class across a session, for example, the syntax would be as follows:

<create creator="new" javascript="Customer" scope="session">
  <param name="class" value="wrox.Customer"/>
</create>

The scope attribute is particularly useful when data needs to be stored throughout the context of a session, allowing the client to set and get information off of a Java bean.

13.2.3.5. Converters

Generally speaking, DWR is very good at converting Java data types to JavaScript data types and vice versa. Strings in Java get mapped to strings to JavaScript, integers and floats in Java get mapped to number in JavaScript, and hashtables in Java get mapped to objects in JavaScript. To provide these mappings, DWR uses converters.

Converters simply convert data types between Java and JavaScript. There are a number of predefined converters that ship with DWR, such as the DateConverter, which works between the java.util.Date and the JavaScript Date object, the ArrayConverter, and the StringConverter. There is also the BeanConverter, which works between beans and JavaScript objects. This particular converter is disabled by default for security reasons. You can allow it by including a single line of code within the <allow/> element in dwr.xml:

<dwr>
  <allow>
    <convert converter="bean" match="your.full.package.BeanName"/>
  <!-- other allowed converters and creators -->
  </allow>
</dwr>

The match attribute specifies which bean to allow. You can allow an entire package by using an asterisk as a wildcard:

<convert converter="bean" match="your.full.package.*"/>

Also, it's possible to exclude certain properties from being converted by adding a <param/> element like this:

<convert converter="bean" match="your.full.package.BeanName">
  <param name="exclude" value="property1, property2"/>
</convert>

The properties and methods you want to exclude should be separated by a comma and a space. Alternatively, and more safely, you can specify which properties can be converted (all others are ignored):

<convert converter="bean" match="your.full.package.BeanName">
  <param name="include" value="property1, property2"/>
</convert>

NOTE

For more information on which converters are available and which need to be enabled before they can be used in your code, see http://getahead.ltd.uk/dwr/server/dwrxml/converters.

13.2.4. Summary of DWR

DWR is an excellent way to harness the power of Java for use in web applications. Joe Walker, the primary developer of DWR, constantly is updates the framework with new functionality designed to make web applications more responsive. Since DWR works with the literally hundreds of available Java packages, chances are that there's something already available that can perform the operations necessary for your application. There is also the advantage that there is no shortage of information, tutorials, and experts willing to share their knowledge about the Java platform.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset