Friday, October 24, 2008

Giving EJB 3.1 a Test Drive

Although not yet final, project GlassFish features already an alpha implementation of EJB 3.1. TheServerSide posted a nice series of articles on what you can expect to come - but honestly, wouldn't it be much more fun to actually get some hands dirty? Here's a quick description on how you can give it a spin. We will demonstrate the following new features:
  • Singleton Beans
  • Simplified EJB packaging
  • Simplified EJB without Interfaces
  • Unified JNDI naming
Start with downloading the latest release of GlassFish v3. The Windows installer guides you through the installation. Use the default settings except for the authentification - choose a username and a password (this used to default to "admin/adminadmin"). At the end you will be prompted by the Updatetool - start it and install the Add-Ons shown in the image below:


Beside the EJB Container we'll also install the new JAX-RS REST API to access the EJB. The JAX-RS JSR has just recently gone final, and is definitely also worth a look. Once the installation is done, we'll switch to the IDE to code our beans. I'm using Eclipse with the Maven 2 plugin to get started quickly.

Choose New Maven Project and select the maven-archetype-webapp archetype as shown in the screenshot below. We're naming our project "ejbtestdrive" - note the name as it will be used a couple of times in the further process. Finish the create wizard and add the src/main/java folder as a source folder in Eclipse after creating it.


To resolve our dependencies, lets first configure the Maven repositories to download the EJB 3.1 as well as the JAX-RS API. Add the following lines to your pom.xml:
<repositories>
<repository>
<id>glassfish</id>
<name>Glassfish Repository</name>
<url>http://download.java.net/maven/glassfish</url>
</repository>
<repository>
<id>java.net</id>
<name>java.net Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
</repositories>

Which allows us to download following dependencies:
<dependencies>
<dependency>
<groupId>javax.ejb</groupId>
<artifactId>ejb-api-3.1-alpha</artifactId>
<version>10.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

Almost done with the Maven setup. We'll just add the GlassFish Maven plugin to our build and set the compiler to deal with Java 6 (Java 5 should be fine too):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.glassfish.maven.plugin</groupId>
<artifactId>maven-glassfish-plugin</artifactId>
<version>2.1</version>
<configuration>
<glassfishDirectory>${glassfish.home}</glassfishDirectory>
<domain>
<name>domain1</name>
<adminPort>4848</adminPort>
</domain>
<user>${domain.username}</user>
<adminPassword>${domain.password}</adminPassword>
<components>
<component>
<name>${project.artifactId}</name>
<artifact>${project.build.directory}/${project.build.finalName}.${project.packaging}</artifact>
</component>
</components>
<autoCreate>true</autoCreate>
<debug>true</debug>
<echo>true</echo>
</configuration>
</plugin>
</plugins>
</build>

The referenced properties should be defined in a Maven profile, but for simplicity we'll just add them now to our POM (just replace the values with your installation location and your username/password):
<properties>
<glassfish.home>E:\work\servers\glassfish\glassfishv3-prelude\glassfish</glassfish.home>
<domain.username>admin</domain.username>
<domain.password>adminadmin</domain.password>
</properties>

You're now ready to run the WAR file on GlassFish. Just run
mvn package glassfish:deploy

to start GlassFish and get your WAR file deployed (Note: If the Maven plugin complains about asadmin not be a Win32 application, just remove the UNIX script so the .bat will be taken).

Finally, time to write some code! Go back to Eclipse and create a package. Our EJB is going to be a (surprise surprise) HelloWorld bean. Create a new class which looks like the following:
package com.ctp.ejb;

import java.util.LinkedHashSet;
import java.util.Set;

import javax.ejb.Singleton;

/**
* The friendly first EJB 3.1 bean.
* @author [you!]
*/
@Singleton
public class HelloWorldBean {

private Set<String> names = new LinkedHashSet<String>();

public String hello(String name) {
names.add(name);
return "Hello " + names;
}
}
In case Eclipse complains about the compiler compliance, just set the Java project facet in the project's properties to something > 1.4. You've now created one of the new Singleton EJBs. The bean is going to remember everybody who said "hello" and give back a friendly greeting. Note that there is no local or remote interface at all! Also using a global state in a bean is something nasty to achieve before EJB 3.1. Run
mvn package glassfish:redeploy

to deploy your bean - noteably in just a simple WAR file! Did you notice how fast this deploys?

Now it's time to access our bean over JAX-RS. Create another class which looks like this:
package com.ctp.ejb;

import javax.naming.InitialContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

/**
* A RESTful bean being hopefully quite busy!
* @author [you!]
*/
@Path("/hello/{username}")
public class HelloWorld {

@GET
@Produces("text/plain")
public String hello(@PathParam("username") String name) throws Exception {
HelloWorldBean hello = (HelloWorldBean)
new InitialContext().lookup("java:global/ejbtestdrive/HelloWorldBean");
return hello.hello(name);
}
}

Simple enough, right? This is taking requests under /hello/... with a username, looking up the bean in JNDI and call it's hello method. Did you notice the format of the JNDI lookup? Yes, this is finally standardized, making your applications much more portable! In case you named your application differently, make sure the names match between global/ and /HelloWorldBean.
Further details on the JAX-RS functionalities can be found here.

As a last step, we need to add the Jersey servlet to our web.xml. Modify it to match this snippet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>embeddable</display-name>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.ctp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

Check that your package name matches in the Servlets init param. Now package and redeploy - and we're ready to go! Just go to http://localhost:8080/ejbtestdrive/hello/[your name] to see you greeted by your new EJB! Experiment with different names and browser sessions and see the global state preserved.

Hope you enjoyed this little test drive. Let us know on how you're using EJB 3.1 and what your experience is with it!

2 comments:

Balarini said...
This comment has been removed by the author.
Balarini said...

Hello Thomas,

First of all, thanks for the tutorial. I had some problems with it, but i managed to finish it succefully.

I was using Eclipse 3.5.2, first downloaded Maven2 plugin, then started a Dinamic Web Project and put Maven dependency on it.

Eclipse creates a sun-web-xml file, that contains a contextroot tag, and THAT value in the contextroot must be the .lookup("java:global/ValueOfContextRoot/HelloWorldBean") in order to work.

Great! Thanks again!