JavaOne Hands-On Lab

Project Jersey: Building RESTful Web Services in Java

LAB-5542

Copyright 2009 Sun Microsystems

 

LAB-5542: Project Jersey: Building RESTful Web Services in Java

Expected Duration: 120 minutes

Last Updated: May 20th, 2009

Project Jersey is the reference implementation of JAX-RS (JSR-311) - Java API for building RESTful Web Services. This API makes RESTful Web Services development with Java and some other VM-based languages very easy and straightforward. Besides the features defined by the standards, Jersey defines additional set of API's that make the developers' life even easier.

In this lab, we will start from basics - showing how to build a Hello World type of application and then show some more advanced features of JAX-RS and Jersey such as support for various media types, basic error handling, MVC support and client-side API's.

Copyright  2009 Sun Microsystems, Inc. All rights reserved. Sun, Sun Microsystems,
the Sun logo, Solaris, Java, the Java Coffee Cup logo, JavaOne, the JavaOne logo,
and all Solaris-based and Java-based marks and logos are trademarks or registered
trademarks of Sun Microsystems, Inc. in the United States and other countries.

Prerequisites

 

This hands-on lab assumes you have some basic knowledge of, or programming experience in, the following technologies:

You will need to refer to the accompanying presentation 5542_restwebservice.pdf during the lab.


System Requirements

 

Software Needed For This Lab

 

Please install the following set of software. If you have any questions on installation, please feel free to send questions to the lab forum mentioned below.

Please refer to Exercise 0 of this lab for the guidance on how to install these.


Notations Used in This Documentation

 

Lab Exercises

 

Additional Resources

 

Where To Go For Help

 

Exercise 0: Install and Configure Lab Environment

 

In addition to the contents of this lab's zip file you should have the following installed on your computer:

After installing Firefox we need to install the Poster add-on, which will enable us to send any kind of HTTP requests to our RESTful service and see the raw responses.

  1. If Firefox is not already running, start it.
  2. When Firefox has started, select Tools->Add-ons from the main menu.
  3. In the Add-ons dialog enter "Poster" into the search field and hit Enter.
  4. Poster should appear as the first add-on in the list. Click Add to Firefox.
  5. In the next dialog click Install Now.
  6. After the installation is complete, restart Firefox to complete the changes. If the Add-on dialog pops up after the restart, close it.
  7. You should see the Poster icon in the Firefox statusbar.

We also need to install the Maven plugin into the NetBeans IDE. Here is how to do this:

  1. If NetBeans is not already running, start it.
  2. When NetBeans has started, select Tools->Plugins and select "Maven" in the Available Plugins tab.

  3. Click the Install button.
  4. In the following dialog accept the license and click Install.
  5. After the installation completed, click Finish and close the Plugins dialog.

To make sure the Maven NetBeans plugin is using the right version of Maven:

  1. In NetBeans select Tools->Options (on Mac OS select NetBeans ->Preferences) and switch to Miscellaneous->Maven tab.
  2. Set the patch to your Maven installation in External Maven Home field.

And we need to ensure that GlassFish v2 is registered as a server in NetBeans:
  1. In NetBeans click on the Services tab.

  2. Now expand the Servers node in the Services tab (if possible). If you don't see GlassFish v2 under that node, right-click it and choose Add Server.

  3. In the Add Server Instance dialog that will pop up, select GlassFish V2 and click Next.

  4. In the next dialog screen set the Platform Location to your GlassFish installation directory. Once you do it, Register Local Default Domain should be selected automatically. Click Finish.

Exercise 1: Hello world! (30 minutes)

 

This excercise provides a quick introduction into the basics of the JAX-RS/Jersey programming model. For more details, please see the Jersey Tutorial available on the web.

In this lesson we are going to build a very basic "Hello world" type of RESTful web service. It will provide an introduction to how you can quickly get started building RESTful sevices using Jersey. You will see the basic concepts of the JAX-RS/Jersey programming model.


Background Information

 

What are RESTful Web Services?

 

RESTful web services are services that are built to work best on the web.

Representational State Transfer (REST) is an architectural style that specifies constraints, such as the uniform interface, that if applied to a web service induce desirable properties, such as performance, scalability, and modifiability, that enable services to work best on the Web. In the REST architectural style, data and functionality are considered resources, and these resources are accessed using Uniform Resource Identifiers (URIs), typically links on the web. The resources are acted upon by using a set of simple, well-defined operations. The REST architectural style constrains an architecture to a client-server architecture, and is designed to use a stateless communication protocol, typically HTTP. In the REST architecture style, clients and servers exchange representations of resources using a standardized interface and protocol. These principles encourages RESTful applications to be simple, lightweight, and have high performance.

RESTful web services typically map the four main HTTP methods to the operations they perform : create, retrieve, update, and delete. The following table shows a mapping of HTTP methods to the operations they perform.

HTTP Method      Operations Performed
GET Get a resource
POST Create a resource or other operations as it has no defined semantics
PUT Create or update a resource
DELETE Delete a resource

Introducing Project Jersey

 

Project Jersey is an open source, production quality reference implementation for JSR 311: JAX-RS: The Java API for RESTful Web Services. It implements support for the annotations defined in JSR-311, making it easy for developers to build RESTful web services with Java and the Java VM. Jersey also adds additional features not specified by the JSR.

The goals and philosophy of Project Jersey are to:

In this exercise we're going to see what it takes to build a new RESTful web service from scratch using Jersey.

NOTE: To be able to provide clear steps, we are using NetBeans to develop this lab. Anyway, all that we are going to show can easily be achieved without NetBeans, using a simple text editor or some other IDE. To demonstrate this, for operations other than creating and editing java files we provide simple instructions on how to do the same thing directly on the command line.

Steps to Follow

 

Step 1: Creating a New Project

  1. If NetBeans is not already running, start it.
  2. When NetBeans has started, select File->New Project.
  3. In the New Project wizard, select Maven->Maven Project and click Next.

  4. Expand "Archetypes from remote Maven Repositories", select "Jersey Archetype: Web App with WAR packaging" and click Next.

  5. In the next screen of the wizard, name the project as "jerseyservice" and click the Finish button.

    NOTE: If this is the first time you are creating a new project based on Jersey maven archetype, it may take more than a minute to create the new project after hitting the Finish button. During this time maven automatically downloads all the necessary dependencies.

Without the NetBeans IDE

All that we've done so far can easily be done without NetBeans. The steps are equivalent to executing the following on the command line in the directory where you want to create the new project:

prompt> mvn archetype:generate -DarchetypeCatalog=http://download.java.net/maven/2
    -DinteractiveMode=false -DarchetypeArtifactId=jersey-quickstart-webapp
    -DarchetypeGroupId=com.sun.jersey.archetypes
    -DgroupId=mycompany.com -DartifactId=jerseyservice -Dpackage=com.mycompany

Step 2: Building the Project

Now that the new project is created, let's build it.

  1. In NetBeans right-click on jerseyservice project - i.e. the new project we've just created - in the Projects tab and choose Build in the pop-up menu.

Since this is the very first time we are building a project depending on Jersey libraries, the build may take several minutes. So, in the meantime, we will go ahead to the step 3 and explore the project structure.

Without the NetBeans IDE

To build the project without NetBeans you can execute the following in the project root directory:

prompt> mvn install

Step 3: Exploring the Project Structure

Let's look more closely at how our new project looks like.

  1. Expand the Web Pages node and open index.jsp by double-clicking on it. It is a very simple page serving as a starting point for the web application the new project represents. It contains a link to a sample RESTful web service - we call it a resource - that got generated as part of this project, and also a link to the Project Jersey web site.
  2. Under Web Pages->WEB-INF open the web.xml file for editing by right-clicking on it and selecting Edit from the pop-up menu. This file is a standard web application deployment descriptor.

    TROUBLESHOOTING: In case the web.xml file opened in design mode (this can happen if you double-click it instead of right-clicking and choosing Edit), switch to XML editing mode by clicking XML in the editor window.

  3. Let's turn on the line numbers in the NetBeans editor to help us navigate through the source code. This can be done by right-clicking on the grey vertical bar on the left side of the editor window and choosing Show Line Numbers in the pop-up menu.
  4. Let's look at the interesting portion of the web.xml file:
     3     <servlet>
     4         <servlet-name>Jersey Web Application</servlet-name>
     5         <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
     6         <init-param>
     7             <param-name>com.sun.jersey.config.property.packages</param-name>
     8             <param-value>com.mycompany.jerseyservice</param-value>
     9         </init-param>
    10         <load-on-startup>1</load-on-startup>
    11     </servlet>
    12     <servlet-mapping>
    13         <servlet-name>Jersey Web Application</servlet-name>
    14         <url-pattern>/webresources/*</url-pattern>
    15     </servlet-mapping>
    

    Lines 4 and 5 configure com.sun.jersey.spi.container.servlet.ServletContainer as the servlet class. This class is an internal class from the Jersey framework that can handle the HTTP requests coming from the clients and route the requests to the implementations of our resources. It knows where too look for the resource classes from the value of com.sun.jersey.config.property.packages property. This property is set on lines 7 and 8 of the deployment descriptor to com.mycompany.jerseyservice, so Jersey will automatically look for the resource classes in that package and all of the subpackages.

    Lines 12 to 15 then define a mapping of the servlet to a URL pattern. It tells the server, request to which URLs should be delegated to the Jersey servlet. In this case it is all URLs starting with /webresources/ right after the application context URL prefix.

  5. Expand Source Packages->com.mycompany.jerseyservice and open MyResource.java by double-clicking on it. This is a simple example of a JAX-RS/Jersey resource class. It is a very simple class, just having a single method returning a string. What turns it into a resource are the annotations.
     8 /** Example resource class hosted at the URI path "/myresource"
     9  */
    10 @Path("/myresource")
    11 public class MyResource {
    12 
    13     /** Method processing HTTP GET requests, producing "text/plain" MIME media
    14      * type.
    15      * @return String that will be send back as a response of type "text/plain".
    16      */
    17     @GET
    18     @Produces("text/plain")
    19     public String getIt() {
    20         return "Hi there!";
    21     }
    22 }

    The @Path annotation on line 10 indicates this class is a resource available at path /myresource relative to the application servlet URL. The method of this class is annotated by two annotations (lines 17 and 18). The first one - @GET - indicates this method should be used to handle HTTP GET requests, the second one - @Produces - declares the MIME type of the data the method produces (text/plain in this case).

  6. Under Project Files you can see pom.xml. That is the file containing the maven project description. It contains references to all the dependencies based on which maven knows which jars need to be downloaded to build or test the project. More on the pom.xml file format can be found in maven documentation available from maven.apache.org.
  7. The file settings.xml is also maven-specific and is out of scope of this lab.

Step 4: Running the Project

Now that we know how the project looks like and why, let's try to run it. To run the project, we first need to configure it to use GlassFish V2 as the deployment server:

  1. In NetBeans, right-click on the project and choose Properties (at the end of the pop-up menu).
  2. In the Project Properties dialog select the Run category and set the Server field to GlassFish V2. Also note, the Context Path is set to /jerseyservice.
To run the project:
  1. Right-click on the project and choose Run from the pop-up menu. NetBeans will create a war file containing the compiled project, start GlassFish and use it for deploying the war file. A web browser should be started automatically, once the whole process is done, pointing to http://localhost:8080/jerseyservice/. This displays the index.jsp from our project in the browser.
  2. "Jersey resource" link points to our resource (MyResource). If we click on it, the browser will automatically send an HTTP GET request to a given URL which will be handled by our resource class - i.e. getIt() method will be called which returns a plain text resonse "Hi there!". So, let's click on the link to see if it really works.

Without the NetBeans IDE

To run the project without NetBeans you can execute the following in the project root directory:

prompt> mvn install
prompt> asadmin start-domain
prompt> asadmin deploy target/jerseyservice.war

Step 5: Making Modifications

Let's see how we can customize the project a little bit:

  1. Rename MyResource.java to HelloResource.java by right-clicking on it in the Projects view and choosing Refactor->Rename from the pop-up menu. Type the new name into the dialog and click Refactor.
  2. Open HelloResource.java (if not open yet) and change the URI template (in the @Path annotation) for the resource from /myresource to /hello. Update the comment as well.
  3. Rename the getIt() method to getXML(), change the produced MIME type to text/xml and let the method return "<greeting>Hello world!</greeting>". Update the comments as well. Here is how the resulting class should look like:
    /** Example resource class hosted at the URI path "/hello"
     */
    @Path("/hello")
    public class HelloResource {
    
        /** Method processing HTTP GET requests, producing "text/xml" MIME media
         * type.
         * @return String that will be send back as a response of type "text/xml".
         */
        @GET
        @Produces("text/xml")
        public String getXML() {
            return "<greeting>Hello world!</greeting>";
        }
    }
  4. Since we changed the URI template, we need to update the link to our resource in index.jsp. Open that file and replace line 4 with:
    <p><a href="webresources/hello">Hello resource</a>
  5. Run the application again - NetBeans will build and re-deploy it. We can see it now uses the new URI and the browser recognizes the MIME type of the returned value as XML.


Summary

 

In this exercise, we explained the basic concepts of REST architectural style and JAX-RS/Jersey programming model. You saw what it takes to create a simple RESTful web application using Jersey.

So far, we have been using only the very basic functionality of the Jersey framework. Our resource supported just a single HTTP method - GET, and it did not do much - returned a hardcoded value. In the following exercise we will explore some more advanced server-side features.

 

Exercise 2: Advanced JAX-RS/Jersey features (65 minutes)

 

In this exercise we are going to turn the project we developed in the last exercise into a web application for manipulating system properties. This will allow us to show some of the more advanced JAX-RS and Jersey features. We will do this by adding a new resource class that will contain methods for handling updates to the system properties as well as returning their representations in the form of plain text, XML and even JSON.

Before we start coding, it is important to think about the structure of the URLs we want to expose. Choosing the right URL structure is quite important for RESTful web services.

For this exercise, we will use the following URL structure:

URL template Operation Description
/properties GET List of all system properties
/properties/{property} GET Returns a representation of a property named {property}
PUT Adds/updates a property named {property}
DELETE Removes a property named {property}

Steps to Follow

 

Step 1: Creating a New Resource

Make sure you have the project from the last exercise open in NetBeans. If not, follow these steps to open it:

  1. In NetBeans, select File->Open Project and browse to the jerseyservice directory.

  2. Click Open Project.

Let's create a new resource for system properties:

  1. In NetBeans, create a new class named PropertyResource by right-clicking on Source Packages->com.mycompany.jerseyservice and choosing New->Java Class from the pop-up menu.
  2. In the New Java Class dialog, set the name to PropertyResource and click finish.
  3. Attach @Path("/properties") annotation to the class.
  4. NetBeans will indicate there is an error on that line. That is because the Path annotation needs to be imported. Click on the lightbulb next to that line and click on "Add import for javax.ws.rs.Path".
  5. Here is how the code should look like so far:
    package com.mycompany.jerseyservice;
    
    import javax.ws.rs.Path;
    
    @Path("/properties")
    public class PropertyResource {
    
    }
  6. Now it will get a bit more interesting. We will add a new method that will handle HTTP GET requests and return plain text response containing the name-value pair for the given system property specified in the last part of the URL. I.e. we want to be able to get this method invoked for any URL of the following form:
    http://localhost:8080/jerseyservice/webresources/properties/{property}
    where {property} is the name of the property we want to retrieve. Here is how such method looks like:
        @GET
        @Path("/{property}")
        @Produces("text/plain")
        public String getProperty(@PathParam("property") String propertyName) {
            return propertyName + " = " + System.getProperty(propertyName);
        }
    
    Add this method to the PropertyResource class and fix all imports by clicking on the lightbulbs and following the hints.

    As you can see from the code above, you can have variables surrounded by curly braces as part of the path template specified in the @Path annotation. These are then accessible by annotating any method parameter using the @PathParam annotation. Jersey then automatically passes the value of that variable into such annotated method parameter.

  7. The whole class should now look like this:
    package com.mycompany.jerseyservice;
    
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    
    @Path("/properties")
    public class PropertyResource {
        @GET
        @Path("/{property}")
        @Produces("text/plain")
        public String getProperty(@PathParam("property") String propertyName) {
            return propertyName + " = " + System.getProperty(propertyName);
        }
    }
    
  8. Run the project.
  9. The following URL should retrieve a value of "java.home" system property:
    http://localhost:8080/jerseyservice/webresources/properties/java.home
    Let's see if it works. Copy & Paste it into the browser's address bar and hit Enter. You should see your Java home directory path.
  10. Now, what if we enter a URL containing an invalid property name - i.e. a system property name that does not exist? Our method handles all requests conforming to the template. It does not check whether a given property exists. So, the result will be the same as if the system property did exist and its value was null. Try it for yourself by entering the following URL into the browser as an example:
    http://localhost:8080/jerseyservice/webresources/properties/invalid
    The resulting response will be:
    invalid = null
  11. Let's make the code more robust by checking if the property exists and returning an error if it doesn't. Jersey makes it easy to return errors by defining Java exceptions corresponding to the standard HTTP error codes your application can throw. One of those is com.sun.jersey.api.NotFoundException that we will be using in this case. We will add a new method to implement the check and call it from our GET method. The code will look like this (make sure you fix the imports after updating the code):
        private static void checkPropertyExists(String propertyName) {
            if (!System.getProperties().containsKey(propertyName)) {
                throw new NotFoundException("Error - Property Not Found: " + propertyName);
            }
        }
    
        @GET
        @Path("/{property}")
        @Produces("text/plain")
        public String getProperty(@PathParam("property") String propertyName) {
            checkPropertyExists(propertyName);
            return propertyName + " = " + System.getProperty(propertyName);
        }
    
  12. Run the application and try the "invalid" URL again. You will see the error message.
  13. To verify that the right status code is returned when the property is not found, we can use the Poster Add-on for Firefox. To bring up the Poster window, click on the Poster icon in the Firefox status bar.
  14. Let's first type in the valid URL (i.e. the URL for the java.home property) and click the GO button next to the GET action. This sends the HTTP GET request to a given URL (similarly to when you enter this URL into a browser.
  15. We can see the response as well as the HTTP status code. For this operation the status code is 200 = OK.
  16. Close the Response dialog and try to change the URL to the "invalid" one:
    http://localhost:8080/jerseyservice/webresources/properties/invalid
    After clicking GO, you can see that not only the error message gets returned, but as Poster shows also the status code has the right value - 404 = Not found.

Step 2: Supporting More MIME Types

Let's now try to extend our resource to be able to return several different representations of a system property, based on the requested MIME type. We want to support XML and JSON in addition to the currently supported plain text.

Jersey makes this very easy by providing a support for JAXB beans to represent resources using both XML and JSON formats. All you need to do is return a JAXB bean from the GET operation and Jersey takes care of translating it into the right MIME type the client expects.

So, let's add this functionality to our service:

  1. First we have to create a new JAXB bean to represent a single system property. To do this, create a new Java class named PropertyBean in the com.mycompany.jerseyservice package.
  2. Annotate the class with the JAXB annotation @XmlRootElement.
  3. Add two private String fields - name and value, and two public constructors - one with no arguments and the other one taking name and value as arguments and setting the private fields.
  4. Add setters and getters for the two private fields.
  5. The resulting class should look like this:
    package com.mycompany.jerseyservice;
    
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement
    public class PropertyBean {
        private String name;
        private String value;
    
        public PropertyBean() {
        }
    
        public PropertyBean(String name, String value) {
            this.name = name;
            this.value = value;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    }
    
  6. Now that we have the JAXB bean representing a system property, let's add another GET method - named getJAXBProperty() to our resource. This method will make use of the PropertyBean we've created to support text/xml, application/xml and application/json MIME types:
    package com.mycompany.jerseyservice;
    
    import com.sun.jersey.api.NotFoundException;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    
    @Path("/properties")
    public class PropertyResource {
        private static void checkPropertyExists(String propertyName) {
            if (!System.getProperties().containsKey(propertyName)) {
                throw new NotFoundException("Error - Property Not Found: " + propertyName);
            }
        }
    
        @GET
        @Path("/{property}")
        @Produces("text/plain")
        public String getProperty(@PathParam("property") String propertyName) {
            checkPropertyExists(propertyName);
            return propertyName + " = " + System.getProperty(propertyName);
        }
    
        @GET
        @Path("/{property}")
        @Produces({"text/xml", "application/xml", "application/json"})
        public PropertyBean getJAXBProperty(@PathParam("property") String propertyName) {
            checkPropertyExists(propertyName);
            return new PropertyBean(propertyName, System.getProperty(propertyName));
        }
    }
    
  7. Let's run the project and test it using the Poster Firefox Add-on. In the poster, enter the URL of java.home property.
  8. Besides this, we will also need to configure the Poster to accept only a certain MIME type. To do that, click on the GO button next to the Headers field. This will allow us to add additional attributes to the HTTP request header.
  9. In the Request Header dialog put "Accept" into the Name field, "text/plain" into the Value field, click Add/Change and close the dialog. This will cause the Poster to specifically request a response of the text/plain MIME type.
  10. Now click the GO button next to the GET action. You will see the Poster will get the standard response produced by the getProperty() method of our resource we already saw in the previous steps.
  11. Let's see what happens if we change the MIME type to some of the new ones we added by the way of adding the getJAXBProperty() method. Close the Response window and click GO next to the Headers field again.
  12. This time we will set the Accept attribute to application/xml. To do this, click on the Accept attribute in the Request Header dialog, modify its value by typing application/xml into the Value field and clicking the Add/Change button.
  13. Close the Request Header dialog and send the request by clicking the GO button next to the GET action. Now you see the response contains an XML representation of the system property
  14. Similarly, you can modify the Accept header attribute to retrieve the JSON representation of the property (by setting the value to application/json).

Step 3: Implementing a Message Body Writer

To further improve our code, it would be nice if we could have just one method handling the GET requests, instead of having the two separate ones as we are having currently (one for the plain text and the other one for XML and JSON).

By default, Jersey does not know how to generate a plain text representation of a JAXB bean - i.e. our PropertyBean class in this case. However, we can plug this in using the SPI Jersey provides. What we need to do is to create a "Provider" class implementing MessageBodyWriter interface and handling the serialization of PropertyBean plain text representation.

Here is how to do this:

  1. Create a new Java class in the com.mycompany.jerseyservice package and name it PropertyWriter.
  2. Copy the following code into the Java file:
    package com.mycompany.jerseyservice;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Type;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.MultivaluedMap;
    import javax.ws.rs.ext.MessageBodyWriter;
    import javax.ws.rs.ext.Provider;
    
    @Provider
    public class PropertyWriter implements MessageBodyWriter<PropertyBean> {
        public boolean isWriteable(Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType) {
            return MediaType.TEXT_PLAIN_TYPE.equals(mediaType) &&
                    PropertyBean.class.isAssignableFrom(type);
        }
    
        public long getSize(PropertyBean t, Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType) {
            return -1;
        }
    
        public void writeTo(PropertyBean t, Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType,
                MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
                throws IOException, WebApplicationException {
            entityStream.write(t.toString().getBytes());
        }
    }
    
    This class will automatically be picked up by the Jersey framework, since it is annotated by the @Provider annotation. When creating a reponse, Jersey will call the isWritable() method of this provider to see if it can convert a given type of object into a stream of a given MIME type. The writeTo() method is then called by the framework to perform the conversion.
  3. As you can see, the writter we have created in the last step makes use of the toString() method to convert the PropertyBean to text. Let's override the toString() method in the PropertyBean to make sure we get the desired output. Add the following code to the PropertyBean class:
        @Override
        public String toString() {
            return name + " = " + value;
        }
    
  4. Finally, we can remove getProperty() method from the PropertyResource class and add "text/plain" MIME type to the list of MIME types getJAXBProperty() method produces. The whole class should now look as follows:
    package com.mycompany.jerseyservice;
    
    import com.sun.jersey.api.NotFoundException;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    
    @Path("/properties")
    public class PropertyResource {
        private static void checkPropertyExists(String propertyName) {
            if (!System.getProperties().containsKey(propertyName)) {
                throw new NotFoundException("Error - Property Not Found: " + propertyName);
            }
        }
    
        @GET
        @Path("/{property}")
        @Produces({"text/plain", "text/xml", "application/xml", "application/json"})
        public PropertyBean getJAXBProperty(@PathParam("property") String propertyName) {
            checkPropertyExists(propertyName);
            return new PropertyBean(propertyName, System.getProperty(propertyName));
        }
    }
    
  5. Run the application and use Poster to send a GET request with Accept:text/plain in the header - similarly to how we did it for other MIME types previously. Observe the reception of the correct reponse.

Step 4: List of Properties

Now that we are able to retrieve various representations (text, XML, JSON) of a single property, let's see how to return a list of all properties:

  1. Add another GET method to the PropertyResource class. Let's name this method getProperties(). This time we won't add the @Path annotation, since we want this method to be invoked for the path /properties itself (this path is already attached to the whole class so no need to put it on the method). The method will return a list of properties (sorted by property names - using TreeSet) - i.e. List<PropertyBean> and take no parameters:
        @GET
        public List<PropertyBean> getProperties() {
            List<PropertyBean> result = new ArrayList<PropertyBean>(System.getProperties().size());
    
            for (Object name : new TreeSet(System.getProperties().keySet())) {
                result.add(new PropertyBean((String) name, System.getProperty((String) name)));
            }
    
            return result;
        }
    
  2. Note the method is not even annotated by the @Produces annotation. This is because instead of duplicating this annotation on both GET methods, we will simply move the annotation to the class level - i.e. remove it even from the first GET method and attach it to the class instead.
  3. The updated PropertyResource class should look as follows:
    package com.mycompany.jerseyservice;
    
    import com.sun.jersey.api.NotFoundException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.TreeSet;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    
    @Path("/properties")
    @Produces({"text/plain", "text/xml", "application/xml", "application/json"})
    public class PropertyResource {
        private static void checkPropertyExists(String propertyName) {
            if (!System.getProperties().containsKey(propertyName)) {
                throw new NotFoundException("Error - Property Not Found: " + propertyName);
            }
        }
    
        @GET
        @Path("/{property}")
        public PropertyBean getJAXBProperty(@PathParam("property") String propertyName) {
            checkPropertyExists(propertyName);
            return new PropertyBean(propertyName, System.getProperty(propertyName));
        }
    
        @GET
        public List<PropertyBean> getProperties() {
            List<PropertyBean> result = new ArrayList<PropertyBean>(System.getProperties().size());
    
            for (Object name : new TreeSet(System.getProperties().keySet())) {
                result.add(new PropertyBean((String) name, System.getProperty((String) name)));
            }
    
            return result;
        }
    }
    
  4. Jersey framework is able to transform a list of JAXB beans to XML and JSON, however not to text/plain. So, we need to implement another message body writer - this time for a list of properties. To do this, create a new Java class named PropertyListWriter and copy & paste the following code into it:
    package com.mycompany.jerseyservice;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Type;
    import java.util.List;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.MultivaluedMap;
    import javax.ws.rs.ext.MessageBodyWriter;
    import javax.ws.rs.ext.Provider;
    
    @Provider
    public class PropertyListWriter implements MessageBodyWriter<List<PropertyBean>>{
        public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
                MediaType mediaType) {
            return MediaType.TEXT_PLAIN_TYPE.equals(mediaType) && List.class.isAssignableFrom(type);
        }
    
        public long getSize(List<PropertyBean> t, Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType) {
            return -1;
        }
    
        public void writeTo(List<PropertyBean> t, Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType,
                MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
                throws IOException, WebApplicationException {
            StringBuffer result = new StringBuffer();
            for(PropertyBean propertyBean : t) {
                result.append(propertyBean.toString()).append('\n');
            }
            entityStream.write(result.toString().getBytes());
        }
    }
    
  5. Let's run the application again and browse to the following URL to see the list of all system properties:

    http://localhost:8080/jerseyservice/webresources/properties

  6. You can also use Poster to try other MIME types.

Step 5: Using the MVC Feature of Jersey

The resource we've developed so far is quite hard to play with in a web browser. It would be nice to return an HTML representation as well and do that in such a way that the list of properties would provide direct links to the individual property resources. We can nicely achieve this by the MVC feature of Jersey.

First we will create two simple JSP pages - one for representing the list of properties and the other one for representing a single property:

  1. Right-click on Web Pages folder of the project and choose New->JSP.
  2. Name it "properties" and click Finish.
  3. This new JSP will be used to represent the list of properties. Jersey automatically makes the "model" (i.e. data to be rendered by this JSP) accessible as a variable named it. In this case it will contain the list of system properties. Let's update this new JSP to render the list of properties properly. First, right under the line with <%@page> tag, we will declare a usage of the core JSP tag library as follows:
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
    This will enable us to use tags from this library prefixed by "c" namespace (as defined in the prefix attribute in the above tag). The tag we are interested in is forEach that will enable us to iterate through the elements of the property list.
  4. Let's change the title of the JSP to "System Properties":
            <title>System Properties</title>
    
  5. Update the heading with the same:
            <h1>System Properties</h1>
    
  6. Finally, here is how we can iterate throught the list of properties (passed in by Jersey in the above mentioned variable named it) and add links to the individual property resources.
            <ul>
                <c:forEach var="p" items="${it}">
                    <li><a href="properties/${p.name}">${p.name}</a></li>
                </c:forEach>
            </ul>
    
  7. The whole page should now look like this:
    <%@page contentType="text/html" pageEncoding="UTF-8"%>
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
       "http://www.w3.org/TR/html4/loose.dtd">
    
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>System Properties</title>
        </head>
        <body>
            <h1>System Properties</h1>
    
            <ul>
                <c:forEach var="p" items="${it}">
                    <li><a href="properties/${p.name}">${p.name}</a></li>
                </c:forEach>
            </ul>
        </body>
    </html>
    
  8. Create another new JSP page and name it "property" (this one will be used to represent a single property).
  9. Model for this page will be the PropertyBean (representing the property) itself. Change the title of the page to "System Property - ${it.name}":
            <title>System Properties - ${it.name}</title>
    
  10. Change the heading to the property name itself - i.e. ${it.name}.
  11. Under the heading the only text we will put in is the property value - i.e. ${it.value}
  12. The whole JSP should look as follows:
    <%@page contentType="text/html" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
       "http://www.w3.org/TR/html4/loose.dtd">
    
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>System Properties - ${it.name}</title>
        </head>
        <body>
            <h1>${it.name}</h1>
            ${it.value}
        </body>
    </html>
    
  13. Now we need to add GET methods to our PropertyResource to return the HTML representations of the list of properties and an individual property. Open PropertyResource class by double-clicking on it in the Project Explorer in NetBeans.
  14. Right after getJAXBProperty() method, add another GET method named getHTMLProperty() with the same parameter as the getJAXBProperty() method plus annotated with @Produces("text/html") and returning Viewable from com.sun.jersey.api.view package.
  15. All we need to do in this method is to return a new instance of Viewable, passing the name of the JSP template for representing a single property as the first parameter, and the data/model itself (i.e. the PropertyBean object representing the requested property as the second parameter (to get this we will simply reuse the getJAXBProperty() method):
        @GET
        @Path("/{property}")
        @Produces("text/html")
        public Viewable getHTMLProperty(@PathParam("property") String propertyName) {
            return new Viewable("/property", getJAXBProperty(propertyName));
        }
    

    Make sure you fix imports (to import Viewable class).

  16. Now add another GET method (this time for the list of properties) named getHTMLProperties(). Same as the previous one, annotate it with @Produces("text/html") and make it return Viewable.
  17. In the body of the method we need to return a new instance of Viewable similarly to the previous method:
        @GET
        @Produces("text/html")
        public Viewable getHTMLProperties() {
            return new Viewable("/properties", getProperties());
        }
    
  18. To make running the exercise more conveniently, we will make NetBeans open the browser at the right URL when running the project. To do this, Right-click on the project, choose Properties (at the end of the pop-up menu).
  19. In the Project Properties dialog select the Run category and set Relative URL to /webresources/properties - this will ensure that each time we run the application it will open the page with the list of properties.
  20. Let's run the project.
  21. You can see that the properties are now rendered into HTML - you can see a list of properties with the proper links.
  22. By clicking on a link you can see the HTML page for the individual property.

Step 6: Operations for Making Updates

Finally we will add operations to make updates to the system properties.

  1. Add the following method to the PropertyResource class:
        @PUT
        @Path("/{property}")
        public Response setProperty(@Context UriInfo uriInfo,
                @PathParam("property") String name, String value) {
            boolean isNew = !System.getProperties().containsKey(name);
    
            System.setProperty(name, value);
            PropertyBean result = new PropertyBean(name, value);
            if (isNew) {
                return Response.created(uriInfo.getAbsolutePath()).entity(result).build();
            } else {
                return Response.ok(result).build();
            }
        }
    

    Fix imports and make sure the Context class is imported from javax.ws.rs.core package.

    As you can see, this is a PUT method. The method receives the property name and value as parameters and adds/sets the system property. In this example you can see another way of building the response - instead of returning the entity directly, or throwing exceptions, you can directly build and return the Response object. We are using this to return the right status code based on whether a new property was added (results in status code 201 = Created), or an existing property was set (results in status code 200 = OK). The property itself gets returned in the body of the response. Also, when returning the "Created" status code, we need to pass in the URI of the newly created property. That URI is the same as the request URI, so we can retrieve that from the UriInfo object the method takes as a parameter.

    Now, let's look at the parameters more closely:

    • The first parameter is annotated by the @Context annotation - that causes the value of this parameter to be automatically injected by the Jersey framework. The value will contain information on the URI of the request that triggered this method.
    • The second parameter should look familiar - it is the one the value of which will automatically be extracted from the URI.
    • The last parameter - value will be populated by the body of the request.
    The parameter values injection makes the implementation of the PUT method quite simple.

  2. Another method we are going to add is the DELETE method. Copy & paste the following into the PropertyResource class:
        @DELETE
        @Path("/{property}")
        public void deleteProperty(@PathParam("property") String propertyName) {
            checkPropertyExists(propertyName);
            System.getProperties().remove(propertyName);
        }
    
    This method is very simple. It has no return value, so Jersey will automatically generate a response with the status code set to 204 = No content. Otherwise the code should be straightforward.
  3. That's it - let's run the application again.
  4. Open the following URL in the web browser to retrieve the list of properties:

    http://localhost:8080/jerseyservice/webresources/properties

  5. Now use Poster to add a new property named "aaa" with the value of "This is a property value". To do this, fill in the following into the URL field of the Poster window:
    http://localhost:8080/jerseyservice/webresources/properties/aaa
    Enter "This is a property value" into the "Content to Send" field, in the Actions dropdown box select PUT, and click GO next to it.
  6. In the response dialog observe that the right status code got returned, (i.e. 201) and also the Location header attribute contains the URI of the new property.
  7. Refresh the browser window containing the list of all system properties and you should see our new property "aaa" added to the beginning of the list.
  8. In the Poster, change the value in the "Context to Send" field to "This is an updated property value" and send another PUT request. Now you should see status code 200.
  9. Refresh the brower window and observe that the value of the property "aaa" got updated.
  10. Finally, let's send a DELETE request to the same URI by setting the Action in the Poster window to DELETE and clicking GO next to it.
  11. Observe that the right status code got returned - 204.
  12. You can see another identical DELETE request using Poster (by clicking the GO button again) and see this time we get 404 (i.e. Not Found) status code back.
  13. Refresh the browser window containing the list of all properties and see our "aaa" property is no longer appears listed.

This concludes the second exercise.

Summary

 

In this exercise, you saw how to create a RESTful web service using the features provided by JAX-RS and Jersey. You saw how a resource could produce data in more than one representation. You also saw how to map the resource methods to the HTTP methods of GET, PUT and DELETE.

For the development purpose we saw how we could use the web browser, or Poster to send various HTTP methods and see what the response would be.

In the following exercise, we will see how we can use the Jersey Client API to consume the services we've created so far..

 

Exercise 3: Consuming RESTful Web Services using Jersey Client API (25 minutes)

 

In this exercise we will see how to consume a RESTful web service using the Jersey Client API. We will see how easily we can send requests to a service and get back a response.

We will create a Java Swing Application, which will consume the jerseyservice we created in the previous exercise. Using this application we will be able to list the system properties, and update/add/delete a particular property.

In the previous exercise we have seen how to create a RESTful Web Service using maven. To show how you can use Jersey without Maven, in this exercise we will download the required Jersey jars manually and use them in an ant-based project.


Steps to Follow

 

Step 1: Creating a Client Project

In order to save some time, the lab provides a pre-built skeleton application, which already contains all of the UI components. The only thing we will be adding during this lab is the business logic. We will start by opening this pre-built project and walking through its structure a bit.

  1. From the NetBeans IDE, open jerseyclient project located in <lab_root>/restwebservice/exercises/exercise3.
  2. What got opened is a simple ant-based project implementing a Swing UI.

    The com.mycompany.jerseyclient package has three classes:
    • MainWindow - Implements the main window of the application containing a list box for listing all the properties and buttons to perform operations on the properties.
    • EditPropWindow - Implements a dialog window used to display/gather user input for a particular property.
    • DataProvider - A simple class containing methods that should perform the actual operations of retrieving, updating and deleting the properties - the UI components delegate to these methods - so this class is the only one we will need to touch.

    All these classes are reasonably documented, so you can browse through their code to find out more.

  3. Let's run the application to get a better idea of how it looks like by right-clicking on "jerseyclient" in the Projects tab and clicking on Run.
  4. The main window will appear. To get list of all properties, the main window calls DataProvider.getPropertyNames() method, which currently always returns hard-coded values - property1, property2 and property3. The same call is made when you click on Refresh button.
  5. Click on New button - the EditPropWindow dialog will appear. You can enter some name and value. Upon clicking OK, a call to DataProvider.setPropertyValue() is made passing the property name and value you entered. Currently this method does nothing. The application also does a refresh (by calling DataProvider.getPropertyNames() to update the list box content).
  6. Select a property from the list and click on Edit button. The same dialog will appear but this time the property name field is read-only. To get the current value of the selected property, the application calls DataProvider.getPropertyValue() passing the property name. This method currently always returns "dummy". Once user changes the value in the dialog and clicks OK, the application calls DataProvider.setPropertyValue() and does a refresh.
  7. Select a property from the list and click Delete. Application asks if you are sure - after clicking OK it calls DataProvider.deleteProperty() (which currently does nothing) and does a refresh.
  8. Exit the application by closing the main window.

Step 2: Adding the Required Libraries to the Classpath

Before we start properly implementing methods of the DataProvider class, we need to make sure we have the required Jersey jar files on the classpath. Every release of Jersey provides a snapshot of the dependencies page showing which libraries you need to depend on when using certain features of Jersey. For developers not using maven, it provides links for downloading the JAR files directly.

We will use the Jersey Client API. To unmarshall the data from XML we will use JAXB (similarly to using it in jerseyservice for marshalling to XML/JSON). As a result, we will need the following jar files for this exercise:

For your convenience, the needed files can be found in lib directory under the root of jerseyclient project.

Let's add them to the classpath of our project:
  1. Right-click on the Libraries node under the jerseyclient project and choose "Add JAR/Folder..."
  2. In the Add JAR/Folder dialog browse to <lab_root>/restwebservice/exercises/exercise3/jerseyclient/lib directory, select all jars in it and click OK.

    NOTE: As mentioned above, only jersey-bundle and jsr311-api jars are needed when running on JDK6. Please add all jars as suggested in case you are not sure which version of JDK you are running on.

Step 3: Implementing DataProvider Methods

Now we can finally get to the main topic of this exercise. We will use the Jersey Client API to properly implement methods of the DataProvider class.

  1. Open the DataProvider class for editing by double-clicking on it in the Projects tab.
  2. Jersey Client API defines WebResource class that makes it easy to invoke operations on web resources (such as PropertiesResource we developed in the previous exercise). Let's declare a static variable of type WebResource we will use to access the properties resource:
        private static WebResource propsResource = Client.create().resource(
                "http://localhost:8080/jerseyservice/webresources/properties");
    
  3. Now, before we start implementing the individual methods, let's copy the PropertyBean class from the jerseyservice project we developed in the previous exercise by right-clicking on it in the Projects tab, choosing Copy from the pop-up menu and then right-clicking on com.mycompany.jerseyclient package, choosing Paste->Refactor Copy and clicking Refactor in the Copy Class dialog.

    We will use this class for representing a single property. Since it is a JAXB bean, Jersey will be able to automatically unmarshall XML in server responses into its instances.

  4. Get back to the DataProvider class and replace getPropertyNames() method implementation with the following:
            List<PropertyBean> properties = propsResource.accept("application/xml")
                    .get(new GenericType<List<PropertyBean>>() {});
    
            String[] propNames = new String[properties.size()];
            int i = 0;
            for (PropertyBean property : properties) {
                propNames[i] = property.getName();
                i++;
            }
            return propNames;
    
    The call to the REST service is on the first line of this code. We are using the accept() method of WebResource class to indicate what media types our client accepts and then issue an HTTP GET request by calling get() method on the resource. We are indicating what type the response should be unmarshalled into by passing an instance of GenericType representing List<PropertyBean>. This ensures Jersey will try to process the server response using an appropriate reader (in this case JAXB) to construct a result of the requested type - so we can assign the returned result directly to a variable of List<PropertyBean>. The rest of the code is just constructing an array of property names from the list of property beans.
  5. Besides the get() method, WebResource defines methods corresponding to the remaining HTTP methods - most importantly put() and delete(). We can get to a particular sub-resource by calling path() method on a resource. So now it should be straightforward how to implement the remaining methods of DataProvider. Replace the deleteProperty() implementation with the following:
            propsResource.path(propertyName).delete();
    
  6. Replacing getPropertyValue() should also be straightforward:
            PropertyBean property = propsResource.path(propertyName)
                    .accept("application/xml").get(PropertyBean.class);
            return property.getValue();
    
  7. And finally the code for setPropertyValue() method:
            propsResource.path(propertyName).put(propertyValue);
    
  8. The whole class should look as follows:
    package com.mycompany.jerseyclient;
    
    import com.sun.jersey.api.client.Client;
    import com.sun.jersey.api.client.GenericType;
    import com.sun.jersey.api.client.WebResource;
    import java.util.List;
    
    /** Class that handles all the communication with the RESTful web service
     * for manipulating remote system properties.
     */
    public class DataProvider {
        private static WebResource propsResource = Client.create().resource(
                "http://localhost:8080/jerseyservice/webresources/properties");
    
        /** Sets the property of a given name to a given value.
         *
         * @param propertyName Name of the property to set or create.
         * @param propertyValue A new value of the property.
         */
        public static void setPropertyValue(String propertyName, String propertyValue) {
            propsResource.path(propertyName).put(propertyValue);
        }
    
        /** Retrieves the value of a given property.
         *
         * @param propertyName Name of the property to retrieve the value of.
         * @return value of the property.
         */
        public static String getPropertyValue(String propertyName) {
            PropertyBean property = propsResource.path(propertyName)
                    .accept("application/xml").get(PropertyBean.class);
            return property.getValue();
        }
    
        /** Deletes the property of a given name.
         *
         * @param propertyName Name of the property to delete.
         */
        public static void deleteProperty(String propertyName) {
            propsResource.path(propertyName).delete();
        }
    
        /** Retrieves names of all properties.
         *
         * @return array of names of the properties.
         */
        public static String[] getPropertyNames() {
            List<PropertyBean> properties = propsResource.accept("application/xml")
                    .get(new GenericType<List<PropertyBean>>() {});
    
            String[] propNames = new String[properties.size()];
            int i = 0;
            for (PropertyBean property : properties) {
                propNames[i] = property.getName();
                i++;
            }
            return propNames;
        }
    }
    
  9. Now we are done - run the application.
    TROUBLESHOOTING: If the application does not work, check if GlassFish V2 is running and that jerseyservice application has been deployed.
  10. Add a new property named "aaa". After clicking OK in the property dialog, you should see the property added in the list.
  11. Try changing the value of the property by selecting it and clicking Edit. The next time you select the property and click Edit you should see the new value.
  12. Delete the property by clicking Delete. After confirming, observe the property is no longer in the list.
  13. You can play by modifying the properties from the web browser and from the Swing client simultaneously.
    NOTE: Don't modify or delete any of the existing properties. Play only with the ones you added.

This concludes the third exercise.


Summary

 

In this exercise, you saw how the Jersey Client API could be used to consume a RESTful Web Service. You have seen how to:

  1. Get a handle to a web resource.
  2. Build request to a web resource using the path() method.
  3. Send HTTP requests using the get() , put() and delete() methods.
  4. Specify the mime-type in which you want to get the response to a request, using the accept() method.

Congratulations! You have successfully completed LAB-5542: Project Jersey Hands-on lab.

You can send technical questions via email to the public Jersey users alias: