Developing RESTful Web Services using JBoss RESTEasy

Enterprise integration was and remains a key challenge in ensuring availability of enterprise data as a single source of truth in real time manner. Over the decades number of techniques/technologies have come and gone. We have seen EAI,JMS, Web Services, SOA etc that have contended providing true enterprise integration solutions. The latest to join this bandwagon is REST and its implementation avatar is described as RESTful Web Services. In case you are interested in understanding what REST is and how does it compare vis-a-vis SOAP, WebServices et all refer the link(URL: http://www.petefreitag.com/item/431.cfm) for details. This post is going to provide a ready-to-use tutorial around how to implement RESTful WebServices using JBoss RESTEasy project.

Enterprise integration was and remains a key challenge in ensuring availability of enterprise data as a single source of truth in real time manner. Over the decades number of techniques/technologies have come and gone. We have seen EAI,JMS, Web Services, SOA etc that have contended providing true enterprise integration solutions. The latest to join this bandwagon is REST and its implementation avatar is described as RESTful Web Services. In case you are interested in understanding what REST is and how does it compare vis-a-vis SOAP, WebServices et all refer the link for details. This post is going to provide a ready-to-use tutorial around how to implement RESTful WebServices using JBoss RESTEasy project.

The RESTEasy project is configurabe as a web application archive(WAR) file and is deployable in any JEE compliant servlet container. For our tutorial, I have deployed it in a Tomcat container(version 5.5). The development environment is set up using Eclipse Europa version 3.3.2 and the Sysdeo Tomcat Eclipse plugin version 3.2.1. To kick start the proceedings, create a Tomcat Project called RESTWeb in Eclipse. Specify your context name and web application root. I have named both as “/rest”. Within the project create the following folder structure RESTWeb/rest/WEB-INF/lib. Within WEB-INF folder create the web.xml file. The structure of the web.xml is as follows:

RESTful Web Services Application
<!-- Set this if you want Resteasy to scan for JAX-RS classes-->

resteasy.scan
true

org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap

Resteasy
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher

Resteasy
/*

30

The key entry in the web.xml to make a note of is resteasy.scan. By setting this property to true, on startup the RESTEasy runtime evaluates all deployed class files and determines if any are REST related and registers them appropriately. Get the latest RESTEasy project related files at the download site. I am using RESTEasy version GA 1.1. Within the WEB-INF/lib folder add all dependant jars. These can be found within the resteasy-jaxrs.war\WEB-INF\lib folder of the download. Copy the files in the RESTWeb project lib folder and start the Tomcat server. Refer the SysDeo plugin documentation for project specific settings. In the Tomcat logs you will notice the following warning message:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/E:/eclipse/workspace/REST/rest/WEB-INF/lib/slf4j-simple-1.5.8.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/eclipse/workspace/REST/rest/WEB-INF/lib/slf4j-simple1.5.8.jar!/org/slf4j/impl/StaticLoggerBinder.class]
This is caused by the presence of two slf4j files, namely slf4j-api-1.5.8.jar and slf4j-simple-1.5.8.jar. Remove the file slf4j-simple-1.5.8.jar from the lib folder and restart the Tomcat and the warning messages will not appear.Now let’s set up the REST specific files. RESTEasy project extensively uses annotations to add REST flavour to Java classes. For our tutorial I am setting up three class files namely, InsuranceProduct, InsuranceProductListing and InsuranceProductService. InsuranceProductService represents the REST specific classes and InsuranceProduct and InsuranceProductListing represents data classes. Here’s their structure (package name and import statements have been removed for better legibility):

——————— Start InsuranceProductService —————————-

@Path("/insurance")
public class InsuranceProductsService {

	@GET
	@Path("/products")
	public String getProducts() {
		InsuranceProductListing listing = new InsuranceProductListing();
		try {
			JAXBContext ctx = JAXBContext.newInstance(InsuranceProductListing.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(listing, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

	@GET
	@Path("/product")
	public String getProduct(){
		InsuranceProduct product = new InsuranceProductListing().getProduct();
		try {
			JAXBContext ctx = JAXBContext.newInstance(InsuranceProduct.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(product, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

	@GET
	@Path("/list")
	public String getList(){
		List products = new InsuranceProductListing().getProducts();
		try {
			JAXBContext ctx = JAXBContext.newInstance(ArrayList.class, InsuranceProduct.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(products, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

	@GET
	@Path("/product/{name}")
	public String getProduct(@PathParam("name") String name) {
		InsuranceProduct product = new InsuranceProductListing().getProduct(name);
		try {
			JAXBContext ctx = JAXBContext.newInstance(InsuranceProduct.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(product, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

	@GET
	@Path("/product")
	public String getProductByParameter(@QueryParam("name") String name) {
		InsuranceProduct product = new InsuranceProductListing().getProduct(name);
		try {
			JAXBContext ctx = JAXBContext.newInstance(InsuranceProduct.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(product, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

	@GET
	@Path("/product")
	public String getProductByMatrix(@MatrixParam("name") String name) {
		InsuranceProduct product = new InsuranceProductListing().getProduct(name);
		try {
			JAXBContext ctx = JAXBContext.newInstance(InsuranceProduct.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(product, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

}

——————— End InsuranceProductService —————————————-

——————— Start InsuranceProductListing ————————————–

@XmlRootElement(name="insuranceProducts")
@XmlSeeAlso({InsuranceProduct.class})
public class InsuranceProductListing {

	private List products = new ArrayList();

	private static InsuranceProduct NULL_PRODUCT = null;

	static {
		NULL_PRODUCT = new InsuranceProduct();
	}

	private InsuranceProduct product = null;

	{
		InsuranceProduct product1 = new InsuranceProduct();
		product1.setName("TermLife");

		product1.addDuration("5 years");
		product1.addDuration("10 years");
		product1.addDuration("15 years");
		product1.addDuration("20 years");
		product1.addDuration("25 years");

		this.products.add(product1);
		this.product = product1;

		InsuranceProduct product2 = new InsuranceProduct();
		product2.setName("WholeLife");

		product2.addDuration("5 years");
		product2.addDuration("10 years");
		product2.addDuration("15 years");
		product2.addDuration("20 years");
		product2.addDuration("25 years");

		this.products.add(product2);

	}

	@XmlElement(name="product", type=InsuranceProduct.class)
	public List getProducts() {
		return this.products;
	}

	public void setProducts(InsuranceProduct[] productsArray) {
		this.products = Arrays.asList(productsArray);
	}

	private InsuranceProduct[] getProductArray(List prodList) {

		InsuranceProduct[] prodArray = new InsuranceProduct[prodList.size()];
		for (int i = 0; i < prodList.size(); i++) {
			prodArray[i] = prodList.get(i);
		}
		return prodArray;
	}

	public InsuranceProduct getProduct() {
		return this.product;
	}

	public InsuranceProduct getProduct(String name) {
		if (name == null){
			return NULL_PRODUCT;
		}
		for (InsuranceProduct product : this.products) {
			if (name.equals(product.getName())) {
				return product;
			}
		}
		return NULL_PRODUCT;
	}

}

——————— End InsuranceProductListing —————————————-

——————— Start InsuranceProduct ———————————————

@XmlRootElement(name="product")
@XmlType(propOrder={"name", "duration"})
public class InsuranceProduct {

	private String name = null;
	private List durations = new ArrayList();

	@XmlElement
	public String getName() {
		return name;
	}

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

	@XmlElement(name="duration")
	@XmlElementWrapper(name="durations")
	public String[] getDuration() {
		return getStringArray(this.durations);
	}

	public void setDuration(List duration) {
		this.durations = duration;
	}

	public void addDuration(String duration) {
		this.durations.add(duration);
	}

	private String[] getStringArray(List durationList) {

		String[] durationArray = new String[durationList.size()];
		for (int i = 0; i < durationList.size(); i++) {
			durationArray[i] = durationList.get(i);
		}
		return durationArray;
	}

}

——————— End InsuranceProduct ———————————————–
Let’s begin with understanding how a POJO class is enhanced with annotations to make it RESTful. Refer the Insurance ProductService class. The first line it contains is

@Path("/insurance")

This establishes the relative URL to access a specific functionality within the InsuranceProductService class. Based on the current configuration, the class can be accessed using the URL: http://<servername&gt;:<port>/rest/insurance. Note this URL by itself will not retrieve anything, this will be used in conjunction with additional relative part mentioned along with the individual class method. Let me explain how.

Let’s look at the method getProducts defined within the class. The method is appended with two annotations namely @GET and @Path. The @GET annotations specifies that this is a RESTful Web Service with semantic compliance to the HTTP GET method. The @Path annotation specifies the sub-path which needs to be appended to the class’s @Path annotation. So to invoke the RESTful Web Service, type the URL http://<servername&gt;:<port>/rest/insurance/products in your browser. The browser will display the following XML:


    TermLife

      5 years
      10 years
      15 years
      20 years
      25 years

    WholeLife

      5 years
      10 years
      15 years
      20 years
      25 years

Let’s try and understand the code written within the getProducts method. The code is as below:

	public String getProducts() {
		InsuranceProductListing listing = new InsuranceProductListing();
		try {
			JAXBContext ctx = JAXBContext.newInstance(InsuranceProductListing.class);
			Marshaller marshaller = ctx.createMarshaller();
			StringWriter sw = new StringWriter();
			marshaller.marshal(listing, sw);
			return sw.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;

	}

The getProducts method returns the response XML as a String. Therefore, the method implementation needs to convert the Java object(s) into its corresponding XML format. This is facilitated in RESTEasy via JAXB. The code written within the try catch block is the JAXB code to convert the InsuranceProductListing object into its equivalent XML representation. Please do take note of one thing, the class name specified in the parameter of the newInstance method of JAXBContext and the object passed in the Marshaller object’s marshal method should be the same else you might be accosted by the following exception:

JAXBException: xxx.xxx.Xxxxx nor any of its super class is known to this context

Now we need to look at the aspects that need to be added to the InsuranceProductListing class to make its properties marshallable for responding to getProducts request. The first and foremost entry that is required within the InsuranceProductListing class is to specify that it is a XMLRootElement. This is specified by the @XmlRootElement annotation. The name attribute defines the XML root tag name. The @XmlSeeAlso annotation helps specify the additional dependant classes referred in this class.

@XmlRootElement(name="insuranceProducts")
@XmlSeeAlso({InsuranceProduct.class})
public class InsuranceProductListing {
Next we need to define the elements/attributes of the response xml. This is done by defining using the @XmlElement annotation. Likewise it has @XmlAttribute annotation for attribute definition.
@XmlElement(name="product", type=InsuranceProduct.class)
public List getProducts() {
	return this.products;
}
Similar definitions need to be done within InsuranceProduct class. Just one more thing to note, in case you have a requirement where the elements need to be displayed in a specific order, that can be specified as a class level @XmlType annotation for e.g. in InsuranceProduct, it is defined by the following line:
@XmlType(propOrder={"name", "duration"})
For collections, by default the JAXB implementation does not provide a wrapper tag, we can add it by using the @XmlElementWrapper annotation. For example, in InsuranceProduct class for the durations List, we have implemented it  in the following manner:
@XmlElementWrapper(name="durations")
Now let’s look at the method getList. Unlike the getProducts method which returns a InsuranceProductListing class that acts as a wrapper for list of InsuranceProduct objects, the getList method returns a List of InsuranceProduct. This invocation fails with the following exception:
unable to marshal type "java.util.ArrayList" as an element because it is missing an @XmlRootElement annotation
Basically RESTEasy does not support methods that define ArrayList or Array etc as their root level objects.
Update(22/03/2011): The above statement may no longer apply. Apparently the current version 5.1.0 supports array/list as return types. For details refer here. Thanks Cleberson for making me look this up.Now let us move on to passing search parameters to our RESTful Web Service requests. RESTEasy supports three different mechanisms:

  • PathParam
  • QueryParam
  • MatrixParam

The implementations can be viewed by referring to the getProduct, getProductByParameter and getProductByMatrix methods respectively. For PathParam the URL is http://<servername&gt;:<port>/rest/insurance/product/TermLife, for queryParam the URL is http://<servername&gt;:<port>/rest/insurance/product?name=WholeLife and for matrixParam the URL is http://<servername&gt;:<port>/rest/insurance/product;name=TermLife. QueryParam and MatrixParam support multiple input parameters. There are other param options such as CookieParam or FormParam to retrieve information from the corresponding web application component.

Update(4th Nov ’11): If you need a view from the other end i.e. the client side refer my blog post on RESTEasy Client Framework.

That’s all from the REST desk for now.

Advertisements

18 thoughts on “Developing RESTful Web Services using JBoss RESTEasy

    1. Sorry did not get a chance to work on the client API. The documentation has a good reference here. Will put it on my TODO list.

  1. Hi There,

    What imports did you put at the top of your files? I’m getting errors as Eclipse doesn’t understand @GET or @PATH

    I have imported all the libs into the lib directory though…

    Thanks

    1. Jonathan,

      Sorry for the delayed response. I am using the following imports:

      import javax.ws.rs.GET;
      import javax.ws.rs.MatrixParam;
      import javax.ws.rs.Path;
      import javax.ws.rs.PathParam;
      import javax.ws.rs.QueryParam;

  2. Looking for a good example of using RESTEasy for receiving multipart/form-data file posts -i.e., getting a file that a user uploaded from a web browser. Noticed your well written example and thought you might be able to point me in the right direction.

  3. Hi,
    I have a small doubt .I request you please clarify.

    Problem: I have a method which will take jobid as input parameter and it will return some string. This method completely depends on some other Restservice URL. This rest service url i have to pick from a property file. Here My doubt is how we can construct the url?

    below url is valid url.
    http://localhost:9090/rest/getjob?jobid=10

    getjob is rest service implementation method.

    getJob( String jobid){

    System.out.println(“jobid”+);

    }

    can u please some one guide how we can pass parameters to method in rest services?./

  4. Refer my example InsuranceProductService. In this example look at line 56. The user will need to pass param by using the URL /product/TermLife.

    An alternative approach is using a queryParam as explained in line 72. the URL path would be product?name=WholeLife.

    Hope that answers your question.

    1. Cleberson,

      It appears that RESTEasy uses a proprietary annotation ‘Wrapped’ to allow array / ArrayList to be used a returning value.

      Refer the url for details. Also refer this. I have posted an update on my post. Thanks for bringing this to my notice.

  5. Hy Mr. President..thank you for the help.

    But what I want is send an array or arraylist to my rest and not retreaving datas something like that:

    public void createCustomers(List customers){
    …insert into curstomers
    }

    where the client sends the collection or array of customer

    thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s