Saturday, October 1, 2011

Using DWR with JBoss and EJBs

Today we're going to get our hands dirty with the Direct Web Remoting (DWR) library in conjunction with JBoss and the EJB mechanism.

The why

DWR is a powerful library for all things related to remote method calls using HTTP. It can serialize lots of things, use DTOs if provided but above all it does one thing so easy it should be forbidden: It allows you to create or retrieve an instance and call its method directly from your JavaScript. This is why I'll always favor DWR over hand-written mechanisms or misuse of REST libraries like RESTfully. REST is all about resources - Ajax not necessarily...

The how

We're going to use DWR version 3.0.M1 as this is the latest available in Maven repository at the time of writing. Creating the project itself is outside of the scope of this post however configuring the framework isn't so we're start with that.

web.xml


<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <servlet>
    <servlet-name>dwr</servlet-name>
    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>dwr</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
  </servlet-mapping>
</web-app>
As you can see here this is pretty much the basic that you'd expect from a web application that has one servlet in it. The other stuff will be done using a HTML page and a stateless local bean so let's get on with it.

Service interface and ServiceBean implementation

package com.aplaline.example.ejb;

import javax.ejb.Local;

@Local
public interface Service {
	String action();
}
Nothing fancy here - let's move on to the implementation:
package com.aplaline.example.ejb.impl;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.aplaline.example.ejb.Service;

@Stateless(mappedName="ServiceBean")
public class ServiceBean implements Service {
	@Override
	public String action() {
		return "Hello, world! from EJB!";
	}
}
Again... absolutely nothing fancy here - standard Hello, world! style bean. Let's see how we can configure DWR to serve the Service.action() method...

dwr.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
    "http://directwebremoting.org/dwr/dwr30.dtd">

<dwr>
  <init>
    <creator id="ejb3" class="com.aplaline.dwr.Ejb3Creator" />
  </init>
  <allow>
    <create creator="ejb3" javascript="ServiceBean">
      <param name="bean" value="ear/ServiceBean/local" />
    <param name="interface" value="com.aplaline.example.ejb.Service"/>
    </create>
  </allow>
</dwr>
Now that's the meat I'm talking about! Let's get a closer look at what's in there:

The creator


The creator class originally coming from DWR is best suited for other J2EE containers but it has a huge issue with JBoss so we're implementing our own, JBoss-friendly one:
package com.aplaline.dwr;

import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.directwebremoting.create.AbstractCreator;
import org.directwebremoting.extend.Creator;
import org.directwebremoting.util.LocalUtil;
import org.directwebremoting.util.Messages;

public class Ejb3Creator extends AbstractCreator implements Creator {
  private String bean = "";
  private String interfaceClass = "";
  
  public void setBean(String bean) {
    this.bean = bean;
  }

  public void setInterface(String interfaceClass) {
    this.interfaceClass = interfaceClass;
  }

  @Override
  public Class getType() {
    try {
      return LocalUtil.classForName(interfaceClass);
    } catch (ClassNotFoundException ex) {
      throw new IllegalArgumentException(
          Messages.getString("Creator.BeanClassNotFound", interfaceClass)
      );
    }
  }

  public Object getInstance() throws InstantiationException {
    Context jndi = null;

    try {
      Properties props = new Properties();
      props.load(getClass().getResourceAsStream("/jndi.properties"));
      jndi = new InitialContext(props);
      return jndi.lookup(bean);
    } catch (Exception ex) {
      throw new InstantiationException(bean + " not bound:" + ex.getMessage());
    } finally {
      if (jndi != null) {
        try {
          jndi.close();
        } catch (NamingException ex) {
          // Ignore
        }
      }
    }
  }
}
What it does is it allows you to specify the interface class as well as full bean name as depicted in the dwr.xml example above.

The create


In here we're specifying all the bits and pieces needed for the framework later on to create JavaScript proxy and identify our bean when the time comes. This is the time to see how we can use it, shall we?

The index.html

<html>
<head>
  <title>Example</title>
  <script type="text/javascript" src="/dwr/dwr/interface/ServiceBean.js"></script>
  <script type="text/javascript" src="/dwr/dwr/engine.js"></script>
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
  <script type="text/javascript">
    $(document).ready(function() {
      ServiceBean.action(function(response) {
        $("#output").append("<p>" + response + "</p>");
      });
    });
  </script>
</head>

<body>
  <h1>Example</h1>
  <div id="output"></div>
</body>
</html>
This needs a word or two of explanation. At first we're including a JavaScript proxy class that will serve as a mediator between JavaScript and the server side. Then there's the required engine.js inclusion. This includes all the bits and pieces of client side DWR. Then we're including jQuery from Google CDN because I hate to re-get the library over and over again. Then there's the most interesting part - the usage: The ServiceBean object is created by inclusion of the ServiceBean.js resource. It automatically has a action method that takes all the parameters as the server-side counterpart would (none in this example) and as the last parameter there's a callback to execute after the response is returned. Pretty simple, right?

Bottom line

If you'll ever find yourself in need to call some EJB (or Spring or Guice) managed instances from JavaScript you should seriously consider using DWR as it makes life a lot easier.

As always here is a ready-to-use example for you to check out (tested with JBoss 4.2.3).

I've been using it from within Eclipse thus the name of EAR is "ear" (and thus the JNDI name of the EJB starts with "ear/"). If you run it from command line please update the name accordingly before use!

Have fun!

No comments: