Monday, October 5, 2009

Proxy client based on Jersey with a bit of HATEOAS built in, version 2

I had some feedback and some time to reflect on my previous blog posting on this topic. So here is a revised version of that post. The basic proxying mechanism is the same; but how we deal with references to resource has been updated to remove any references to URIs. (Thank for the push in that direction Solomon.)

The root BucketsResource class is little changed from before, I had just added "Resource" post fix to make it easier to tell them apart from the bean.

package bucketservice;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("buckets")
public interface BucketsResource {

    @POST
    public BucketResource createNewBucketBound(String input);

    @GET
    @Produces("application/buckets+xml")
    public BucketList getBuckets();
    
    @Path("/{id}")
    public BucketResource getBucket();
}

The BucketResource class is slightly different than before in that we now specify a JAXB mapping class. The implementation of this is not important for the moment; but behind the scenes it converts the resource into a URI. For the purposes of XML (un)marshalling this type is now a URI. (I haven't tested it but this would most likely be the type in the published schema)

package bucketservice;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.core.Response;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import proxy.ResourceAdapter;


@XmlJavaTypeAdapter(BucketResource.Adapter.class)
public interface BucketResource {
     
    public static class Adapter
      extends ResourceAdapter<BucketResource> {
        
    }
    

    @GET 
    public String getBucket();

    @DELETE 
    public Response.Status delete();

}

Finally since we have put the XML "smarts" in the resource interface the BucketList class no longer needs to have any custom code generation.

package bucketservice;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class BucketList 
{
    
    BucketResource list[];

    public void setList(BucketResource[] list) {
        this.list = list;
    }

    public BucketResource[] getList() {
        return list;
    }
}

The client code is the same as before; but instead using the native getList method rather than the method we no longer have to generate. The advantage of working this way is that the two lists don't have to be maintained in sync as we would have done previously.

The problem that is not addressed is how to easily map domain objects into URIs; but Solomon has a reasonable solution to this in his blog. I am not sure though whether this as useful for the client case as your needs are different than when writing the server.

I did some work on converting values into resource objects cleanly by reading the annotations on the supplied resources. This would make it easier for a bean to for example create a "this" resource which would render as a URI. I need to write the code that will take a resource and extract the parameters in order to make this work bi-directionally.

    // Creating new buckets
    //
        
    BucketResource b15 = of(rootResource,BucketResource.class, BucketsResource.class).build(
                   "15");

    ResourceBuilder<BucketResource> bucketBuilder =
        of(rootResource, BucketResource.class, BucketsResource.class);

    BucketResource b7 = bucketBuilder.build(
                   "7");
    BucketResource b8 = bucketBuilder.build(
                   "8");

    // Extract parameters from a given set of resources
    //

    assert "8".equals(bucketBuilder.extract(b8);

I guess you could use the following to generate a self reference; but it is just not as pretty as Solomon's version.


    // Generating a "self reference"

    public class Bean
    {
       @XmlJavaTypeAdapter(BeanResourcePropertyAdapter.class)
       private String id;
    }

    public BeanResourcePropertyAdapter extends XmlAdapter<URI, String>
    {
       // TODO work out whether ??? can be derived from a context

       public URI marshal(String id)
       {
          return = ((ProxiedResource)of(????, BeanResource.class).build(id))
              .getURI();
       }

       public String unmarshal(URI br)
       {
          return of(????, BeanResource.class).extract(br);
       }
    }

A good deal more thinking on this to be done; but enough for today. Time to get some feedback.

No comments: