Web services made easy with EJB, JPA and JAX-WS

November 4th, 2009 by Henri Bezemer

In this post I will share some code and findings that came out of an experiment with EJB 3.0, JPA (included in EJB 3.0) and JAX-WS (Java API for XML-Based Web Services) 2.0. My goal was to create a useful layer of web services on top of a database with as little Java code as possible. I wasn’t sure if the three technologies could be mixed together in a thin layer without running into serious trouble, so I decided to create an example application and test it on three popular open source Java Application Servers: Geronimo (2.1.4), Sun GlassFish Enterprise Server (2.1) and JBoss (5.1.0 GA). I’m running these Application Servers on Sun JDK 6.

The example application

I wanted to create a couple of simple web services that could still be useful for other means, like some experiments that I want to do with WS-BPEL later on. I decided on a CRM oriented example, because CRM is quite often involved in business processes (at least the business processes that I’m familiar with).

 ERM-customer-address-email

The diagram depicts the entity Customer, who has zero or more Addresses, and zero or more Email(addresses). If a customer has more than one address, each address must be of a different type (like home address or mail address). Multiple email addresses per customer must also be of different type (home, work). Besides that, each email address must be unique. Having all these constraints in place (even if they are somewhat arguable from a functional perspective) allows to easily test how the web services will deal with spontaneous database rollbacks.

 

 

 

In this experiment I’m using MySQL 5.1 (together with JDBC driver MySQL Connector/J 5.1). Here is a DDL script that creates the tables and their constraints in a MySQL database:

drop table if exists customer;
create table customer
(
	id int primary key auto_increment,
	ext_id int null unique,
	name varchar(100) not null
);
drop table if exists email;
create table email
(
	customer_id int,
	type int,
	address varchar(100) not null unique,
	primary key (customer_id, type),
	foreign key (customer_id) references customer(id)
);
drop table if exists address;
create table address
(
	customer_id int,
	type int,
	street varchar(100) not null,
	postal_code varchar(100) not null,
	city varchar(100) not null,
	primary key (customer_id, type),
	foreign key (customer_id) references customer(id)
);

The customer table’s primary key values are automatically by MySQL (not all but most relational databases support this feature in some way). The foreign keys from Email and Address to Customer overlap partially with the compound primary keys. This will turn out to be a challenge to JPA!

Project structure and build process

I’m using the Eclipse IDE for Java EE Developers (Galileo), but I don’t use any fancy Eclipse features other than the basic Java productivity features. The primary development tool in this experiment is actually Maven (2.2.1), which will do all the compiling, packaging, and which will also download all necessary Java libraries. Get Maven up and running on your development machine. If you like to work with Eclipse (I do!), Eclipse works really well together with Maven via the m2eclipse Eclipse plug-in (a separate download). If you have this plug-in installed, Eclipse will inspect your Maven POMs on the fly and will provide all necessary Java libraries on the Eclipse Java build path automatically. Eclipse can now make our lives easier with code completion!

To set up an empty Maven enabled project in Eclipse, activate the New Project dialog (menu path new -> project…). Select Maven, Maven Project and click Next. Then check the option to create a simple project (skip archetype selection). Enter the Maven coordinates (shown later) and press Finish. Eclipse will create a new project, with the standard Maven directory layout:

src/main/java
src/main/resources
src/test/java
src/test/resources
target

The Java packages go in the src/main/java directory, while the src/main/resources directory will hold any resources (like deployment descriptors). The src/test/* directory is not used (well, not in this example, in the real world you’ll keep unit test Java code and resources in here). The target directory is not created initially but will pop up after a run of  the build process. It will contain the deployable jar artifact.

I like to set up Maven projects under a “master” POM. All other (subordinate) POMs will inherit things like the specific versions of plug-ins and libraries from the master POM. Also, the master POM can kick off the build process of the subordinate POM’s by listing the subordinate POM’s as modules. Create an Eclipse project following the process outlined above and call it master (the name is not important, because the master POM will be referred to via Maven coordinates, and not via the file system path). Use these coordinates:

  • groupId=com.zienit.simple,
  • artifactId=master,
  • packaging=pom,
  • version=0.0.1-SNAPSHOT.

The only thing that lives in this Eclipse project is the master POM (pom.xml, stored in the project’s root). Open it and edit it to look like this:

<?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>

		</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>../simpleejb</module>
	</modules>
</project>

This POM defines a specific repository from which Maven can download a number of useful artifacts, specifically jaxws-rt and jaxws-tools (which are needed for JAX-WS development). All other artifacts are automatically downloaded from the default central repository. The master POM also defines which specific version of plug-ins and libraries are going to be used. Creating a central place to manage versions is a good engineering practice. Finally, the POM instructs the compiler to compile for Java 1.5 (Remember to instruct Eclipse’s internal compiler to do this as well, via the project properties, or you’ll end up with lots of red wavy lines under annotations). If you run Maven install on this POM (In Eclipse, you can do this via menu path Run -> Run As and select the option Maven install), then this POM and all subordinate POMs (only one in this case) are build and installed (in the local Maven repository).

 
The next Eclipse project to create is called simpleejb. This is the projects that contains the actual code. Use the following Maven coordinates for this POM:

  • groupId=com.zienit.simple
  • artifactId=simpleejb
  • packaging=ejb
  • version=0.0.1-SNAPSHOT

For the parent POM use:

  • groupId=com.zienit.simple
  • artifactId=master
  • version=0.0.1-SNAPSHOT
<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>
	</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>

This POM defines a number of dependencies on Java libraries: the JPA, EJB and JAX-WS libraries. I’m using the Apache versions of the Java API’s because these can be downloaded from the central repository. The Sun versions cannot and have to be installed into the local Maven repository by hand, because of copyright restrictions. Since the API’s are only used at compile time, the source of the libraries is of little concern. Note that I broke my own good engineering practice by using specific version numbers on the JPA and EJB API’s (for demonstration purposes of course ;-) . Finally, the Maven EJB Plug-in is instructed to generate EJB 3.0 components, including a separate client-jar that can be used by client code (for instance a Servlet) to access the EJB’s. All classes that are used in the EJB interface are included, the bean implementation classes (with naming convention *Bean) are excluded from the client-jar.

Note that this relatively short, declarative POM is all that is needed to generate an EJB jar file and an accompanying client jar file!

Coding the JPA perspective

Time to look at some Java code, specifically the code that will wrap the customer table in the database. In Eclipse, create a package com.zienit.simpleejb under source folder /src/main/java. Then create class Customer:

package com.zienit.simpleejb;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.*;

@Entity
@Table(name = "customer")
public class Customer implements Serializable {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int id;
	@Column(name="ext_id")
	private Integer extId;
	@Column(length=100)
	private String name;
	@OneToMany(mappedBy="customer",fetch=FetchType.EAGER,cascade=CascadeType.REMOVE)
	private Set<Email> emailAddresses;
	@OneToMany(mappedBy="customer",fetch=FetchType.EAGER,cascade=CascadeType.REMOVE)
	private Set<Address> addresses;

	public Customer() {}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public Integer getExtId() {
		return extId;
	}

	public void setExtId(Integer extId) {
		this.extId = extId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Set<Email> getEmailAddresses() {
		return emailAddresses;
	}

	public void setEmailAddresses(Set<Email> emailAddresses) {
		this.emailAddresses = emailAddresses;
	}

	public Set<Address> getAddresses() {
		return addresses;
	}

	public void setAddresses(Set<Address> addresses) {
		this.addresses = addresses;
	}
}

This class is annotated to declare it as a JPA entity. There are a few things interesting about this code.

First of all, the class implements Serializable to make it usable as a parameter in an EJB method. JPA in itself does not demand Serializable.

Either the class’ fields or the class’ properties (the getters and setters) can be annotated to declare them persistent. I prefer to annotate the fields. This allows me to create setters with checks build-in, or to provide only getters (read only properties). In fact, I could have left the setId method out because the id must be set by JPA and MySQL behind the scenes and by no one else (however, setId is needed to make JAX-WS work properly, so that’s why it’s in the code).

The @OneToMany annotation is needed to define a bidirectional relationship. The many side of the relationship (e.g. email) is called the owning side. The other side (customer) is called the inverse side. The inverse side must specify via which field the owning side points back at it (mappedBy=”customer”).

The collection of email addresses belonging to the customer is fetched eagerly. If this is not explicitly specified (the default fetching is lazy), then the collection might still be empty when it is returned via an EJB method, which is not the desired functionality.

Next take a look at two small enum classes:

package com.zienit.simpleejb;

public enum EmailType {
	UNSPECIFIED,
	HOME,
	WORK;
}

and

package com.zienit.simpleejb;

public enum AddressType {
	UNSPECIFIED,
	HOME,
	MAIL;
}

I’m using these enums mainly to see if and how they will turn up in the WSDL (or rather in the embedded or imported XML schema’s) which comes in to play later.

Let’s take a look at the Email class:

package com.zienit.simpleejb;

import java.io.Serializable;

import javax.persistence.*;
import javax.xml.bind.annotation.XmlTransient;

@Entity
@Table(name = "email")
@IdClass(com.zienit.simpleejb.EmailPK.class)
public class Email implements Serializable {

	@Id
	@Column(name="customer_id")
	private int _cust;

	@ManyToOne
	// this field must not be written to the database
	@JoinColumn(insertable=false,updatable=false)
	private Customer customer;
	@Id
	private int type;
	@Column(length=100)
	private String address;

	public Email() {}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	@XmlTransient // bidirectional relation causes a loop for XML serialization; break it at this side.
	public Customer getCustomer() {
		return customer;
	}

	public void setCustomer(Customer customer) {
		this._cust = customer.getId();
		this.customer = customer;
	}

	public EmailType getType() {
		return EmailType.values()[type];
	}

	public void setType(EmailType type) {
		this.type = type.ordinal();
	}
}

There are quite a few things to say about this class. First, because it has a composite primary key, a special class is needed. The @IdClass annotation specifies this class (EmailPK, shown later).

It would make sense to adorn the field Customer with both @Id and @ManyToOne annotations. This would express the fields dual role: part of the primary key and foreign key. However, this is not allowed by the JPA specification (OpenJPA ,the JPA component bundled in Geronimo, supports it anyway). I personally think that the specification is weak here, but I decided to stick with the specification. I have to introduce another field _cust and keep this field synchronized. Note that this field must reflect the actual field type (int). The synchronization is implemented in the method setCustomer() where both fields are updated with the correct value. Also note, there are now two fields in the class competing to update one field in the table. I’ve annotated the Customer field to declare it not insertable and not updatable. This will make sure that only _cust will be written to the database.

The @XmlTransient annotation will be discussed in the paragraph covering the JAX-WS perspective of this class.

The composite primary key class for Email looks like this:

package com.zienit.simpleejb;

import java.io.Serializable;

import javax.persistence.Column;

public class EmailPK implements Serializable {

	@Column(name="customer_id")
	private int _cust;
	private int type;

	public EmailPK() {}

	public void setCustomer(int id) {
		this._cust = id;
	}

	public int getCustomer() {
		return _cust;
	}

	public void setType(EmailType type) {
		this.type = type.ordinal();
	}

	public EmailType getType() {
		return EmailType.values()[type];
	}

	@Override
	public boolean equals(Object o) {
		return (o instanceof EmailPK) &&
			_cust == ((EmailPK)o)._cust &&
			type == ((EmailPK)o).type;
	}

	@Override
	public int hashCode() {
		return _cust * EmailType.values().length + type;
	}
}

The fields customer and type must have the same name as the corresponding fields in class Email.

Note that the type field and type property have a different type (int versus EmailType). The conversion is done via the ordinal and the values methods of the EmailType enum class. This trick only works if the coding in the database starts with zero and increases with steps of one.

A JPA composite primary key class must override the equals and hashCode methods. Note that the @Override annotation helps to avoid misspelling of the method name (and thereby unwillingly introducing an additional method instead of overriding an inherited one). Make sure to abide equals and hashCode semantics (check your local SCJP study guide :-) )!

The Java classes that are needed to wrap the Address table are very similar to the classes discussed above. I give you the code without comments.

package com.zienit.simpleejb;

import java.io.Serializable;

import javax.persistence.*;
import javax.xml.bind.annotation.XmlTransient;

@Entity
@Table(name = "address")
@IdClass(com.zienit.simpleejb.AddressPK.class)
public class Address implements Serializable {
	@Id
	@Column(name="customer_id")
	private int _cust;

	@ManyToOne
	// this field must not be written to the database
	@JoinColumn(insertable=false,updatable=false)
	private Customer customer;
	@Id
	private int type;
	@Column(length=100)
	private String street;
	@Column(name="postal_code", length=100)
	private String postalCode;
	@Column(length=100)
	private String city;

	public Address() {}

	@XmlTransient
	public Customer getCustomer() {
		return customer;
	}

	public void setCustomer(Customer customer) {
		this._cust = customer.getId();
		this.customer = customer;
	}

	public AddressType getType() {
		return AddressType.values()[type];
	}

	public void setType(AddressType type) {
		this.type = type.ordinal();
	}

	public String getStreet() {
		return street;
	}

	public void setStreet(String street) {
		this.street = street;
	}

	public String getPostalCode() {
		return postalCode;
	}

	public void setPostalCode(String postalCode) {
		this.postalCode = postalCode;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}
}

 

 
package com.zienit.simpleejb;

import java.io.Serializable;

import javax.persistence.Column;

public class AddressPK implements Serializable {

	@Column(name="customer_id")
	private int _cust;
	private int type;

	public AddressPK() {}

	public void setCustomer(int id) {
		this._cust = id;
	}

	public int getCustomer() {
		return _cust;
	}

	public void setType(AddressType type) {
		this.type = type.ordinal();
	}

	public AddressType getType() {
		return AddressType.values()[type];
	}

	@Override
	public boolean equals(Object o) {
		return (o instanceof AddressPK) &&
			_cust == ((AddressPK)o)._cust &&
			type == ((AddressPK)o).type;
	}

	@Override
	public int hashCode() {
		return _cust * AddressType.values().length + type;
	}
}

Coding the EJB Perspective

It is now time to take a look at the EJB perspective. I’m creating a simple stateless session bean that will act as a facade for the JPA activity. First look at the remote interface:

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);
}

The interesting thing here is the annotation: @Remote. That’s all. Let’s look at the bean’s implementation class:

package com.zienit.simpleejb;

import java.util.Set;

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

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

	@PersistenceContext private EntityManager em;

	@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();
	}
}

From an EJB perspective the annotation @Stateless is important. Notice that the bean’s implementation class implements the bean’s interface. This is a good practice (this was impossible under the EJB 2.0 specification!).

The @PersistenceContext annotation allows the container to inject a JPA entity manager at runtime. This object plays an important role in creating new persistent entities or looking existing ones up in the database. Note that, since we’re doing database activity inside an EJB stateless session bean’s methods, we automatically get a transaction with the execution of each of the methods.

There has to be a connection between your code and the database. This connection is defined in the JPA file persistence.xml which must be located in the META-INF directory. Go to the project folder /src/main/resources and create subdirectory META-INF. Then create the file persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
	version="1.0">
	<persistence-unit name="customer">
		<jta-data-source>jdbc/CustomerDS</jta-data-source>
	</persistence-unit>
</persistence>

Coding the JAX-WS perspective

There is a multitude of annotation in CRMBean that I haven’t discussed so far. The most important one is @WebService, which declares this class to be a WSDL port type (a collection of methods). @WebService allows to name various elements in the WSDL definition to your preference. The WSDL will be created at deployment time by a tool that uses introspection on the bytecode to get the information that it needs. Introspection can’t determine things like the names or method arguments (because this is normally not included in the bytecode). Therefore, I’ve annotated the method signatures with @WebParam to explicitly provide the names for the arguments. Also, a useful name if provided for the return value of the methods through the use of the @WebResult annotation.

I’ve also postponed the explanation of @XmlTransient in class Email and Address. This annotation is necessary because of the circular references between Customer and Email and between Customer and Address, which is a logical result of their bidirectional relationship. This circular reference however is problematic for the JAX-WS (generated) code that tries to serialize the objects to XML. This code will enter an endless loop. The @XmlTransient annotation tells the serialization process not to consider this field. We could have chosen either side of the relationship to be excluded, but from a functional perspective, the link from Customer to Email is more valuable than the reverse link, so I’ve kept that one. 

Prepare for testing

Once the bean is deployed to an application server, there will be a number of web services waiting for requests. There are several tools available to test web services in a flexible way. However, since I’m doing development with Eclipse I might as well use the goodies that come with it. Switch to the Java EE perspective, select run -> Launch the Web Services Explorer and behold the Web Services Explorer. Select the WSDL icon on the top left to switch the navigator to WSDL Main. Click on WSDL Main in the navigator and then enter a url in the Open WSDL dialog and click Go. You can now invoke your service with parameters provided by yourself. I’ll explain more about the particular url to enter later in this post.

Deployment to Apache Geronimo

Before the web service can be successfully deployed to Geronimo, a few things have to be prepared. First, the MySQL Connector/J JDBC driver must be installed into Geronimo’s repository. I used the Geronimo console to upload the JDBC jar and install it into the Geronimo repository. Click Services -> Repository in the Console Navigation panel and enter the path to the driver’s jar file and the following ’sensible’ coordinates:

  • groupId: mysql
  • artifactId: mysql-connector-java
  • version: 5.1.10
  • type: jar

Next, a database pool can be created using the Geronimo console. Start the Geronimo database pool wizard to create a database pool called jdbc/CustomerDS. Choose database type MySQL and click next. Select the correct JDBC jar file from the list, enter a database name, a user and a password and keep the other defaults. There should be a live database pool now (check it!).

As it turned out after my first optimistic deploy attempt, Geronimo will need a deployment plan (a container specific deployment descriptor) that declares the dependency between the EJB and the database pool. Create this file called openejb-jar.xml in directory src/main/resources/META-INF:

<?xml version="1.0" encoding="UTF-8"?>
<openejb-jar xmlns="http://openejb.apache.org/xml/ns/openejb-jar-2.2"
	xmlns:sys="http://geronimo.apache.org/xml/ns/deployment-1.2">

	<sys:environment>
		<sys:moduleId>
			<sys:groupId>com.zienit.simple</sys:groupId>
			<sys:artifactId>simpleejb</sys:artifactId>
			<sys:version>0.0.1-SNAPSHOT</sys:version>
			<sys:type>jar</sys:type>
		</sys:moduleId>

		<sys:dependencies>
			<sys:dependency>
				<sys:groupId>console.dbpool</sys:groupId>
				<sys:artifactId>jdbc_CustomerDS</sys:artifactId>
				<sys:version>1.0</sys:version>
				<sys:type>rar</sys:type>
			</sys:dependency>
		</sys:dependencies>
	</sys:environment>
</openejb-jar>

Build the project using Maven (Maven install). Upload the EJB archive that Maven created in the target subdirectory, using the Geronimo Console (Applications -> EJB JARs). After successful deployment, the web service is waiting for requests at http://<host>:8080/CRMService/CRMPortType (substitute <host> with the correct ip address). This is the default location and is composed of the service name (CRMService) and the port name (CRMPortType). A good first check is to enter this url in a browser with ?wsdl appended to it, to request the WSDL file that was automatically created by the server at deployment time. If this WSDL is visible, it is time to start testing the web service in the Eclipse Web Service Explorer using that same url (with ?wsdl).

One major bug I found in Geronimo occurs when the database decides to roll back due to violated integrity constraints. The exception will not be propagated up to the web service layer, in other words the web service client will never know that no change was written to the database. A serious problem!

Deployment to GlassFish

 In Glassfish the JDBC driver is installed less elegantly than in Geronimo: just copy the driver’s jar file to the \lib subdirectory of the Glassfish installation. Then, using the GlassFish console, a Connection Pool must be created for the MySQL database, and next a JDBC Resource must be created that links to the Connection Pool. Make sure the JDBC resource is called jdbc/CustomerDS.

My first optimistic deploy attempt was well received by GlassFish. The EJB jar was deployed without a container specific deployment descriptor without problems. The web service listens at http://<host>:8080/CRMService/CRMPortType?wsdl. Note that this default location is identical to the default location produced by Geronimo. I’ve tested the web service using Eclipse Web Service Explorer. I did not find any problems.

Deployment to JBoss

In JBoss the installation of the JDBC driver follows the same pattern as GlassFish. You must copy the driver’s jar to the lib directory under the chosen server-type (usually default). Next, using the JBoss Administration Console, a datasource must be created. Here I run into the first odd problem: JBoss cannot deal with JNDI names that contain a slash (jdbc/CustomerDS). So, I created a datasource under the simpler name CustomerDS and changed persistence.xml accordingly. This still didn’t work, and after consulting Google for a while, came the second surprise: in persistence.xml, I also had to prefix CustomerDS with “java:”.

  
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
	version="1.0">
	<persistence-unit name="customer">
		<jta-data-source>java:CustomerDS</jta-data-source>
		<properties>
			<property name="hibernate.show_sql" value="true" />
		</properties>
	</persistence-unit>
</persistence>

I also added a specific property to instruct hibernate (which implements the JPA in JBoss) to dump the generated SQL queries to the console for a closer inspection. This was needed because it still didn’t work! At deployment time I got the illustrous message: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags. Again after Googling I found the cause. In the original source code, I had been a bit lazy and I used java.util.Collection in the Customer class to contain the one-to-many relationships. This is not a good practice: use java.util.List (for ordered items) or java.util.Set (for unique items). When JBoss finds more than one eagerly fetched one-to-many relationships, these relationships must be contained in java.util.Set members. Luckily, java.util.Set has the correct semantics in this case.

After this change in the source code, the EJB could be deployed. The web service listens at default location http://<host>:8080/simpleejb/CRMBean. Oddly, this location is different from the default location chosen by Geronimo and GlassFish, but I guess the JAX-WS specification does not state any default, so any behavior in this matter is acceptable.

During testing a ran into another problem. Hibernate was trying to write the field _cust to the database, and since this field does not exist, MySQL rightfully decides not to cooperate. After another Google session I found a pointer to the problem: the field _cust must be annotated with @Column(name=customerId) at all occurrences in the code. I originally left it out in the primary key classes. After this final change JBoss worked fine.

Conclusion

Thanks to EJB 3, JPA and JAX-WS, it is possible to create web services with relatively little java code and xml appendices. It gave me an “agile” feeling, a sensation not felt in the EJB 2 days. The three open source application servers that I tried implement the specifications as expected (though Geronimo contains a showstopper bug at this moment and JBoss did put up a real fight before letting the example run). I hope you found this post useful. Thanks for reading.

No Comments

Comments are closed.