Abstract

About two years ago, I published an article about Exposing the Functionality implemented in Stateless Session Beans (EJB 2.1) using Web Services. J2EE 1.4 times are over and the new version of the Java Enterprise framework, called Java Enterprise Edition 5 (JavaEE 5, or simply JEE) has emerged. In this article the same business scenario is repeated in the new framework.

Requirements

Before we dive into code examples, some software is required. The good news about the software is, that it also evolved over time. Here is what we use:

  • Sun’s Java 6 SDK
  • JBoss AS 5.1.0 GA for JDK6
  • Eclipse Galileo 3.5.1 for JEE development


Some words about the used software. It is important to get the JDK6-compiled version of the JBoss Application Server. There are some issues with running the version compiled with Java 5 on JDK6, since JDK6 has its own JAX-WS implementation which causes problems with JBoss (affected should be all versions of JBoss < 5.2. Currently it seems like version 5.2 will be compiled with JDK6 by default.) If you launch JBoss from Eclipse, make sure to add the -Djava.endorsed.dirs=PATH_TO_JBOSS/jboss-5.1.0.GA/lib/endorsed as a VM argument, if launched from the command line, please add it as an option inside of the run.conf.bat file:

set "JAVA_OPTS=%JAVA_OPTS% -Djava.endorsed.dirs=$JBOSS_HOME/lib/endorsed"

Otherwise you will get the following exception:

java.lang.UnsupportedOperationException: setProperty must be overridden by all subclasses of SOAPMessage

After installation (unpacking) of Eclipse, make sure to configure the JDK (under Preferences > Java > Installed JREs) and the JBoss instance (Preferences > Server > Runtime Environment).

Use Case

In order to demonstrate the technology by example, imagine the following use case. The system under construction is capable of providing measurements of some sensors. The sensors are identified by numbers and can be queried by the user by providing the timespan of measurements. As a result, the systems delivers the set of measurements for the given timespan with one measurement per minute but at most sixty measurements. Every measurement contains the id of sensor from which it has been recorded, the timestamp, the value as a byte array, the measurement unit and finally a flag whether the measurement exceeds the limits or specified alarm values.

Implementation

We start the implementation with the creation of a JEE Session Bean and then expose the functionality using a second Facade bean. In doing so we follow the EJB 3 specification.

Eclipse for Java EE development

The plugins in Eclipse for Java EE provide massive support and foster the development by deep integration withe JEE servers. Instead of a simple Java Project, start with an Enterprise Application Project. Then, create a new EJB Project and add it to the created Enterprise Application Project. In order to deploy the resulting application to the server, right-click on the enterprise project and select Run As > Run on Server .

To check the deployment on the application server, use the JBoss Consoles. These can be accessed via http://localhost:8080/ for the locally installed JBoss. An additional Web Service Console is accessible via http://localhost:8080/jbossws/.

Implementing the Functionality

In order to implement the functionality in a stateless session bean, we start with the creation of the business interface.

/**
 * Defines the sensor manager functionality
 */
public interface SensorManager
{
	/**
	 * Retrieve sensor data
	 * @param sensorId id of the sensor
	 * @param timespan the measurement period
	 * @return a list of measurements in the given period
	 * @throws SensorManagerException on any kinds of errors
	 */
	public List<Measurement> getSensorData(SensorID sensorId, Timespan timespan) throws SensorManagerException;
}

The POJOs Measurement, SensorID and Timespan are used to show the usage of custom user types:

public class Measurement
{
	private SensorID sensorID;
	private Date timestamp;
	private boolean critical;
	private byte[] value;
	private String unit;
...
// getters and setters
}
....
public class SensorID
{
	private long sensorId;
...
// getters and setters
}

public class Timespan
{
	private Date start;
	private Date end;
...
// getters and setters
}

After the creation of the business interface, let us create the actual Session Bean. The class is annotated with the @javax.ejb.Stateless annotation in order to indicate that the bean is a Stateless Session Bean. Since it implements the business interface (and only it), the container will be able to deduce that the given interface is the Local Business interface: this is just one of many examples of the Configurations by Exception in EJB 3. In order to be able to reference the bean from other beans, we annotate the binding of the local interface to a specific JNDI name using the vendor-specific annotation org.jboss.ejb3.LocalBinding.

@Stateless
@LocalBinding(jndiBinding="de.techjava.sensor/SensorManager")
public class SensorManagerBean implements SensorManager
{
	/** Logging facility */
	protected static Logger LOG = Logger.getLogger(SensorManagerBean.class);
	/** Max number of returned values */
	private static final long MAX_DURATION = 60;

	/**
	 * Implementation of the business method
	 */
	public List<Measurement> getSensorData(SensorID sensorId, Timespan timespan) throws SensorManagerException
	{
		LOG.debug("Entering getSensorData()");
		if (sensorId == null || timespan == null || timespan.getStart() == null || timespan.getStart() == null)
		{
			throw new SensorManagerException("Missing a mandatory parameter, that was null (not set)");
		} else if (!timespan.getStart().before(timespan.getEnd()))
		{
			throw new SensorManagerException("The timespan is defined by the start that should be before end");
		}

		List<Measurement> measurements = new LinkedList<Measurement>();
		for (long i = 0; i < getNumberOfElements(timespan); i++)
		{
			Date date = new Date();
			date.setTime(timespan.getStart().getTime() + i * 1000 * 60);
			if (date.after(timespan.getEnd()))
				break;
			Measurement measurement = createMeasurement(sensorId, date, i);
			measurements.add(measurement);
		}

		LOG.debug("Leaving getSensorData(). Returning " + measurements.size() + " values.");
		return measurements;
	}

	/**
	 * Retrieves the number of measurements in timespan
	 */
	private long getNumberOfElements(Timespan timespan) { ... }

	/**
	 * Creates a measurement instance, a dummy implementation
	 */
	private Measurement createMeasurement(SensorID sensorId, Date timestamp, long number) { ... }
}

Web Service Definition

The quality of the web service interface is an important design consideration. As described in the previous post, in many cases it is a good idea to create it by hand instead of generation by a tool. In this particular case, we create another annotated Stateless Session Bean, which is used as a Facade and contains all Web Service-specific settings. From this Bean we generate the WSDL and then tune it in order to show the capability of the customization. In doing so we try to match the WSDL from the previous example as close as possible.

Firstly, we create the business interface and the bean class, then annotate it and finally add the code delegating the Web Service request to the SensorManager.

public interface MeasurementProviderFacade
{
	public List<Measurement> getSensorData(SensorID id, Timespan timespan) throws SensorDataOperationFault;
}

...

@Stateless
public class MeasurementProviderFacadeBean implements MeasurementProviderFacade
{
	public List<Measurement> getSensorData(SensorID id, Timespan timespan) throws SensorDataOperationFault
	{
	...
	// implementation code
	}
}

In order to have a Session Bean exposed as a Web Service, the JAX-WS specification defines a set of annotations. The most important two are @javax.jws.WebMethod and @javax.jws.WebService. The @WebService annotation is used to mark the class to be exposed e.G.

@Stateless
@WebService(
	serviceName = "MeasurementProviderService",
	name = "MeasurementProviderPortType",
	portName = "MeasurementProviderPort",
	targetNamespace = "http://www.techjava.de/2010/ws4jee/measurement/"
)
public class MeasurementProviderFacadeBean implements MeasurementProviderFacade
{
	@WebMethod(
		operationName="GetSensorDataOperation",
		action="http://www.techjava.de/2010/ws4jee/measurement/GetSensorDataOperation"
	)
	public List<Measurement> getSensorData(SensorID id, Timespan timespan) throws SensorDataOperationFault { ... }
}

All the attributes of the annotation are optional and their values will be derived from the classname and the name of the business interface. The default target namespace for the service definition is derived from the package name, the annotated class is located in. By default, all public methods of the annotated class will be exposed as WSDL operations, with operation’s name derived from the method name. Using the @WebMethod annotation, the name of the WSDL operation and the SOAP action can be changed.

The careful reader might have observed that the getSensorData-method is throwing an exception. In order to map the exception to a SOAP-Fault, the javax.xml.ws.WebFault class-level annotation can be used. The faultBean attribute is used to provide the full-qulified class name of the exception.

...
@WebFault(
	name="GetSensorDataFault",
	targetNamespace="http://www.techjava.de/2010/ws4jee/measurement/",
	faultBean="de.techjava.sensor.bean.SensorDataOperationFault")
public class MeasurementProviderFacadeBean implements MeasurementProviderFacade { ... }

In order to increase the quality of the WSDL further, the method parameters and return type can be annotated with javax.jws.WebParam and javax.jws.WebResult respectively, so the method signature becomes a little unreadable. In order not to copy the long string containing the taget namespace a String constant can be used.

...
	public final static String TYPES = "http://www.techjava.de/2010/ws4jee/measurement/types/";
...
	@WebResult(name="measurement", targetNamespace=TYPES)
	public List<Measurement> getSensorData(
		@WebParam(name="sensor-id", targetNamespace=TYPES) SensorID id,
		@WebParam(name="timespan", targetNamespace=TYPES) Timespan timespan) throws SensorDataOperationFault	{ ... }

Finally, in order to force the marshaller / unmarshaller to map the custom data types (Measurement, SensorID and Timespan) to belong to the same namespace, the JAX-B class-level annotation javax.xml.bind.annotation.XmlType has to be used. By default, the datatypes will be mapped to XML Schema types according to their package name and class name.

@XmlType(namespace = "http://www.techjava.de/2010/ws4jee/measurement/types/", name = "TimeSpan", propOrder = { "start", "end" })
public class Timespan
{
	private Date start;
	private Date end;

If deployed to the JBoss, the resulting WSDL looks as following:

<definitions name='MeasurementProviderService' targetNamespace='http://www.techjava.de/2010/ws4jee/measurement/' xmlns='http://schemas.xmlsoap.org/wsdl/' xmlns:ns1='http://www.techjava.de/2010/ws4jee/measurement/types/' xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:tns='http://www.techjava.de/2010/ws4jee/measurement/' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
 <types>
  <xs:schema targetNamespace='http://www.techjava.de/2010/ws4jee/measurement/types/' version='1.0' xmlns:tns='http://www.techjava.de/2010/ws4jee/measurement/types/' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
   <xs:element name='measurement' type='tns:Measurement'/>
   <xs:element name='sensor-id' type='tns:sensor-id'/>
   <xs:element name='timespan' type='tns:TimeSpan'/>
   <xs:complexType name='sensor-id'>
    <xs:sequence>
     <xs:element name='sensorId' type='xs:long'/>
    </xs:sequence>
   </xs:complexType>
   <xs:complexType name='TimeSpan'>
    <xs:sequence>
     <xs:element minOccurs='0' name='start' type='xs:dateTime'/>
     <xs:element minOccurs='0' name='end' type='xs:dateTime'/>
    </xs:sequence>
   </xs:complexType>
   <xs:complexType name='Measurement'>
    <xs:sequence>
     <xs:element name='critical' type='xs:boolean'/>
     <xs:element minOccurs='0' name='sensorID' type='tns:sensor-id'/>
     <xs:element minOccurs='0' name='timestamp' type='xs:dateTime'/>
     <xs:element minOccurs='0' name='unit' type='xs:string'/>
     <xs:element minOccurs='0' name='value' type='xs:base64Binary'/>
    </xs:sequence>
   </xs:complexType>
  </xs:schema>
  <xs:schema targetNamespace='http://www.techjava.de/2010/ws4jee/measurement/' version='1.0' xmlns:ns1='http://www.techjava.de/2010/ws4jee/measurement/types/' xmlns:tns='http://www.techjava.de/2010/ws4jee/measurement/' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
   <xs:import namespace='http://www.techjava.de/2010/ws4jee/measurement/types/'/>
   <xs:element name='GetSensorDataOperation' type='tns:GetSensorDataOperation'/>
   <xs:element name='GetSensorDataOperationResponse' type='tns:GetSensorDataOperationResponse'/>
   <xs:element name='SensorDataOperationFault' type='tns:SensorDataOperationFault'/>
   <xs:complexType name='GetSensorDataOperation'>
    <xs:sequence>
     <xs:element minOccurs='0' ref='ns1:sensor-id'/>
     <xs:element minOccurs='0' ref='ns1:timespan'/>
    </xs:sequence>
   </xs:complexType>
   <xs:complexType name='GetSensorDataOperationResponse'>
    <xs:sequence>
     <xs:element maxOccurs='unbounded' minOccurs='0' ref='ns1:measurement'/>
    </xs:sequence>
   </xs:complexType>
   <xs:complexType name='SensorDataOperationFault'>
    <xs:sequence>
     <xs:element minOccurs='0' name='message' type='xs:string'/>
    </xs:sequence>
   </xs:complexType>
  </xs:schema>
 </types>
 <message name='MeasurementProviderPortType_GetSensorDataOperation'>
  <part element='tns:GetSensorDataOperation' name='GetSensorDataOperation'></part>
 </message>
 <message name='SensorDataOperationFault'>
  <part element='tns:SensorDataOperationFault' name='SensorDataOperationFault'></part>
 </message>
 <message name='MeasurementProviderPortType_GetSensorDataOperationResponse'>
  <part element='tns:GetSensorDataOperationResponse' name='GetSensorDataOperationResponse'></part>
 </message>
 <portType name='MeasurementProviderPortType'>
  <operation name='GetSensorDataOperation' parameterOrder='GetSensorDataOperation'>
   <input message='tns:MeasurementProviderPortType_GetSensorDataOperation' />
   <output message='tns:MeasurementProviderPortType_GetSensorDataOperationResponse' />
   <fault message='tns:SensorDataOperationFault' name='SensorDataOperationFault' />
  </operation>
 </portType>
 <binding name='MeasurementProviderPortTypeBinding' type='tns:MeasurementProviderPortType'>
  <soap:binding style='document' transport='http://schemas.xmlsoap.org/soap/http'/>
  <operation name='GetSensorDataOperation'>
   <soap:operation soapAction='http://www.techjava.de/2010/ws4jee/measurement//GetSensorDataOperation'/>
   <input><soap:body use='literal'/></input>
   <output><soap:body use='literal'/></output>
   <fault name='SensorDataOperationFault'><soap:fault name='SensorDataOperationFault' use='literal'/></fault>
  </operation>
 </binding>
 <service name='MeasurementProviderService'>
  <port binding='tns:MeasurementProviderPortTypeBinding' name='MeasurementProviderPort'>
   <soap:address location='http://127.0.0.1:8080/de.techjava.ws4ee-de.techjava.ws4ee.sensor/MeasurementProviderFacadeBean'/>
  </port>
 </service>
</definitions>

Please note, that the domain datatypes are mapped to the different namespace as the target namespace of the Web Service.

After some tuning the WSDL, like changing the cardinality of elements (minOccurs = 1, maxOccurs = 1) or adding comments we can let the container use the changed WSDL file instead of generating a new one. In order to do this, let us package the WSDL together with the source code. A good place for it is for example /META-INF/wsdl/, so we can provide a reference to it in an wsdlLocationattribute of the @WebService annotation:

@WebService(
...
        wsdlLocation="META-INF/wsdl/MeasurementProviderService.wsdl"
)

Putting all together

After the creation of the Facade Bean and its exposure as a Web Service, we need to delegate the call to the SensorManagerBean, providing the actual implementation. In order to do so, add a private member of type SensorManager inside of the facade bean and annotate it with the javax.ejb.EJB annotation. The container will intialize the member using Dependency Injection and provide a valid reference which can be simply used inside of the method.

...
public class MeasurementProviderFacadeBean implements MeasurementProviderFacade
{
	@EJB(mappedName="de.techjava.sensor/SensorManager")
	private SensorManager sensorManager;

	/**
	 * Simple delegate to the business method of the sensor manager
	 */
	@WebMethod(
			operationName="GetSensorDataOperation",
			action=TNS + "/GetSensorDataOperation"
	)
	public @WebResult(name="measurement", targetNamespace=TYPES) List<Measurement> getSensorData(
			@WebParam(name="sensor-id", targetNamespace=TYPES) SensorID id,
			@WebParam(name="timespan", targetNamespace=TYPES) Timespan timespan) throws SensorDataOperationFault
	{
		try
		{
			return sensorManager.getSensorData(id, timespan);
		} catch (SensorManagerException e)
		{
			throw new SensorDataOperationFault("Error accessing the Sensor Manager: " + e.getMessage() + "");
		}
	}
}

Here is the call in Web Service Explorer:
image WS Explorer

Outline

In this article we showed how to expose the functionality implemented in an EJB 3 Session Bean using Web Service Technology. In fact, the creation of a FacadeBean was not required since one could annotate the SensorManager-Bean directly, but we separated the implementation Bean from the exposing Bean to make it easier to understand. Even if we didn’t match the WSDL from the J2EE 1.4 example exactly, we came very close. Merely some message-related datatypes have different names. In this example we used the Document/Literal/Wrapped style of Web Services in order to gain maximum interoperability. Even though the creation of the Web Service is a simple task using the annotations. The number of tools and dependencies has dramataically reduced since J2EE 1.4 and the development became less error-prone. Together with other EJB3 improvements it makes the JEE framework suitable for development in the context of enterprise distributed systems.

References and Resources

The example source code is available for download ( ws4jee.zip). Just import the projects from the archive into Eclipse Worskspace. Make sure to set up the JRE and JBoss Server instance for the projecst to work.

  • [2008,techreport] bibtex Go to document
    E. Jendrock, J. Ball, D. Carson, I. Evans, S. Fordin, and K. Haase, "The Java EE 5 Tutorial," 2008.
    @techreport{JAVAEE,
      author = {Eric Jendrock and Jennifer Ball and Debbie Carson and Ian Evans and Scott Fordin and Kim Haase},
      title = {The Java EE 5 Tutorial},
      url = {http://java.sun.com/javaee/5/docs/tutorial/doc/},
      month = {Oct},
      year = {2008}
    }
  • [2007,book] bibtex Go to document
    O. Ihns, D. Harbeck, S. M. Heldt, H. Koschek, R. Schl�r, J. Ehm, and C. Sahling, EJB 3 professionell. Grundlagen- und Expertenwissen zu Enterprise JavaBeans 3 fr Einsteiger, Umsteiger und Fortgeschrittene , publisher DPunkt Verlag, , 2007.
    @Book{IHNS_2003,
      author = {Oliver Ihns and Dierk Harbeck and Stefan M. Heldt and Holger Koschek and Roman Schlmmer and Jo Ehm and Carsten Sahling},
      title = {EJB 3 professionell. Grundlagen- und Expertenwissen zu Enterprise JavaBeans 3 fr Einsteiger, Umsteiger und Fortgeschrittene },
      publisher {DPunkt Verlag},
      year = {2007},
      month = {Jul},
      url = {Oliver Ihns, Dierk Harbeck, Stefan M. Heldt, Holger Koschek, Roman Schlmmer, Jo Ehm, Carsten Sahling}
    }