Steven Verborgh

Just another weblog

Easy restfull JAX-RS webservices and extended WADL on Glassfish v3 (using ant.)

Nov 21, 2009

REST of Representational State Transfer seem to be the latest buzzword for some time in the world of webservices. Creating them, though not as easy as with Rails, using JAX-RS and the Jersey Reference implementation is not that difficult.

In this short introduction we will look into using EJB 3.1 lite in a web application to create a very simple dummy webservice. Since it would be enterprise enough on itself, we will also extend the default WADL file with the help of some ant tasks and doclets. To finish it up we will also generate a client based on the WADL and XSD. You will also see the advantages of using JAXB with JAX-RS.

For this little demo I used glassfish v2 b73 and Netbeans 6.8 Beta. The default Jersey implementation is OK, it's just not as bleeding edge as glassfish itself. For the generation of the extended WADL, we need the Jersey WADL ResourceDoclet. This isn't supplied with GFv3 or Netbeans, so you will need to download it from here, also, it needs xerces, sou you will need to find that somewhere. You can find out which version your GFv3 installation has by looking at maven pom.properties of the jersey-gf-bundle.jar, which you can find in the modules dir of your Glassfish installation.

So, we are going to look at a restful webservice that is going to generate XML. As you might know, we have the perfect JSR specification for that in the form of JAXB. The needed libraries are already available in Netbeans 6.8. And as you might have guessed, returning JAXB objects from a JAX-RS method is going to integrate naturally and take some strain from our shoulders. No need to marshal and unmarshal by ourself.

The XML we want to generate looks like this when we ask for the list:

<products xmlns="http://verborgh.be/demo/v1">
  <product id="1">
    Product1
  </product>
</products>
or like this when we recieve of send when updating or showing an individual product:
<product id="1" xmlns="http://verborgh.be/demo/v1">
  Product1
</product>

When thinking about this XML in JAXB, you notice you will need 2 classes. A first class representing the products tag and a second representing the individual product tags. This means 2 complex types and 2 elements. A complex type being the definition of the XML structure, and the elements being the effective tag that "references" the complex types.

If you would look at the XSD (,and I will show you how to generate it from the objects,) it would look like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema elementFormDefault="qualified" version="1.0" targetNamespace="http://verborgh.be/demo/v1" xmlns:tns="http://verborgh.be/demo/v1" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="product" type="tns:productType"/>

  <xs:element name="products" type="tns:productsType"/>

  <xs:complexType name="productType">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="id" type="xs:long"/>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="productsType">
    <xs:sequence>
      <xs:element ref="tns:product" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

The generation of complex types is controlled by using @XmlTypes on your classes. These types will also be important when we start generating clients, or when we want to use a complete WADL with representation. Our generated XML needs a root element. In the case of the listing this will be products, in case of a single product this wil be product. These elements are controlled by the @XmlRootElement. The nested product element of the products container is dependent on the @XmlElement annotation.

So to start, out Product class with the necessary annotations:

package be.verborgh.demo;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;

/**
 *
 * @author verborghs
 */
@Entity
@NamedQuery(name="Product.findAll", query="SELECT p FROM Product p")
@XmlRootElement(name="product")
@XmlType(name="productType")
@XmlAccessorType(XmlAccessType.FIELD)
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @XmlAttribute(name="id")
    private Long id;
    @XmlValue
    private String name;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Product)) {
            return false;
        }
        Product other = (Product) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "be.verborgh.demo.Product[id=" + id + "]";
    }

}

Notice that we also use the @XmlValue on the property name indicate that this will be the value within the product tag. We could have used @XmlElement annotation if we wanted an extra subtag, this is useful in the case where the product has more properties than just a name.
We also made sure the id is available as an attribute.
It should be noted that we could have done without the annotation it we where happy with the default WADL file, and just accept the default generated XML tags.
I prefer placing my annotations on the fields so I needed to override the defaults by using the @XmlAccessorType annotation.

Just to make spice it up a notch, I also added some JPA annotations that will allow me to persist the objects and give you a working example. I'm not going to explain them here, you will have to look their meaning up all by yourself.

Next up is out Products class:

package be.verborgh.demo;

import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

/**
 *
 * @author verborghs
 */
@XmlRootElement(name="products")
@XmlType(name="productsType")
@XmlAccessorType(XmlAccessType.FIELD)
public class Products {
    @XmlElement(name="product")
    private List<Product> products;

    public static Products create(List<Product> productlist) {
        Products products = new Products();
        products.products = productlist;
        return products;
    }

    public List<Product> getProducts() {
        return products;
    }
}

This class will represent the products-tag. Everyting here is as you would expect. You might notice the @XmlElement annotation that defines the name as 'product'. If we don't do this, the name of the subtags would be 'products', based on the name of the field.
We also have a factory method that wil allow us to easily create a Products instance that contains a List of Product objects.

One last thing: I like clean cut wound by razor sharp xml, and since we are going to generate an XSD and make our webservice unnesacary complex with the WADL thing, we might as well define the namespace in out package-info.java. So here you go:

@XmlSchema(namespace = "http://verborgh.be/demo/v1", elementFormDefault = XmlNsForm.QUALIFIED)
package be.verborgh.demo;

import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

Ok, these classes and their annotations are enough to allow us to generate an XSD later on. Now the second thing that needs work is the Restful webservice itself.. . you'll see this will be the easy part.

So since you got this far, you probably are a programmer an without further due I present to you: the webservice itself. Lets look at the code and go through it step by step.

package be.verborgh.demo;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

/**
 *
 * @author verborghs
 */
@Stateless
@Path("products")
public class ProductsService {

    @PersistenceContext
    private EntityManager em;

    /**
     * @response.representation.200.qname {http://verborgh.be/demo/v1}products
     * @response.representation.200.mediaType application/xml
     */
    @GET
    @Produces("application/xml")
    public Products index() {
        return Products.create(em.createNamedQuery("Product.findAll").getResultList());
    }

    /**
     * @request.representation.qname {http://verborgh.be/demo/v1}product
     * @request.representation.mediaType application/xml
     * @response.representation.201.qname {http://verborgh.be/demo/v1}product
     * @response.representation.201.mediaType application/xml
     * @response.param {@name Location}
     *                 {@style header}
     *                 {@type {http://www.w3.org/2001/XMLSchema}anyURI}
     *                 {@doc The URI where the user has been created.}
     */
    @POST
    @Produces("application/xml")
    @Consumes("application/xml")
    public Response create(Product product) {
        em.persist(product);
        return Response.created (
		          UriBuilder.fromPath("/products/{id}").build(product.getId())
			  ).entity(product).build();
    }

    /**
     * @response.representation.200.qname {http://verborgh.be/demo/v1}product
     * @response.representation.200.mediaType application/xml
     */
    @GET
    @Path("{id}")
    @Produces("application/xml")
    public Product read(@PathParam("id") Long id) {
        return em.find(Product.class, id);
    }

    /**
     * @request.representation.qname {http://verborgh.be/demo/v1}product
     * @request.representation.mediaType application/xml
     */
    @PUT
    @Path("{id}")
    @Consumes("application/xml")
    public void update(@PathParam("id") Long id, Product product) {
        em.merge(product);
    }

    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") Long id) {
        em.remove(em.find(Product.class, id));
    }
}

First of all, the class and its annotations.

@Stateless
@Path("products")
public class ProductsService {

In Java EE 6, some things have changed for the better. (There still is a long way to go, but still, better!). One of the things that got easier is deployement. For me personnaly there where times I threw up writing JPA in "JSF managed beans" because of all the boiler plate code needed to "just-fucking-execute-the-sql-and-commit-the-transaction" (Ok, I admit @Resource UserTransaction tx isn't that hard.) From now on, we can just put our EJB's in our webapplication just like we would do for normal classes, no need for ears, wars and jars ... one simple war!
Ow, and did you notice, we don't have an EJB interface? Yes, there is something called no-view EJB. Actually, in this case adding a interface didn't play nicely along with jersey. I am assuming it is just the way jersey resolves the beans, or maybe I should put my annotations on the interface, who knows! It doesn't matter, in this case we just want an easy Restful webservice, so no-view EJB it is. And in case I forgot to mention, it is the @Stateless annotation that makes it an EJB.

The next annotation is the @Path annotation. This defines the url of the resource. So the URL will become: http://[server]:[port]/[appname]/[jersey mapping]/[path-of-resource]. I like the way Rails works, one controller with al the restfull action for a certain resource type in it, so, if you have read other jax-rs tutorials you notice I won't do sub resources (,thats the way I roll!.) So here we will hava a resource "/products".

The advantage of using an EJB in this case is the declarative transactions and the dependency injection. As you see below, we can use the @PersistenceContext annotation to gain access to an EntityManager. We don't have to use the EntityMangerFactory, unlike in servlets, since the specification talks about one instance per request. The specification doesn't speak about the how this maps to stateless EJB's. (Now that I think about it, this needs some more research. But I'll leave it at this for this simple example.)
I'm not going any deeper in the JPA part, and I will assume most of you know how to create a persistence unit.

    @PersistenceContext
    private EntityManager em;

Before looking at the methods, a short introduction to HTTP verbs and resources is needed. So what can we do with a resource? Well, it is the same as with a DAO, we can execute basic CRUD methods an we can list all of the resources. In the next table you see the mapping and the verbs we will use. I don't think more explaination is needed.

HTTP verbmappingmethoddescription
GET/productslistget the list of all products in the form of a products xml fragment>
POST/productscreatecreate a new product by posting a valid product xml fragment. This returns a 302 created status, together with the location of the new resource.
GET/products/[id]readGet a single product, again a valid product xml fragment.
PUT/products/[id]updateUpdate a product, by putting a product xml fragment.
DELETE/products/[id]deleteDelete a product.

It is almost to simple to be true. Again, some tutorials like to use subresources, that means, 1 class representing the collection, and 1 class representing an individual product. They call it a pattern... they just had to call it a pattern, like we don't have enough stupid things already that give a bad reputation to the real patterns!

Lets have a look at the first method.

    /**
     * @response.representation.200.qname {http://verborgh.be/demo/v1}products
     * @response.representation.200.mediaType application/xml
     */
    @GET
    @Produces("application/xml")
    public Products index() {
        return Products.create(em.createNamedQuery("Product.findAll").getResultList());
    }

There are several aspects we need to delve into in this method. There is the strange javadoc, the two annotations and lastley, the magic of the method.
As mentioned above, we need to have a method dat gives us the list of al products. We also mentioned that this should be a simple GET tot the container of the "product" resource. The lack of a @Path annotation indicates that this method will get called we we execute the GET verb on the url assigned to our ProductsService Resource class, this URL being "/products".
The @Produces annotation signifies that this method can only return content of the mimetype application/xml. If we wouldn't add it here, the supported mimetype would have been */*, everything thus. We could have added @Productes({"application/xml", "text/xml", "application/json"}) and everything would have worked since the Jersey implementation of JAX-RS knows how to generate JSON and XML from JAXB objects. I'll leave it as an exercise for the reader.
So the magic of the method is actually nothing more than Jersey that is able to serialize/unmarshal JAXB objects.

But, we still have the javadoc. As you know the javadoc does nothing to the code. It's just documentation. But in a time long before annotations, there was something called XDoclet that got assimilated into javadoc doclets, and what you are seeing here, are just some doclet tags that get parsed by the WADL resourcedoclet. I'll explain later on what we will do here, but you should immediatly see that we are defining the mimetype and the xml element that wil be generated by this method. It is needed to create an extended WADL application.xml document. The namespace is of course the one we defined in our own package-info.java file and the type of the qname is what we have defined on our Products class with the help of the @XmlRootElement annotation. You should also see that the number 200 is a valid HTTP statuscode. 200, in this case just being "ok".

The next method again, is a method on the container. Resources get created in the container and as I mentioned, we will try to follow the rest specifications, so this means we wil use a POST method. This as you see below, means using the @POST annotation.

   /**
     * @request.representation.qname {http://verborgh.be/demo/v1}product
     * @request.representation.mediaType application/xml
     * @response.representation.201.qname {http://verborgh.be/demo/v1}product
     * @response.representation.201.mediaType application/xml
     * @response.param {@name Location}
     *                 {@style header}
     *                 {@type {http://www.w3.org/2001/XMLSchema}anyURI}
     *                 {@doc The URI where the user has been created.}
     */
    @POST
    @Produces("application/xml")
    @Consumes("application/xml")
    public Response create(Product product) {
        em.persist(product);
        return Response.created (
		          UriBuilder.fromPath("/products/{id}").build(product.getId())
			  ).entity(product).build();
    }

There are still some things that need an explanation. The first thing here is the @Consumes annotation. This is nothing more than indicating that when we post to the resource, we expect to see the valid xml fragment in the body. The method parameter of the type Product and the doclet @request.representation.qname both indicate what form the XML fragment should be in.

Since REST implies that each resource is only represented by only one URL, we need to return the location of the newly created entity. The conventional way of doing this is returning a "203 created" response. The JAX-RS specification gives us an easy to use class that allows us to do just that: Response. This class has some static methods that return a ResponseBuilder to add some extra properties like cookies, headers, content etc.
Here we use the Response.created method that set the correct status code (302) and get the location of the newly created xml entity. This URL can be created by means of the UriBuidler, that transforms relative locations to an absolute location, and that is able to interpolate the special fields. In this case it replaces the {id} by the id of the persisted JPA entity.
Because of some problems with code generation, we also append the XML entity tot the content by using the entity method. This allows use to later on create a client that is able to post new objects and reading the result back without problems. The problem here is that the generated code by wadl2java doesn't follow 302 created responses.

The doclet contains as we did before a definition of what gets returned, this time for the 302 code. But it also contains a definition for what we expect to be posted to us. The last thing there is that it also defines what header parameters wil be available. In this case it defines that the client can expect to find a Location header, that contains a URL. As far as I know, it isn't used by the wadl2java compiler.

The last method we are going to look at, the rest just reuses the same concepts, is the read method. What makes the read/update/delete method special is the use of the @Path annotation on a method. The JAX-RS specification says that this means we have a subresource of the container. In this case the url gets appended with the id and the full path becomes for example /product/1. We can gain access to the value of the id passed in the path by using the @PathParam annotation on a parameter of the method. JAX-RS is able to convert this to among other things primitive types.

    /**
     * @response.representation.200.qname {http://verborgh.be/demo/v1}product
     * @response.representation.200.mediaType application/xml
     */
    @GET
    @Path("{id}")
    @Produces("application/xml")
    public Product read(@PathParam("id") Long id) {
        return em.find(Product.class, id);
    }

Before going any further I would like to point out the usage of the @DELETE and the @PUT annotation on the other methods.
Also, if you read the specifications or want to do more, you could have a look at query parameters, matrix parameters, cookie parameters and header parameters.

To run our service, and generate client code, we need to generate and create some artifacts. Let start with what we can generate. If you downloaded the wadl resourcedoclet and the xerces libraries, now is the time to add them to you project. You will also need to add the JAXB library to your project. Normally Netbeans has already added the JAX-RS implementation. Make sure you don't package it in your application. Glassfish v3 has its own implementation and for some reason there is a problem when you include your own version.
Because we use Netbeans we can modify the build.xml of our project and hook some extra tasks in the build cycle. We will define a task -pre-dist that depends on init. This task wil generate our schema and use the doclet tags we defined to generate a resourcedoc.xml in our build path. So this is what the ant target looks like:

    <target name="-pre-dist" depends="init"<

        <taskdef name="schemagen" classname="com.sun.tools.jxc.SchemaGenTask" classpath="${javac.classpath}:${endorsed.classpath}:${j2ee.platform.classpath}" /<

        <mkdir dir="${build.web.dir}/v1"/<

        <schemagen srcdir="${src.dir}" destdir="${build.web.dir}/v1" includes="be/verborgh/demo/*" <
            <exclude name="be/verborgh/demo/*Service.java"/<
            <schema namespace="http://verborgh.be/demo/v1" file="schema.xsd"/<
            <classpath path="${javac.classpath}:${endorsed.classpath}:${j2ee.platform.classpath}"/<
        </schemagen<

        <javadoc access="public" classpath="${javac.classpath}:${endorsed.classpath}:${j2ee.platform.classpath}:${build.classes.dir}" <
            <fileset dir="${src.dir}" includes="be/verborgh/demo/*Service.java"/<
            <doclet name="com.sun.jersey.wadl.resourcedoc.ResourceDoclet" path="${javac.classpath}:${endorsed.classpath}:${j2ee.platform.classpath}"<
                <param name="-output" value="${basedir}/${build.classes.dir}/resourcedoc.xml"/<
            </doclet<
        </javadoc<
    </target<

I think it speaks for itself. One thing I like to mention is the use of ${basedir} in the param, for some reason it wants a full path. The variables I use, are defined by Netbeans at runtime when the init get executed. This is also the reason why the task needs to be defined in the target.

Our generated resourcedoc.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<resourceDoc>
    <classDocs>
        <classDoc>
            <className>be.verborgh.demo.ProductsService</className>
            <commentText><![CDATA[]]>
            </commentText>
            <methodDocs>
                <methodDoc>
                    <methodName>index</methodName>
                    <commentText><![CDATA[]]>
                    </commentText>
                    <responseDoc>
                        <representations>
                            <representation
                                mediaType="application/xml" status="200"
                                element="ns2:products"
                            xmlns:ns2="http://verborgh.be/demo/v1"/>
                        </representations>
                    </responseDoc>
                </methodDoc>
                <methodDoc>
                    <methodName>create</methodName>
                    <commentText><![CDATA[]]>
                    </commentText>
                    <requestDoc>
                        <representationDoc
                            element="ns2:product"
                                xmlns:ns2="http://verborgh.be/demo/v1"/>
                    </requestDoc>
                    <responseDoc>
                        <wadlParams>
                            <wadlParam
                                type="xs:anyURI" style="header"
                                name="Location"
                                xmlns:xs="http://www.w3.org/2001/XMLSchema">
                                <doc>The URI where the user has been created.</doc>
                            </wadlParam>
                        </wadlParams>
                        <representations>
                            <representation
                                mediaType="application/xml" status="201"
                                element="ns2:product"
                                xmlns:ns2="http://verborgh.be/demo/v1"/>
                        </representations>
                    </responseDoc>
                </methodDoc>
                <methodDoc>
                    <methodName>read</methodName>
                    <commentText><![CDATA[]]>
                    </commentText>
                    <responseDoc>
                        <representations>
                            <representation
                                mediaType="application/xml" status="200"
                                element="ns2:product"
                            xmlns:ns2="http://verborgh.be/demo/v1"/>
                        </representations>
                    </responseDoc>
                </methodDoc>
                <methodDoc>
                    <methodName>update</methodName>
                    <commentText><![CDATA[]]>
                    </commentText>
                    <requestDoc>
                        <representationDoc
                            element="ns2:product" xmlns:ns2="http://verborgh.be/demo/v1"/>
                    </requestDoc>
                    <responseDoc/>
                </methodDoc>
                <methodDoc>
                    <methodName>delete</methodName>
                    <commentText><![CDATA[]]>
                    </commentText>
                    <responseDoc/>
                </methodDoc>
            </methodDocs>
        </classDoc>
    </classDocs>
</resourceDoc>

It looks like nothing special, but the fact we have the element and namespace declarations in out representationDoc adds a lot of extra metadata that can be used for generating usefull clients. Without these, we would have to fall back on using raw XML in the clients, and guessing what the service needs.
This resourcedoc.xml needs to be on our classpath, and this script places it in the classes folder of our build webapplication, that is: WEB-INF/classes. The last artifact we need to create is an xml document that contains a link to our schema.xsd. As you see in the ant target, we deploy the shema together with our application, so that it is downloadable from the internet. This file is the application-grammars.xml. Just place it in the default package (= no package) of your Netbeans project. The content should be something like shown below, but don't forget to change the URL to the correct location of your XSD.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<grammars xmlns="http://research.sun.com/wadl/2006/10"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:xi="http://www.w3.org/1999/XML/xinclude">
    <include href="http://localhost:8080/jaxrsdemo/v1/schema.xsd" />
</grammars>

This is a place where magic won't happen, for some kind of a reason, we need to provide a java class that provides Jersey with the configuration... so no XML editing, no properties files, just a java file. Remember, this is jersey specific, the specification doesn't even mention WADL. So: lets make the following class:


package be.verborgh.config;

import com.sun.jersey.api.wadl.config.WadlGeneratorConfig;
import com.sun.jersey.api.wadl.config.WadlGeneratorDescription;
import com.sun.jersey.server.wadl.generators.WadlGeneratorGrammarsSupport;
import com.sun.jersey.server.wadl.generators.resourcedoc.WadlGeneratorResourceDocSupport;
import java.util.List;

/**
 *
 * @author verborghs
 */
public class ExtendedWadlGeneratorConfig extends WadlGeneratorConfig{

    @Override
    public List<WadlGeneratorDescription> configure() {
      return generator(WadlGeneratorGrammarsSupport.class)
                .prop("grammarsStream", "application-grammars.xml")
                .generator(WadlGeneratorResourceDocSupport.class)
                .prop("resourceDocStream", "resourcedoc.xml")
                .descriptions();
    }

}

All this class does is defining what files schould be merged with the autogenerated application.wadl WADL file. As you see, we tell it to include the application-grammar.xml that contains our schema location, and our resourcedoc.xml that contains the representationDoc with the specified elements.

The last we need to do, is configure our web.xml to include jersey configured with the WadlGeneratorConfig we just wrote:

    <servlet>
        <servlet-name>ServletAdaptor</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.WadlGeneratorConfig</param-name>
            <param-value>be.verborgh.config.ExtendedWadlGeneratorConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

Now! Deploy your application en go to resource/application.wadl and you should see a nice complete wadl defenition for you application like below:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:application xmlns:ns2="http://research.sun.com/wadl/2006/10">
    <ns2:doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey: 1.1.4 11/10/2009 05:41 PM"/>
    <ns2:grammars>
        <ns2:include href="http://localhost:8080/jaxrsdemo/v1/schema.xsd"/>
    </ns2:grammars>
    <ns2:resources base="http://localhost:8080/jaxrsdemo/resources/">
        <ns2:resource path="products">
            <ns2:method id="create" name="POST">
                <ns2:request>

                    <ns2:representation xmlns:ns3="http://verborgh.be/demo/v1" element="ns3:product" mediaType="application/xml"/>
                </ns2:request>
                <ns2:response>
                    <ns2:representation xmlns:ns3="http://verborgh.be/demo/v1" element="ns3:product" status="201" mediaType="application/xml"/>
                </ns2:response>
            </ns2:method>
            <ns2:method id="index" name="GET">
                <ns2:response>
                    <ns2:representation xmlns:ns3="http://verborgh.be/demo/v1" element="ns3:products" status="200" mediaType="application/xml"/>

                </ns2:response>
            </ns2:method>
            <ns2:resource path="{id}">
                <ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="id" style="template" type="xs:long"/>
                <ns2:method id="delete" name="DELETE"/>
                <ns2:method id="read" name="GET">
                    <ns2:response>
                        <ns2:representation xmlns:ns3="http://verborgh.be/demo/v1" element="ns3:product" status="200" mediaType="application/xml"/>
                    </ns2:response>

                </ns2:method>
                <ns2:method id="update" name="PUT">
                    <ns2:request>
                        <ns2:representation xmlns:ns3="http://verborgh.be/demo/v1" element="ns3:product" mediaType="application/xml"/>
                    </ns2:request>
                </ns2:method>
            </ns2:resource>
        </ns2:resource>
    </ns2:resources>

</ns2:application>

You can also surf to resources/products or use curl to post something to your application as follows:

echo 'test' | curl -X POST -H 'Content-type: application/xml' -d @- http://localhost:8080/jaxrsdemo/resources/products --trace output.txt

I'll update this later to include how to generate a client and use that client.
I also want to add the remark that the use of all the chained builders etc, the configuration in a compiled file, the lack of wadl specification, the use of doclets and the fact that Jersey has it's own custom Dependency Injection implementation made me feel like I was working in an alien world. I think this is a part of the Java EE spec that makes the Spec feel like is was writen by different persons. I want to use CDI, beter annotations, no doclets and configuration by annotation of xml... just saying, but then again. who am i?

samples$ who am i
verborghs pts/1        2009-11-24 09:38 (:0.0)
samples$