How to integrate external clients with JBoss-Seam using JAX-RS – the RESTful web service

By | June 29, 2009

JBoss-Seam is a great JAVA web building framework giving us out of the box CRUD and QBE functionality, allowing to work directly with POJO’s in UI and in business logic without DTO in between. The web UI powered by AJAXified JSF is capable to fulfil even sophisticated HMI requirements. However the software architects deal often with the problem how to integrate easily external systems and clients with the JBoss-Seam application reusing as much as possible of server logic and work with POJO’s at the client side. One of the most efficient approaches I tried so far is JAX-RS aka JSR-311 and the JBoss implementation of it – RESTEasy which is well integrated in JBoss-Seam framework.
This tutorial shows how to expose the CRUD and QBE functionality over JAX-RS RESTful web service and build the simple JAVA client which consumes these services. I assume in this tutorial that we deal with the seam-gen generated application deployed as WAR. I tested it with JBoss-Seam 2.1.2 GA.

The first step is the preparation of our IDE and ant build system. In order to compile new annotations we need jaxrs-api.jar in the IDE classpath. The seam-gen generated applications have the whole building logic in the generated build.xml. We need to extend the list of jars which are going to be included in the WAR file. This list can be found in the file deployed-jars.list in the root directory of the project. Open it an add following entries at the end:

jaxrs-api.jar
resteasy-jaxrs.jar
resteasy-jaxb-provider.jar
slf4j-api.jar
slf4j-log4j12.jar

The next step is to extend our POJO’s by adding JAXB annotations. This is required for the transparent marshalling and demarshalling to and from XML.

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.xml.bind.annotation.XmlRootElement;
import net.dobosz.restseam.model.types.Country;

import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;


@Entity
@XmlRootElement
public class Customer implements Serializable {
	private Long id;
	private String code;
	private String name;
	private String city;
	private String zip;
	private String street;
	private Country country;
	private boolean active;

	@Id @GeneratedValue
	public Long getId() {
		return id;
	}

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

	@Length(min=3, max=5) @NotNull	
	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	@Length(max=50) @NotNull
	public String getName() {
		return name;
	}

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

	public String getCity() {
		return city;
	}

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

	public String getZip() {
		return zip;
	}

	public void setZip(String zip) {
		this.zip = zip;
	}

	public String getStreet() {
		return street;
	}

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

	@Enumerated(EnumType.STRING)
    @Column(length=2)
	public Country getCountry() {
		return country;
	}

	public void setCountry(Country country) {
		this.country = country;
	}

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

}

In the above example I use only one JAXB annotation @XmlRootElement. See JBoss RESTEasy Documentation for all supported JAXB annotations. Also the JAVA6 SE javadoc shows what is possible here.

The next step is to provide the resource class which will wrap the CRUD and QBE functionality in the RESTful JAX-RS way.

We start with the interface of the resource class which will be exposed to external clients:

package net.dobosz.restseam.resource;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

import net.dobosz.restseam.model.Customer;

public interface CustomerResource {

	@GET
	@Path("/{code}")
	public  Customer getCustomer(@PathParam("code") String code) ;

	@GET
	@Path("/list")
	public  List<Customer> getList( ) ;	
	
	@POST
	@Path("/create")
	@Consumes("application/xml")
	public   Customer createCustomer(Customer customer) ;

	@POST
	@Path("/update")
	@Consumes("application/xml")	
	public   Customer updateCustomer(Customer customer );

	@DELETE
	@Path("/delete/{id}")
	public  void deleteCustomer(@PathParam("id") Long id);

	@POST
	@Path("/qbe")
	@Consumes("application/xml")
	public  List<Customer> CustomerQBE(Customer customer);
}

The Implementation is straightforward, because we reuse Home and List classes which were already generated by seam-gen:

package net.dobosz.restseam.resource;

import java.util.List;

import javax.persistence.EntityManager;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;

import net.dobosz.restseam.action.CustomerHome;
import net.dobosz.restseam.action.CustomerList;
import net.dobosz.restseam.model.Customer;

import org.jboss.seam.Component;

@Path("/customer")
@Produces("application/xml")
public class CustomerResourceImpl implements CustomerResource {
	
	//@In(create=true)
	//Injection does not work here
	CustomerHome customerHome = (CustomerHome) Component.getInstance(CustomerHome.class);

	//@In(create=true)
	CustomerList customerList = (CustomerList) Component.getInstance(CustomerList.class);
	

	public Customer getCustomer(@PathParam("code") String code) {
		Customer customer = customerHome.findByUK(code.toUpperCase());
		if (customer == null)
			throw new WebApplicationException(Response.Status.NOT_FOUND);
		return customer;
	}
	

	public Customer createCustomer(Customer customer) {
		customerHome.setInstance(customer);
		if(!customerHome.persist().equals("persisted"))
			throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE);
		return customerHome.getInstance();
	}


	public Customer updateCustomer(Customer customer) {
		EntityManager em = customerHome.getEntityManager();
		customerHome.clearInstance();
		if (!checkExists(customer.getId(), em))
			throw new WebApplicationException(Response.Status.NOT_FOUND);
        em.merge(customer);
		customerHome.setInstance(customer);
		customerHome.update();
		return customerHome.getInstance();
	}


	public void deleteCustomer(@PathParam("id") Long id){
		if (!checkExists(id, customerHome.getEntityManager()))
			throw new WebApplicationException(Response.Status.NOT_FOUND);
		if (!customerHome.remove().equals("removed"))
			throw new WebApplicationException(Response.Status.CONFLICT);
	}


	public List<Customer> CustomerQBE(Customer customer) {
        customerList.setCustomer(customer);
        return customerList.getResultList();
	}

	public List<Customer> getList() {
      customerList.setCustomer(new Customer());
      return customerList.getResultList();
	}
	
	private boolean checkExists(Long id, EntityManager em){
		if (em.find(Customer.class, id) == null)
			return false;
		else
			return true;
	}
}

The method findByUK is the only one I added in CustomerHome manually:

	@Transactional
	public Customer findByUK(String code) {
		Customer customer = null;
		try {
			customer = (Customer) getEntityManager().createQuery(
					"select c from Customer c where c.code = ?1")
					.setParameter(1, code).getSingleResult();
		} catch (NoResultException e) {
			return null;
		}
		return customer;
	}	

Well, this is the whole implementation at the server side. Build and deploy your application. Add some data via the web UI and test your RESTful web service in browser. The URL for your resource can look like this:

http://localhost:8080/restseam/seam/resource/rest/customer/list

where:

restseam          - is the deployment name of your application, typically the name of the WAR file
/seam/resource    - the mapping pattern of Seam Resource Servlet provided in web.xml
/rest             - default RESTEasy prefix - which can be changed in component.xml
/customer         - your resource name provided by class level @Path annotation
/list             - method name provided by the method level @Path annotation

A a response you should get the xml content – the collection of customers.
If you get the error 404 – resource not found, check your build and make sure the resource class was compiled into the right directory WEB-INF/classes.
The seam-gen generated build.xml uses as default the DEV profile which compiles the src/hot classes into WEB-INF/DEV. This is the reason why the resource Servlet can not found the resource class.

Now we can build the simple client.
First we must prepare the jar containing POJO’s and the resource interfaces. I prepared the new target in build.xml performing this task automatically:

    <target name="archive-rs" depends="compileactions" 
            description="Package pojos and resource interfaces jax-rs ">
        <jar jarfile="${dist.dir}/${project.name}_jaxrs.jar" basedir="${classes.action.dir}">
        <include name="net/dobosz/restseam/model/**/*.class"/>
        	 <include name="net/dobosz/restseam/resource/**/*.class"/>	
        	 <exclude name="net/dobosz/restseam/resource/**/*Impl.class"/>
        </jar>
    </target>	

The restseam_jaxrs.jar file which is created by this ant target is needed at the client side as well as following other jars:

commons-codec.jar
commons-httpclient.jar
commons-logging.jar
jaxrs-api.jar
log4j.jar
resteasy-jaxb-provider.jar
resteasy-jaxrs.jar
slf4j-api.jar
slf4j-log4j12.jar
restseam_jaxrs.jar"

The client code is as simple as this:

package net.dobosz.restseam.client;

import java.util.List;

import org.jboss.resteasy.client.ClientResponseFailure;
import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

import net.dobosz.restseam.model.Customer;
import net.dobosz.restseam.resource.CustomerResource;


public class SimpleTest {

	public static void main(String[] args) {

		//Initialise the session
		RegisterBuiltin.register(ResteasyProviderFactory.getInstance());

		//Create resource delegate
		CustomerResource customerResource = ProxyFactory.create(CustomerResource.class,
				"http://localhost:8383/restseam/seam/resource/rest/customer");
		
		System.out.println("Testing GET");
		Customer customer = null;
		
		customer = customerResource.getCustomer("ORCL");
		assert customer != null;
		System.out.println("Customer: "+customer);


		System.out.println("Testing Insert");
		Customer nc = new Customer();
		nc.setCode("TEST");
		nc.setName("xxx");
		nc = customerResource.createCustomer(nc);
		
		assert nc.getId() != null;
		
		System.out.println("New Customer: "+nc);	
	
		System.out.println("Testing Update");
		nc.setName("Ex-Oracler");
		Customer newCustomer = customerResource.updateCustomer(nc);
		System.out.println("Customer after update: "+newCustomer);
		
		System.out.println("Testing  Delete");
		
		customerResource.deleteCustomer(nc.getId());

		System.out.println("Testing  QBE");
		
		Customer qbeCustomer = new Customer();
		qbeCustomer.setCode("X");
		
		List<Customer> result = customerResource.CustomerQBE(qbeCustomer);
		
		for(Customer cust:result){
			System.out.println(cust);
		}
		
       System.out.println("Testing  List");
		
		
		List<Customer> liste = customerResource.getList();
		
		for(Customer cust:liste){
			System.out.println(cust);
		}
		
	    System.out.println("Testing  Update of not existing Customer");
	    
	    Customer notExistingCustomer = new Customer();
	    notExistingCustomer.setId(new Long(22222));
	    notExistingCustomer.setCode("ABCD");
	    notExistingCustomer.setName("abcd");
	    try {
	       notExistingCustomer = customerResource.updateCustomer(notExistingCustomer);
	    } catch (ClientResponseFailure e){
	    	System.out.println("Customer does not exists - Response code:"+e.getResponse().getStatus());
	    }
	}
}

The ProxyFactory is the most powerful part of RESTEasy which creates the delegate class based on the resource interface.
The example above does not care about authentication, however the ProxyFactory accept also the HttpClient Object, so its possible to wire over SSL/TSL or using basic or realm authentication, timeouts, etc. The Jboss-Seam JAAS based security logic can be reused as well.

The complete source code of server and client application can be checked out anonymously from the subversion repository:

svn co http://svn.dobosz.at/svn/restseam

If you want to deploy it, please adjust the jboss.home variable in build.properties and datasource parameters in resources/restseam-dev-ds.xml. The application is currently configured against ORACLE 10gR2 XE database.

4 thoughts on “How to integrate external clients with JBoss-Seam using JAX-RS – the RESTful web service

  1. Marcello Nuccio

    Thanks for this useful post.

    Two notes:

    Why do you repeat @GET, @POST, @DELETE, @Path, and @Consumes annotations for CustomerResourceImpl methods? They are not needed and one main advantage of defining the interface is to keep the implementation free from JAX-RS annotations.

    For the same reason, @Path(“/customer”) and @Produces(“application/xml”) should go to CustomerResource interface. You put them only on the implementation, where they are not needed.

    thanks

    1. Jacek Dobosz Post author

      Thanks for pointing this. The code was written some months ago when the early stage of 2.1.2 were available and it was not quite clear for me how it works because of lack of documentation. I will refactor these examples soon.

      1. Jacek Dobosz Post author

        I tried to move all annotations to the interface, however it doesn’t work as you suggest (at least in 2.1.2). It seams that the class level annotations @GET and @Produces are necessary in the implementation and must be removed from the interface. I will port these examples to 2.1.0 and check again.

Comments are closed.