Consuming Web Services from EJB 3 using JAX-WS

November 30th, 2009 by Henri Bezemer

In this post I will show how to consume a Web Service from an EJB 3 stateless session bean, with very little coding involved by leveraging the JAX-WS specification and tooling. I will add this ability to the CRM EJB that I presented in an earlier post: http://www.zienit.nl/blog/2009/11/enterprise-java/web-services-made-easy-with-ejb-jpa-and-jax-ws. I will also explain how to bundle WSDL and XML Schema files with an EAR deployment file using a catalog, how to customize the JAX-WS generated code and how to design a single Maven build process for deployment to multiple environments (development, staging and production).

The CRM example continued

The CRM example from the earlier post is intended to demonstrate how an EJB 3.0 stateless session bean can be a Web Service provider. Another idea behind the CRM example is to create a useful EJB 3 based partner to a WS-BPEL process. To make a WS-BPEL demonstration a bit more interesting, I need a bidirectional partner. The interaction diagram below shows what functionality is added to the EJB. An organization allows new customers to sign up for its services via the Internet. The process of signing up will be orchestrated using WS-BPEL (topic in a future post). One particular step in this ”front-office” process requires the new customer to click on a hyperlink in an email he or she received, in order to confirm that a valid email address was used to sign up. The WS-BPEL process can then continue with the next step.

interaction-diagram-confirm-email

By clicking on the link, the customer invokes a Java servlet called ConfirmEmailServlet. This servlet invokes a method (confirmEmail) on the CRM EJB. The EJB invokes a method (notifyEmailConfirmed) on the so-called SEI. This is a Service Endpoint Interface generated by the JAX-WS tooling, based on a WSDL port type. The SEI will invoke an operation (notifyEmailConfirmed) on a port via the SOAP protocol (as defined in the WSDL). This operation is one-way, so nothing will be returned (unless the invocation itself fails, then an exception will occur). The servlet will return a thank you message contained in an HTML page (not depicted in the diagram).

The front-office callback Web Service

The front-office callback Web Service is specifically designed to be invoked by the CRM EJB. It is defined in a WSDL file and an accompanying XML schema file.  Actually, the files where created in the NetBeans 6.5.1 IDE and deployed to GlassFish ESB 2.1. (these are my tools of choice to design and run BPEL processes). The schema file contains the definition of the message payload:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:tns="http://www.zienit.com/front-office/schema"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
	targetNamespace="http://www.zienit.com/front-office/schema">

	<xsd:complexType name="notifyEmailConfirmed">
		<xsd:sequence>
			<xsd:element name="email" type="xsd:string" />
		</xsd:sequence>
	</xsd:complexType>
	<xsd:element name="notifyEmailConfirmed" type="tns:notifyEmailConfirmed" />
</xsd:schema>

The message payload consists of an element notifyEmailConfirmed, which has a child element email. This element contains character data (a string) which represents an email address. The definition of the web service:

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="FrontOfficeCBService"
	targetNamespace="http://www.zienit.com/front-office-callback/wsdl"
	xmlns="http://schemas.xmlsoap.org/wsdl/"
	xmlns:tns="http://www.zienit.com/front-office-callback/wsdl"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	xmlns:type="http://www.zienit.com/front-office/schema">
	<types>
		<xsd:schema targetNamespace="http://www.zienit.com/front-office/wsdl">
			<xsd:import namespace="http://www.zienit.com/front-office/schema"
				schemaLocation="http://localhost:9080/Front-office-CA-sun-http-binding/Front-office/front-office.xsd" />
		</xsd:schema>
	</types>
	<message name="notifyEmailConfirmedRequest">
		<part name="part1" element="type:notifyEmailConfirmed">
		</part>
	</message>
	<portType name="FrontOfficeCBPT">
		<operation name="notifyEmailConfirmed">
			<input name="input1" message="tns:notifyEmailConfirmedRequest">
			</input>
		</operation>
	</portType>
	<binding name="FrontOfficeCBBinding" type="tns:FrontOfficeCBPT">
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<operation name="notifyEmailConfirmed">
			<soap:operation />
			<input name="input1">
				<soap:body use="literal" />
			</input>
		</operation>
	</binding>
	<service name="FrontOfficeCBService">
		<port name="FrontOfficeCBPort" binding="tns:FrontOfficeCBBinding">
			<soap:address
				location="http://${soap.address.location}/front-office/FrontOfficeCBPort" />
		</port>
	</service>
</definitions>

On line 3, a specific target namespace for the WSDL is set and on line 5 a namespace prefix tns (”target name space”) is declared. This prefix is needed because references to the various reusable parts of the WSDL file must be namespace qualified. On line 10 the schema is imported (because this import occurs inside the xsd:schema element, this is a schema import, and not a wsdl import). A namespace prefix type was declared on line 8 to reference the elements and types imported from the schema. On line 15 the message notifyEmailConfirmedRequest is defined.

It imports the front-office schema, and declares a namespace prefix type to address it. It is made up of a single part (part1) and this part is made up of the element notifyEmailConfirmed which was introduced in the schema. On line 19, the port type FrontOfficeCBPT (CB short for callback, PT short for port) is defined. It has a single operation notifyEmailConfirmed (line 20) which expects the message notifyEmailConfirmedRequest (lin 21, referenced by its qualified name). In lines 25-34, the binding of the port type to the SOAP protocol is specified. On line 35 the service FrontOfficeCBService is defined, containing a single port FrontOfficeCBPort which uses the SOAP binding defined earlier. Line 38 specifies the address for the port. Note that it contains a part ${soap.address.location}. This part will be substituted with an actual server location in the build process.

The Maven master POM

Because this example is a multi-module project (producing four artifacts: one jar, one ejb-jar, one war and one containing ear), I’ve defined a master POM. I’ve put this in a separate Eclipse project called master. The master POM was already introduced in a previous EJB example, and it will be extended only to declare the extra Maven modules (highlighted):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.zienit.simple</groupId>
	<artifactId>master</artifactId>
	<packaging>pom</packaging>
	<name>master</name>
	<version>0.0.1-SNAPSHOT</version>

	<repositories>
		<repository>
			<id>maven-repository.dev.java.net</id>
			<name>Java.net Repository for Maven 1</name>
			<url>http://download.java.net/maven/1/</url>
			<layout>legacy</layout>
		</repository>
		<repository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Repository for Maven 2</name>
			<url>http://download.java.net/maven/2/</url>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Repository for Maven 2</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</pluginRepository>
	</pluginRepositories>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>com.sun.xml.ws</groupId>
				<artifactId>jaxws-rt</artifactId>
				<version>2.1.4</version>
			</dependency>
			<dependency>
				<groupId>com.sun.xml.ws</groupId>
				<artifactId>jaxws-tools</artifactId>
				<version>2.1.4</version>
			</dependency>
			<dependency>
				<groupId>org.apache.geronimo.specs</groupId>
				<artifactId>geronimo-ejb_3.0_spec</artifactId>
				<version>1.0.1</version>
			</dependency>
			<dependency>
				<groupId>org.apache.geronimo.specs</groupId>
				<artifactId>geronimo-jpa_3.0_spec</artifactId>
				<version>1.1.1</version>
			</dependency>
			<dependency>
				<groupId>javax.servlet</groupId>
				<artifactId>servlet-api</artifactId>
				<version>2.5</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<artifactId>maven-compiler-plugin</artifactId>
					<configuration>
						<source>1.5</source>
						<target>1.5</target>
					</configuration>
				</plugin>

				<plugin>
					<groupId>org.codehaus.mojo</groupId>
					<artifactId>jaxws-maven-plugin</artifactId>
					<version>1.12</version>
				</plugin>

			</plugins>
		</pluginManagement>
	</build>

	<modules>
		<module>../simpleclient</module>
		<module>../simpleejb</module>
		<module>../simpleweb</module>
		<module>../simpleear</module>
	</modules>
</project>

Note that the modules are kept in sibling directories. This is done to stay within the comfort zone of the flattened workspace structure of Eclipse. 

Generating the SEI

Fed with a WSDL file, the JAX-WS tooling will create a bunch of Java source files. I found it is best to package these files in a separate jar file, and maintain it in a separate Eclipse project. My project is called simpleclient. Here is the Maven POM that will generate, compile and package the SEI:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<groupId>com.zienit.simple</groupId>
		<artifactId>master</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.zienit.simple</groupId>
	<artifactId>simpleclient</artifactId>
	<packaging>jar</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>simpleclient</name>
	<url>http://maven.apache.org</url>
	<properties>
		<soap.address.location>localhost:9080</soap.address.location>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.sun.xml.ws</groupId>
			<artifactId>jaxws-rt</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.sun.xml.ws</groupId>
			<artifactId>jaxws-tools</artifactId>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>simpleclient</finalName>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<artifactId>maven-clean-plugin</artifactId>
				<configuration>
					<filesets>
						<fileset>
							<directory>src/main/java</directory>
						</fileset>
					</filesets>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>jaxws-maven-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>wsimport</goal>
						</goals>
						<configuration>
							<xdebug>true</xdebug>
						<catalog>${basedir}/src/main/resources/META-INF/jax-ws-catalog.xml</catalog>
							<bindingDirectory>${basedir}</bindingDirectory>
							<bindingFiles>
								<bindingFile>wsdl-bindings.xml</bindingFile>
								<bindingFile>schema-bindings.xml</bindingFile>
							</bindingFiles>
							<sourceDestDir>${project.build.sourceDirectory}</sourceDestDir>
							<wsdlUrls>
								<wsdlUrl>http://localhost:9080/front-office/FrontOfficeCBPort?wsdl</wsdlUrl>
							</wsdlUrls>
							<verbose>true</verbose>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

This POM does roughly three things: it sets up filtering of resource files (lines 33-38) , it instructs the maven-clean-plugin to do some extra cleaning (namely the source directory, lines 40-49) and it instructs the jaxws-maven-plugin to generate SEI classes (lines 50-74). I’ll explain the JAX-WS part in a bit more detail. One thing to take care of is to have a much information during the process as possible, because the code generation process can be somewhat troublesome as I’ve found out, so the flag verbose and xdebug are our friends. The WSDL file that the wsimport tool will consume behind the Maven scenes is specified (lines 67-69) along with the directory where the source code will be generated to (line 66). On line 60 I’m providing the location of an OASIS XML catalog. In this catalog the mappings are provided that allow packaging of WSDL and schema files within the project (at compile time) or within the jar (at runtime). Here is the catalog file:

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
	<system systemId="http://localhost:9080/front-office/FrontOfficeCBPort?wsdl" uri="wsdl/FrontOfficeCBPort.wsdl"/>
	<system systemId="http://localhost:9080/Front-office-CA-sun-http-binding/Front-office/front-office.xsd" uri="schema/front-office.xsd"/>
</catalog>

The catalog must be named jax-ws-catalog.xml and must be placed in the META-INF directory in order to be considered at runtime. Note that in a Maven project META-INF must be a sub directory of src/main/resources in order to be correctly included in the jar. The catalog maps two resources, namely the WSDL file and the schema file that is included by the WSDL, to locations within the project (and within the resulting jar). The provided locations must be relative to the location of the catalog itself. This means that you must put the wsdl in META-INF/wsdl/FrontOfficeCBPort.wsdl and the schema in META-INF/schema/front-office.xsd. In the case that you want to get these files from a live web service url (with ?wsdl appended), you can open them in your browser and save them to your project using the save as command. Here are some benefits of having local copies of the WSDL and schema files:

  • The client process will be slightly faster because it does not have to load the WSDL and schema files of the network.
  • Your build process is not dependent on the availability of the WSDL and schema files. Web services can be down as any other Web resource.
  • You can use Maven filtering to customize the WSDL (for instance using the ${soap.address.location} approach in the post).
  • You can put the WSDL and schema files under version control (in CVS, Subversion, etc). This gives you a point of reference when an external Web service definition has been changed without prior notice and presents problems.

After this explanation of the catalog, I still need to explain a final part of the POM. That is the JAX-WS and JAXB binding files (lines 61-65 in POM.xml). As should be clear by now, the JAX-WS tooling generates Java code based on WSDL and schema files. This process may run into problems, like name collisions, or you simply may not like the resulting code. To customize the generation process, JAX-WS and JAXB support binding files. I’m only doing a very modest customization here: I’m overriding the default packages names. Here are the binding files:

<?xml version="1.0" encoding="UTF-8"?>
<jaxws:bindings
	wsdlLocation="src/main/resources/META-INF/wsdl/FrontOfficeCBPort.wsdl"
	xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
	<jaxws:package name="com.zienit.frontoffice.callback.service"/>
</jaxws:bindings>

And

<?xml version="1.0" encoding="UTF-8"?>
<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
	<jxb:bindings schemaLocation="http://localhost:9080/Front-office-CA-sun-http-binding/Front-office/front-office.xsd" node="/xsd:schema">
		<jxb:schemaBindings>
			<jxb:package name="com.zienit.frontoffice.types" />
		</jxb:schemaBindings>
	</jxb:bindings>
</jxb:bindings>

I’ve put these files in the project root, at the same level as the Maven POM, because these files are not used at runtime, so have no business being included in the jar file. Let me stress that these files are optional unless you run into problems with the JAX-WS tooling.

Extending the CRM EJB

So far, we haven’t seen a bit of Java code. Well, it comes with this project called simpleejb. The CRM EJB was introduced in a previous EJB example. One method is added to the bean’s interface (highlighted):

package com.zienit.simpleejb;

import java.util.Set;

import javax.ejb.Remote;

@Remote
public interface CRM {
	Customer createCustomer(String name);
	Customer getCustomer(int id);
	Email addEmail(int id, String address, EmailType type);
	Email getEmail(EmailPK pk);
	Email removeEmail(EmailPK pk);
	Set<Email> listEmail(int id);
	Address addAddress(int id, String street, String postalCode, String city, AddressType type);
	Address getAddress(AddressPK pk);
	Address removeAddress(AddressPK pk);
	Set<Address> listAddresses(int id);
	void confirmEmail(String email);
}

This new method is the method that will be invoked from the servlet. Here is the bean’s implementation class, also with the additional code highlighted:

package com.zienit.simpleejb;

import java.util.Set;

import javax.ejb.Stateless;
import javax.jws.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.xml.ws.WebServiceRef;

import com.zienit.frontoffice.callback.service.FrontOfficeCBPT;

@Stateless
@WebService(name = "CRMPortType", serviceName = "CRMService", portName = "CRMSoapPort", targetNamespace = "http://simpleejb.zienit.com")
public class CRMBean implements CRM {

	@PersistenceContext
	private EntityManager em;
	@WebServiceRef(com.zienit.frontoffice.callback.service.FrontOfficeCBService.class)
	private FrontOfficeCBPT frontOffice;

	@WebResult(name = "customer")
	public Customer createCustomer(@WebParam(name = "name") String name) {
		Customer c = new Customer();
		c.setName(name);
		em.persist(c);
		return c;
	}

	@WebResult(name = "customer")
	public Customer getCustomer(@WebParam(name = "id") int id) {
		return em.find(Customer.class, id);
	}

	@WebResult(name = "email")
	public Email addEmail(@WebParam(name = "id") int id,
			@WebParam(name = "address") String address,
			@WebParam(name = "type") EmailType type) {
		Customer c = getCustomer(id);
		if (c == null) {
			return null;
		}
		Email a = new Email();
		a.setAddress(address);
		a.setCustomer(c);
		a.setType(type);
		c.getEmailAddresses().add(a);
		em.persist(a);
		return a;
	}

	@WebResult(name = "email")
	public Email getEmail(@WebParam(name = "pk") EmailPK pk) {
		return em.find(Email.class, pk);
	}

	@WebResult(name = "email")
	public Email removeEmail(@WebParam(name = "pk") EmailPK pk) {
		Email e = getEmail(pk);
		if (e != null) {
			e.getCustomer().getEmailAddresses().remove(e);
			em.remove(e);
		}
		return e;
	}

	@WebResult(name = "email")
	public Set<Email> listEmail(@WebParam(name = "id") int id) {
		return getCustomer(id).getEmailAddresses();
	}

	@WebResult(name = "address")
	public Address addAddress(@WebParam(name = "id") int id,
			@WebParam(name = "street") String street,
			@WebParam(name = "postalCode") String postalCode,
			@WebParam(name = "city") String city,
			@WebParam(name = "type") AddressType type) {
		Customer c = getCustomer(id);
		if (c == null) {
			return null;
		}
		Address a = new Address();
		a.setStreet(street);
		a.setPostalCode(postalCode);
		a.setCity(city);
		a.setCustomer(c);
		a.setType(type);
		c.getAddresses().add(a);
		em.persist(a);
		return a;
	}

	@WebResult(name = "address")
	public Address getAddress(@WebParam(name = "pk") AddressPK pk) {
		return em.find(Address.class, pk);
	}

	@WebResult(name = "address")
	public Address removeAddress(@WebParam(name = "pk") AddressPK pk) {
		Address a = getAddress(pk);
		if (a != null) {
			a.getCustomer().getAddresses().remove(a);
			em.remove(a);
		}
		return a;
	}

	@WebResult(name = "address")
	public Set<Address> listAddresses(@WebParam(name = "id") int id) {
		return getCustomer(id).getAddresses();
	}

	public void confirmEmail(String email) {
		frontOffice.notifyEmailConfirmed(email);
	}
}

As you can see, there is really very little coding involved. A reference to the SEI will be injected at runtime (line 19), and with this reference a Web Service operation is invoked (line 118). Below is the POM for the simpleejb project. The only addition is the dependency on the simpleclient artifact (highlighted). Note that an ejb-jar cannot contain a client jar. Any client jars should be contained by a containing ear. The maven-ejb-plugin is smart enough not to package simpleclient.jar with the ejb-jar.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<artifactId>master</artifactId>
		<groupId>com.zienit.simple</groupId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.zienit.simple</groupId>
	<artifactId>simpleejb</artifactId>
	<packaging>ejb</packaging>
	<name>simpleejb</name>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<dependency>
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-ejb_3.0_spec</artifactId>
			<version>1.0.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-jpa_3.0_spec</artifactId>
			<version>1.1.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.sun.xml.ws</groupId>
			<artifactId>jaxws-rt</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.sun.xml.ws</groupId>
			<artifactId>jaxws-tools</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.zienit.simple</groupId>
			<artifactId>simpleclient</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>simpleejb</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-ejb-plugin</artifactId>
				<configuration>
					<ejbVersion>3.0</ejbVersion>
					<generateClient>true</generateClient>
					<clientIncludes>
						<clientInclude>**/*.class</clientInclude>
					</clientIncludes>
					<clientExcluded>
						<clientExclude>**/*Bean.class</clientExclude>
					</clientExcluded>
				</configuration>
			</plugin>

		</plugins>
	</build>

</project>

The Servlet

The servlet is developed in its own project called simpleweb. It provides an easy way to invoke the confirmEmail method on the CRM EJB. Let’s start with the Java code:

package com.zienit.simpleweb;

import java.io.IOException;
import java.io.PrintWriter;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.http.*;

import com.zienit.simpleejb.CRM;

public class ConfirmEmailServlet extends HttpServlet {

	@EJB private CRM crm;

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		String email = req.getParameter("email");
		crm.confirmEmail(email);
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.write("<html>\n<head>\n<title>Email confirmation</title>\n</head>\n<body>");
        out.write("<p>Thanks for confirming your email address: " + email + "!</p>");
        out.close();
  }
}

A reference to the CRM EJB is injected by the container at runtime. EJB 3 Annotations do make life easy. Here is the web.xml (which is expected by Maven in directory src/main/webapp/WEB-INF):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- use version 2.5 to enable resource injection -->
	<display-name>Simple Web Application</display-name>

	<servlet>
		<servlet-name>confirm-email</servlet-name>
		<servlet-class>com.zienit.simpleweb.ConfirmEmailServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>confirm-email</servlet-name>
		<url-pattern>/confirm-email</url-pattern>
	</servlet-mapping>

</web-app>

Notice that the version attribute plays a role that is more important than you might expect. To tell the container to inject resources, the version must be at least 2.5. Here is the simpleweb project’s POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<groupId>com.zienit.simple</groupId>
		<artifactId>master</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.zienit.simple</groupId>
	<artifactId>simpleweb</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>simpleweb</name>

	<dependencies>

		<dependency>
			<groupId>com.zienit.simple</groupId>
			<artifactId>simpleejb</artifactId>
			<version>0.0.1-SNAPSHOT</version>
			<type>ejb-client</type>
			<!-- included in ear -->
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-ejb_3.0_spec</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<!-- needed because some of the EJB's value objects have JPA annotations -->
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-jpa_3.0_spec</artifactId>
			<scope>provided</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>simpleweb</finalName>
	</build>
</project>

The EAR file

The EAR file is build in a project called simpleear. It contains only this POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<artifactId>master</artifactId>
		<groupId>com.zienit.simple</groupId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.zienit.simple</groupId>
	<artifactId>simpleear</artifactId>
	<packaging>ear</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>simpleear</name>

	<dependencies>
		<dependency>
			<groupId>com.zienit.simple</groupId>
			<artifactId>simpleejb</artifactId>
			<version>0.0.1-SNAPSHOT</version>
			<type>ejb</type>
		</dependency>
		<dependency>
			<groupId>com.zienit.simple</groupId>
			<artifactId>simpleweb</artifactId>
			<version>0.0.1-SNAPSHOT</version>
			<type>war</type>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-ear-plugin</artifactId>
				<configuration>
					<displayName>simpleear</displayName>
					<description>Simple Enterprise Application</description>
					<version>5</version>
					<modules>
						<ejbModule>
							<groupId>com.zienit.simple</groupId>
							<artifactId>simpleejb</artifactId>
						</ejbModule>
						<webModule>
							<groupId>com.zienit.simple</groupId>
							<artifactId>simpleweb</artifactId>
						</webModule>
					</modules>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

This POM constructs the ear file, that contains all the other artifacts. Notice the dependencies between the artifacts: simpleweb.war requires simpleejb.jar and simpleejb.jar in turn requires simpleclient.jar. The class loading rules in the ear file ensures that jars are visible to ejb-jars, and that ejb-jars are visible to wars.

All of the projects modules can be build by running Maven install on the master POM. If I run Maven inside Eclipse (with the m2eclipse plug-in), the POM for simpleear causes a strange build error. This is a bug in the plug-in. A workaround for this is to close the simpleclient, simpleear and simpleweb projects inside Eclipse. Alternatively, if Maven is run outside of Eclipse (i.e. in a command prompt), it also completes without problems.

Because we don’t have a working web service, it is not possible to test the code. This will have to wait for my post about WS-BPEL. It is however quite simple to change this example to invoke another, live web service.

Conclusions

Consuming web services from EJB 3 beans is very straightforward (once you know the intricacies). Thanks to the JAX-WS tooling, annotations and resource injection, very little Java code is needed.

One Comment

1

[...] Dit blogartikel was vermeld op Twitter door Glen Mazza, Henri Bezemer. Henri Bezemer heeft gezegd: Posted a new article on my blog: Consuming Web Services from EJB 3 using JAX-WS. http://bit.ly/6Qv65X [...]