Search This Blog

Showing posts with label jax-rs. Show all posts
Showing posts with label jax-rs. Show all posts

Saturday, February 27, 2010

REST Client Frameworks - Your options

In previous blogs, I have discussed how the different frameworks for REST work, client and server side. In particular, I have investigated different JAX RS vendors such as Jersey, RESTEasy and Restlet JAX-RS and an implementation that is not JAX-RS as well, i.e., Core Restlet whose API pre-dated the JAX RS specification. I am currently looking at different JAX RS vendor implements for client side support. The JAX RS specification does not include a client side API and different JAX RS vendors have proceeded to create their own API's for the same. The framework one selects for the client and service ends do not have to be the same. For example, one could have a RESTful service running using Restlet while having a client developed in RESTEasy that consumes the same. In most cases, one will typically be satisfied with using the same client framework as one is using for the service development in order to be consistent and potentially be able to re-use artifacts in both areas if the framework permits the same.
That said, I have been looking at the following vendors for the Client side API and implementation as my needs require me to.
  • Jersey - A JAX RS reference implementation from Oracle/Sun.
  • RESTEasy - A JAX RS reference implementation by JBoss.
  • Restlet - A pioneer of sorts that has support for Client Side REST calls
  • CXF - An apache project that is a merger of XFire and Celtix. Provides a JAX RS implementation and JAX WS as well.
  • Apache Wink - A JAX RS implementation that has a client and server side api that has had a 1.0 release just recently
  • Spring 3.0 - Spring provides a RestTemplate, quite like the JmsTemplate and HibernateTemplate in its support of REST clients.
  • Apache HTTPClient or Http Components - Direct use of HTTP API - Down and dirty.
As an evaluator, I would look for the following things in a Client framework:
  • User Base - Documentation, Support, Community, Release cycles, Age of solution
  • Easy of API for RESTful operations - URL Parameter substitution, HTTP verbs, Proxy support
  • Dryness of the API - Certain frameworks allow for components such as contracts developed for the service to be re-used in the client
  • Http Connection Control - Ability to do connection pooling, connection reaping and controlling lower level HTTP connection settings such as connection time out if required
  • Safety of the API - It is possible to "leak" connections. This occurs when connections are not restored properly to the pool.
  • Support for different Provider types - JAXB, JSON, Atom, Yaml and others while providing hookins to introduce new provider types.
  • Interception of a requests - For security and ability to add additional information, for example to HTTP headers
  • Deployment footprint - number of dependencies the library brings.
  • Error Response Handling - Ease of handling alternate response types apart from standard expected responses, for example how the framework supports exception throwing.
  • Performance - Efficiency of Provided Converters such as JAXB, JSON, Memory foot print etc
The above list represent things that one would look for while evaluating a solution. What I aim to do with this blog is provide a maven project that utilizes these client framework thus allowing evaluators to investigate the different solutions. I will put forth my opinions as well.
When dealing with connections, it is often desirable to keep alive Http Connections. The Apache Http Client has for sometime now provided a library that facilitates connection keep alive, connection reaping and safe release of connections. It is almost the defacto underlying Http Client library for Http operations. The standard Http support for the core jdk is limited in its offering. Apache Http Client recently underwent a re-haul of their architecture to clean out areas of their base while providing performance enhancements. Using Http Client directly has some limitations though where one would need to build converters for standard media types like JAXB, JSON, Atom etc. RESTful client libraries like RESTEasy, Jersey, Apache Wink and Restlet have a RESTful API layer on top of Apache Http Client that eases the development of RESTful clients.
I am working with a Maven project that has a service that uses Jersey while having clients from different frameworks consume the same. The code is NOT doing any benchmarking of any sort, it can however be used to if required. What the code instead does is demonstrate the different clients and their API's. It also demonstrates the use of Proxies where applicable.
The service developed is very similar to the ones I have used in previous blogs where a client can obtain product information and then perform CRUD operations on Orders. Subsequently, from the clients perspective, there are two clients. Order Clients and Product Clients. Some of the client frameworks being discussed have the concept of annotation driven proxies that allow for easy development; for those frameworks, I have provided proxy clients in the examples as well.


All the clients of the example, implement one of the following two interfaces,
OrdersClientIF

public interface OrdersClientIF { 
public static final String ORDERS_RESOURCE = "/orders";
 /**
  * Create an order.
  */
  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException;

  /**
   * Update an existing order
   */
  public void update(OrderDto dto) throws OrderNotFoundException, OrderValidationException,    OrderException;

  /**
   * Retreive an Order
   */
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException;

  /** 
   * Deletes an order
   */
  public void delete(Long orderId) throws OrderException;
}

ProductsClientIF

public interface ProductsClientIF {
public static final String PRODUCTS_RESOURCE = "/products";
  /** 
   * @return A set of Products
   */
  public Set<ProductDto> getProducts();
}

1. Apache CXF:
Apache CXF is a full fledged JAX RS implementation with a client side api as well. It is a framework for JAX-RS and JAX-WS. A client API is provided in three forms, proxy based, HTTP-Centric and XML-centric. Apache Cxf however does not use HTTP Components or Apache HTTP Client. More information on their Client API can be viewed at http://cxf.apache.org/docs/jax-rs.html#JAX-RS-ClientAPI

a. Proxy Based:

public class ApacheCxfProxiedOrdersClient implements OrdersClientIF {
  /**
   * Proxy Definition
   */
  private static interface OrderCxfIF {
    @GET
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public OrderDto get(@PathParam("id") String id);

    @POST
    @Produces(MediaType.APPLICATION_XML)
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE)
    public OrderDto create(OrderDto dto);

    @PUT
    @Produces(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public void update(@PathParam("id") String id, OrderDto dto);

    @DELETE
    @Path(ORDERS_RESOURCE + "/{id}")
    public void delete(@PathParam("id") String id);
}
....

public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
  try {
    return JAXRSClientFactory.create(baseUri, OrderCxfIF.class).create(dto);
  }
  catch (WebApplicationException e) {
    if (e.getResponse().getStatus() == Status.BAD_REQUEST.getStatusCode()) {
      throw new OrderValidationException(e.getMessage());
    }
    throw new OrderException(e.getMessage());
  }
}
...
@Override
public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
  try {
    return JAXRSClientFactory.create(baseUri, OrderCxfIF.class).get(String.valueOf(orderId));
  }
  catch (WebApplicationException e) {
    if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
      throw new OrderNotFoundException(e.getMessage());  
    }
    throw new OrderException(e.getMessage());
  }
}
..
}
With the Proxy based API one can re-use server side artifacts for the client side as well. The API looks pretty straight forward to use and if requiring more control, one can also utilize the WebClient for more detailed operations such as setting header or content type. For handling exceptions, the Cxf site suggests the using the ResponseExceptionMapper. I however could not get the same to be registered and working. The documentation on the same appeared sparse. If one does not define a ResponseExceptionMapper, then when a failure occurs, a WebApplicationException is thrown. One can utilize the same to re-throw appropriate exceptions and consume alternate return types.

b. HTTP-Centric:

public class ApacheCxfOrdersClient implements OrdersClientIF {
....
  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    try {
       return WebClient.create(baseUri).path(ORDERS_RESOURCE).accept(MediaType.APPLICATION_XML)
         .invoke(HttpMethod.POST, dto, OrderDto.class);
    }
    catch (WebApplicationException e) {
      if (e.getResponse().getStatus() == Status.BAD_REQUEST.getStatusCode()) {
        throw new OrderValidationException(e.getMessage());
      } 
      throw new OrderException(e.getMessage());
    }
  }
....
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    try {
      return   WebClient.create(baseUri).path(ORDERS_RESOURCE).path(String.valueOf(orderId)).accept(
         MediaType.APPLICATION_XML).invoke(HttpMethod.GET, null, OrderDto.class); 
    }
    catch (WebApplicationException e) {
      if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException(e.getMessage());
      }
      throw new OrderException(e.getMessage());
    }
  }
}

With the HTTP Centric client, one uses WebClient instances. As in the case of the Proxy, one can catch the WebApplicationException in the case of failure responses for Exception management.

2. RESTEasy:

RESTEasy is a JBoss project that is a fully certified JAX-RS implementation. The client side framework supports the Proxy style model and a HTTP centric model as well. The client side framework utilizes Apache HTTP Client 3.X and has support for 4.X as well thus one has the ability to easily control connection parameters and pooling. The last time I read about the framework, both Http Client versions are supported but the HttpClient 4 has not been validated as well as the 3 version. The same might have changed since then. It is very easy to set one or the other though. Another nice feature is that an interface can be shared between the client and server. Requests can also nicely be intercepted via an implementation of org.jboss.resteasy.spi.interception.ClientExecutionInterceptor to add addition information to the header etc. More information on their Client API can be viewed from the web site http://www.jboss.org/file-access/default/members/resteasy/freezone/docs/1.2.GA/userguide/html/RESTEasy_Client_Framework.html

a. Proxy Based:

public class ResteasyProxiedOrdersClient implements OrdersClientIF {
  private static interface RestEasyIF {
    @GET
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public OrderDto get(@PathParam("id") Long id);

    @POST
    @Produces(MediaType.APPLICATION_XML)
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE)
    public OrderDto create(OrderDto order);

    @PUT
    @Produces(MediaType.APPLICATION_XML)
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public void update(@PathParam("id") Long id, OrderDto dto);

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

  static {
    RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
    // Execution interceptor registration
    ResteasyProviderFactory.getInstance().registerProvider(ExecutionInterceptor.class);
  }

  private final ClientExecutor clientExecutor;
  private final RestEasyIF delegate;

  public ResteasyProxiedOrdersClient(String baseUri) {
  ...
    clientExecutor = new ApacheHttpClient4Executor(helper.getHttpClient());
    delegate = ProxyFactory.create(RestEasyIF.class, baseUri, clientExecutor);
  }

  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    try {
      return delegate.create(dto);
    }
    catch (ClientResponseFailure failure) {
      if (failure.getResponse().getStatus() == Status.BAD_REQUEST.getStatusCode()) {
        throw new OrderValidationException(failure.getMessage());
      }
      throw new OrderException(failure.getMessage());
    }
  }

  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    try {
      return delegate.get(orderId);
    }
    catch (ClientResponseFailure e) {
      if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException("Order Not found");
    }
    throw new OrderException(e.getMessage());
  }
 }
}

One of the gripes that I had while working with the version of the RESTEasy Proxy is I could not understand why I had to provide an @Consumes annotation on the interface for an update operation that returns "void" and is a PUT HTTP method. The Cxf client did not have that requirement and I feel it redundant to have to specify the same.
Upon failure of an invocation, a ClientResponseFailure is thrown which can then be interrogated to throw any custom exception you desire. I must admit, one thing I have not tested is whether on not, upon receiving a ClientResponseFailure if the response body is not read, will the underlying HttpClient connection be safely released by RESTEasy proxy code for re-use or is there a potential to leak a connection? This is worth investigating if looking at the same.

b. HTTP Centric or Manual Client Request API:

public class ResteasyOrdersClient implements OrdersClientIF {
  ...
  private final ClientExecutor clientExecutor;

  static {
    RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
    ResteasyProviderFactory.getInstance().registerProvider(ExecutionInterceptor.class);
  }

  public ResteasyOrdersClient(String baseUri) {
    ..
    helper = new HttpClientFourHelper();
    clientExecutor = new ApacheHttpClient4Executor(helper.getHttpClient());
  }

  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    ClientResponse response = null;
    try {
      ClientRequest request = new ClientRequest(ORDERS_URI, clientExecutor);
      response = request.body(MediaType.APPLICATION_XML, dto).post();

      if (response.getStatus() 
            == javax.ws.rs.core.Response.Status.BAD_REQUEST.getStatusCode())  {
        throw new OrderValidationException(response.getEntity(String.class));
      }
      return response.getEntity(OrderDto.class);
    }
    catch (OrderValidationException e) {
      throw e;
    }
    catch (Exception e) {
      throw new OrderException(e.getMessage());
    }
    finally {
      // Safe release of connection/stream
     if (response != null) {
       response.releaseConnection();
     }
    }
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    ClientRequest request = new ClientRequest(ORDERS_URI + "/{id}",
     clientExecutor).pathParameter("id", orderId);
    ClientResponse response = null;
    try {
      response = request.accept(MediaType.APPLICATION_XML).get();
      if (response.getStatus() 
           == javax.ws.rs.core.Response.Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException("Order Not found");
      }
      return response.getEntity(OrderDto.class);
    }
    catch (OrderNotFoundException e) {
      throw e;
    }
    catch (Exception e) {
      throw new OrderException(e.getMessage());
    }
    finally {
      if (response != null) {
        response.releaseConnection();
      }
    }
 }
....
}

With the manual API, one has considerable control on the request object such as setting headers etc. One difference to note that unlike in the case of the Proxy Client, in the event of a failure response, a ClientResponseFailure exception is not thrown. One would need to explicitly work with the ClientResponse object to discern the same and throw any custom exceptions desired. With the manual client, one has the ability to explicitly release the down stream connection to prevent leaks.

3. Restlet:

Restlet is one of the earliest frameworks for RESTful services ever developed if not the earliest. It has a very mature API and implementation and provides multiple ways of working with RESTful services. There is the core API which pre-dates the JAX-RS specification while also having a JAX-RS implementation. They have a mature client API that with their upcoming 2.0 release will utilize HttpClient 4.0. The control over the HTTP Client is a bit hard to get to as the API tends to hide the same. However, their API does allow for most HTTP Client control one would imagine. Again, there are two ways in which one can work with the Restlet client framework, either using Proxy Client or via direct HTTP centric API. Restlet documentation can be viewed at http://www.restlet.org/documentation/2.0/tutorial

a. Proxy Based:

public class RestletProxiedOrdersClient implements OrdersClientIF {
  // Proxy interface
  public static interface OrdersResource {
    @Get
    public OrderDto getOrder();

    @Post
    public OrderDto create(OrderDto dto);

    @Put
    public void update(OrderDto dto);

    @Delete
    public void delete();
  }
  ....

  public OrderDto create(OrderDto orderDto) 
    throws OrderValidationException, OrderException {  
    try {
      ClientResource cr = new ClientResource(ORDERS_URI);
      OrdersResource res = cr.wrap(OrdersResource.class);

      OrderDto result = res.create(orderDto);
      return result;
    }
    catch (ResourceException e) {
      if (e.getStatus().equals(Status.CLIENT_ERROR_BAD_REQUEST)) {
        throw new OrderValidationException(e.getMessage());
      }
      throw new OrderException("Unexpected Error:" + e.getStatus());
    }
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    ClientResource cr = new ClientResource(ORDERS_URI + "/" + orderId);
    OrdersResource res = cr.wrap(OrdersResource.class);
    try {
      return res.getOrder();
    }
    catch (ResourceException e) {
      if (e.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
        throw new OrderNotFoundException(e.getMessage());
      }
      throw new OrderException("Unexpected Error:" + e.getStatus());
   }
 }
..
}

Exception management is handled by catching a ResourceException and throwing any custom exception desired. One area that requires further investigation is how to safely release a HTTP Connection when using HTTP Client as the underlying transport and ClientResource with the Proxy. Again, this is an area one would need to dig into if control of HTTP Client parameters is required along with safe release of pooled connections. For more information on the same look at http://n2.nabble.com/Client-Timeout-on-ClientResource-post-method-td3690842.html.

b. HTTP Centric or Manual Client Request API:

public class RestletOrdersClient implements OrdersClientIF {
  private final Client client;
  ....
  public RestletOrdersClient(String baseUri) {
    client = new Client(new Context(), Protocol.HTTP);
    ..
  }

  public OrderDto create(OrderDto orderDto) throws OrderValidationException, OrderException {
    Response response = null;

    try {
      response = client.post(ORDERS_URI, new JaxbRepresentation<OrderDto>(orderDto));

      if (response.getStatus().isSuccess()) {
        return new JaxbRepresentation<OrderDto>(response.getEntity(), OrderDto.class).getObject();
      }
      else if (response.getStatus().equals(Status.CLIENT_ERROR_BAD_REQUEST)) {
        throw new OrderValidationException("Error validating order");
      }
      else {
        throw new OrderException("Error processing order:" + response.getStatus());
      }
    }
    catch (OrderValidationException e) {
      throw e;
    }
    catch (IOException e) {
      throw new OrderException("Unexpected:" + e);
    }
    finally {
      // Explicit safe release of response
      if (response != null) {
        response.release();
      }
    }
  }

  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    Response response = null;

    try {
      response = client.get(ORDERS_URI + "/" + orderId);
      if (response.getStatus().isSuccess()) {
        return new JaxbRepresentation<OrderDto>(response.getEntity(), OrderDto.class).getObject();
      }
      else if (response.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
        throw new OrderNotFoundException("Order Not Found");
      }
        throw new OrderException("Unknown error processing order:" + response.getStatus());
    }
    catch (IOException e) {
      throw new OrderException(e.getMessage());
    }
    finally {
      if (response != null) {
        response.release();
      }
    }
  }
}

The direct Client API is very straight forward as well. With the safe release of Http Connections accounted for and control of the HTTP Client parameters, the Restlet client is a very powerful proven client side implementation.

4. Apache Wink:

Apache Wink is a complete implementation of the JAX-RS specification while providing a client side API to communicate with RESTful services. The framework is relatively new with 1.0-incubating version available at the time of this blog. Apache Wink allows you to work with Http Client and thus control all the lower level operations easily. Unlike the above mentioned frameworks, there is currently no Proxy based client support for Apache Wink. That said, their client API flows very well and is easy to understand and use. An implementation of their ClientHandler interface allows one to easily intercept requests for custom header or security while also providing an avenue to throw custom exceptions based of alternate failure responses. Documentation on the Apache Wink client can be viewed at http://incubator.apache.org/wink/1.0/html/6%20Apache%20Wink%20Client.html

public class ApacheWinkOrdersClient implements OrdersClientIF {
  private final RestClient restClient;
  ...
  public ApacheWinkOrdersClient(String baseUri) {
    ClientConfig config = new ApacheHttpClientConfig(helper.getHttpClient());
    // Exception handler can also be used as an intercepting filter
    config.handlers(new ExceptionHandler());
    restClient = new RestClient(config);   
  }

  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    try {
      return restClient.resource(UriBuilder.fromUri(baseUri)
            .path(ORDERS_RESOURCE).build()).contentType(MediaType.APPLICATION_XML)
            .accept(MediaType.APPLICATION_XML).post(OrderDto.class, dto);
    }
    catch (ClientRuntimeException e) {
      if (e.getCause() instanceof OrderValidationException) {
         throw ((OrderValidationException) e.getCause());
      }
      else if (e.getCause() instanceof OrderException) {
         throw ((OrderException) e.getCause());
      }
      throw e;
    }   
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    try {
      return restClient.resource(UriBuilder.fromUri(baseUri)
        .path(ORDERS_RESOURCE).path("{id}").build(orderId)).accept(MediaType.APPLICATION_XML)
        .get(OrderDto.class);
    }
    catch (ClientRuntimeException e) {
      if (e.getCause() instanceof OrderNotFoundException) {
        throw ((OrderNotFoundException) e.getCause());
      }
     else if (e.getCause() instanceof OrderException) {
        throw ((OrderException) e.getCause());
     }
     throw e;
   }   
  }
  ....
  private static final class ExceptionHandler implements ClientHandler {
    public ClientResponse handle(ClientRequest request, HandlerContext context) throws Exception {
      // Filter for example for standard headers
      request.getHeaders().add("foo", "bar");

      ClientResponse cr = context.doChain(request);
      if (cr.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException(cr.getMessage());
      }
      else if (cr.getStatusCode() == Status.BAD_REQUEST.getStatusCode()) {
        throw new OrderValidationException(cr.getMessage());
      }
      else if (cr.getStatusCode() == Status.SERVICE_UNAVAILABLE.getStatusCode()) {
        throw new OrderException(cr.getMessage());
      }
      return cr;
   }
 }
}

5. Jersey:

Jersey is sun's implementation of the JAX-RS specification. Jersey like other frameworks provides for a client side framework as well. Jersey supports Apache HTTP Client via a totally separate implementation and artifact. Currently the support exists for HttpClient 3.X, whether 4.X will be incorporated is up in the air. One can choose to write custom code to do the same if Http Client 4.X is the direction one wishes to employ. Jersey does not have the concept of Proxy clients currently. However, their API flows very well with their standard client. Their use of Filters on the client side enables easy interception of requests for customization while providing safe release of any connections used. Information on jersey and their client API can be found at https://jersey.dev.java.net/
public class JerseyOrdersClient implements OrdersClientIF {
  ....
  public JerseyOrdersClient(String baseUri) {
    ..
    ApacheHttpClientHandler handler = new ApacheHttpClientHandler(helper.getHttpClient());
    client = new ApacheHttpClient(handler);
    // Filter allows for intercepting request
    client.addFilter(new RequestFilter());
  }
  ...
  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    ClientResponse response = null;
    try {
      response = client.resource(baseUri).path(ORDERS_RESOURCE).entity(dto,
        MediaType.APPLICATION_XML).post(ClientResponse.class);
      throwExceptionIfNecessary(response);
      return response.getEntity(OrderDto.class);
    }
    finally {
      if (response != null) {
        response.close();
      }
    }
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    ClientResponse response = null;
    try {
      response = client.resource(baseUri).path(ORDERS_RESOURCE)
       .path(String.valueOf(orderId)).accept(MediaType.APPLICATION_XML)
       .get(ClientResponse.class);

      if (response.getStatus() == Status.OK.getStatusCode()) {
        return response.getEntity(OrderDto.class);
      }
      else if (response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException(response.getEntity(String.class));
      }
      else if (response.getStatus() == Status.SERVICE_UNAVAILABLE.getStatusCode()) {
        throw new OrderException(response.getEntity(String.class));
      }
      throw new OrderException("Unexpected");
   }
   finally {
     if (response != null) {
       response.close();
     }
   }
 }
 ....
 private static final class RequestFilter extends ClientFilter {
   public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
      MultivaluedMap<String, Object> map = cr.getHeaders();
      map.add("foo", "bar");
      return getNext().handle(cr);
   }   
 }
}

6. Spring RestTemplate:

Aah, what do I say. My favorite framework in the whole wide world is now supporting REST with their 3.X release. Like the popular HibernateTemplate, JdbcTemplate we now have RestTemplate with Spring. Spring supports server side JAX-RS and a client API to consume the service with the RestTemplate. RestTemplate can be configured to work with HttpClient 3.X and thus have control over lower level HTTP parameters and pooling pretty easily. The RestTemplate like other thoughtful Spring implementations is based of callbacks where safe release of resources is important. The RestTemplate has simple methods for commonly used HTTP operations while providing the call back mechanism when one desires more control. I must however mention with great restraint that going the call back route is not an easy task and requires considerable customization. This becomes important if you wish to customize the header properties etc. Error handling is easily accomplished with an extention of ResponseErrorHandler. Further information on the RestTemplate can be viewed at the following location http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/remoting.html#rest-client-access

public class SpringOrdersClient implements OrdersClientIF {
  private final RestTemplate template;
  ....
  public SpringOrdersClient(String baseUri) {
    ...
    ClientHttpRequestFactory requestFactory
       = new CommonsClientHttpRequestFactory(helper.getHttpClient());
    template = new RestTemplate(requestFactory);
    // Set Error handler
    template.setErrorHandler(new ErrorHandler());
  }

  public OrderDto create(OrderDto orderDto) throws OrderValidationException, OrderException {
    return template.postForObject(ORDERS_URI, orderDto, OrderDto.class);      
  }

  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    return template.getForObject(ORDERS_URI + "/{id}", OrderDto.class,
        Collections.singletonMap("id", String.valueOf(orderId)));
  }
  ....
  private static final class ErrorHandler implements ResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
      if (response.getStatusCode().series().compareTo(Series.SUCCESSFUL) == 0) {
        return false;
      }
      return true;
    }

    public void handleError(ClientHttpResponse response) throws IOException {
       if (response.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
          throw new OrderNotFoundException("Order Not Found");
       } else if (response.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
          throw new OrderValidationException("Error validating order");
       } else {
          throw new OrderException("Unexpected error:" + response.getStatusText());
       }
    }  
  }
}

Running the Examples:

The example is available as JDK 6.X project that uses maven. An integration test is provided that uses each of the different mentioned clients to communicate the very same life cycle test for CRUD operations with the RESTful service. Once you have a maven environment, execute "mvn install" from the root level of the project to see the entire project build and execute the integration test where each of the clients are demonstrated. For those interested, you could use the clients to determine benchmarks, test for memory foot print, what have you.
Download the source from HERE.

Parting Thoughts:
I hope the above examples are of use to someone evaluating REST frameworks for their project and would like to witness each of them in action. I have been a REST fanatic ever since introduced to the architectural style and simply enjoy playing with frameworks and tools that present themselves. It would have been nice if during jsr 311 the expert body had defined a client side api as well. I beleive a future JSR is expected for a client API to JAX-RS.
One can details about each of the above mentioned projects and metrics about the same such as commiters, activity, maturing etc from Oh Loh. That said, I would like to share my 2c on the above frameworks. Note that these are my own 2c that I am pulling out of my b..t and is not in any way my employers views or thoughts on the matter.

Apache Cxf:
The one word that comes to mind when I view this framework is BLOAT. It seems to bring in so many dependencies that are of no use if ones goal is simply to work with REST. Their documentation on the REST clients API was not the best with broken links. It is my understanding that they do not support Apache HTTP Client and there is no drive to do the same. If a team is supporting JAX-WS and JAX-RS, maybe their framework works in synergy. You will notice that among all the frameworks mentioned above, I never bothered to support an example of CXF, the primary reason for the same is my disillusionment with their documentation and WIKI where I encountered broken links and partial information. I also ran into an issue where I needed to have HttpServletRequest on the client as a dependency.

RestEasy:
In RestEasy, you have me as a fan. I really like the effort put into the framework by its owner Bill Burke and his team. Their proxy solution is very enticing and will serve to address most RESTful consumers. I might be pipe dreaming here but never the less, I recall reading somewhere that the RESTEasy's implementation of the client library will be serving as the foundation of the client side equivalent of JSR 311. Active development, with support for ASYNC HTTP along with good documentation is what they bring to the table. It is an especially notable consideration to use RESTEasy if re-use across service and client for "contract" purposes is desired. One additional point of note is the philosphy that RestEasy employs regarding backward compatibilty. They seem very concious regarding the direction they employ with change to ensure backward compatibility.

Restlet:
Restlet is an established and proven framework. One of the things I particularly like about Restlet is that their community is very active in offering assitance to those in need. If you post a question on their mailing list, you can almost be guaranteed a response as long as the question is within answerable parameters. They are very quality concious as well. One gripe that I do have have with Restlet is that they have chosen to break compatiblity between their 1.1.X series of releases when moving to their 2.0.X series without a transitionary phase.
The selling point of Restlet as a client API is their transparent API and simplicitly, coupled with their helpful community.

Apache Wink:
YAJAXRS (Yet another JAX-RS implementation) is my initial reaction when thinking of Apache Wink. However, when I look further, although comparitively immature in the space, they have a solid offering in WINK. If you are looking for a light weight JAX-RS implementation, look no further. Their client API utilizes Http Client 4.0 to effect and in my tests with the API, it found it really fast and performant. Their API is simple, transparent and effective. Their documentation is however sparse and I wonder regarding their longevity when compared with the big hitters such as jersey, restlet and resteasy.

jersey:
jersey, as I said in a previous blog, home town of the boss, rocks. What appeals to me is the simplicity of the API, the adoption, the community and decent documentation regarding the API. Their support for Http Client 3.X is present. I am certain they will support 4.X soon.

spring:
Its hard to say anything about spring without seeming biased in favor. Spring's RestTemplate is as solid as can be expected. Their standard call back based approach for safe release of resources works well even for the REST template. If one is using Spring-mvc and their REST support, very few reasons would drive me to consider an alternative framework for the client. One among them is definitely finer grained control, another is Http Client 4.X support. Documentation is sparse on RestTemplate as well. But one has a community to back up on. There might a bit of up front customizations, standard call back etc that an adopter might create but once done I feel that it would be a easy to work with the RestTemplate.
Clearly one has many choices in selecting a client side framework for RESTful HTTP. In most cases it probably makes sense to use the same framework for the service and client end. Then again, if you are only a consumer of a service you have multiple choices among those shown above as well as the option of using Apache HTTP Client or Components directly and bypassing the higher level frameworks. For some, integrating with the spring framework is important and all the above frameworks have means of integration points, both on the service and client sides. Support for Client Proxies is something one might want to consider as they tend to simplify the programming model. Further if Resource definitions can be shared among client server, that can be quite useful in being DRY (Don't repeat yourself) and provide means for contract definition. For those interested in performance and tuning of the HTTP Connections, using a framework that allows you to manage connection pooling and other parameters is definitely the way to go. One should also look at the maturity, user base, support, back ward compatibility support when making a selection. Are there other options apart from the above mentioned? In addition, any recommendations based of personal experience with the above mentioned client frameworks is always welcomed.

Monday, March 2, 2009

An Example of Caching with REST using Jersey JAX-RS

One of the constraints/benefits of a RESTful architecture is the use of Cache's where possible. REST architecture gains from the use of cache's by reducing network bandwidth and unnecessary I/O. In short, caching of information has a direct impact on the scalability of the RESTful architecture.

Service Side Cache:
When a request is made to retrieve a data set, if the data set information does not change over a fixed duration as determined via non-functional requirements, then caching the data set on the Server side has the benefit of not having to suffer for example, database I/O for every client request. Network bandwidth utilization from Client to Server is of course suffered with a Server only cache.

Client Side Cache:
HTTP has a very cool construct in terms of information that a server can provide to a client saying cache or do not cache the payload provided by the server via HTTP Header attributes. In addition, client's can also utilize Conditional GET's to only obtain payload if the data has changed on the server. With Conditional GET one could also only return the changes that have occurred and a Client could easily compute the delta and update its local cached copy of the data.

I wonder how many organizations utilize these features provided by HTTP. The Server side cache can easily be accommodated via using Squid or some other tool.

On the Client side, now thats a bit of discussion. As part of HTTP response, a server can let the client know whether or not to Cache the provided representation via the "Expires" HTTP header attribute. As an example, when a Product Client or Web Browser, requests a list of Products from the server, if the server knows that the Product data will not be refreshed for some time, it can inform the client to cache the payload representing Products for the duration until expiration. What I like about this control is that the Server and not the Client is instructing the duration for which the data is valid and can be cached. Client's in a Client-Server environment that decide to cache data based of non-functional requirements is a bad way to cache IMO. The duration logic should IMO emanate from the service or server.

Using the "Expires" header the server can tell the client to cache the data till a particular date or provide a time duration to cache. The former can be a problem if the client and server clocks are not in sync. For example, the server tells the client to cache a piece of data till Dec 20th, 2012. However, the Client clock is 10 mins behind the server. So although the data on the client has not expired, the server data has. For this reason, setting a duration for expiration via a time duration such as 10 mins will allow both Client/Server to be in sync regarding expiration of the cache.

What about a case when caching is recommended on the client but there is a certain amount of volatility involved with the data. For example, lets say we have an OrderClient that GETs order information about a particular order from the server. The Order information could potentially be updated by a user subsequently, for example, adding a new line item to the Order. In such a case one could avail the Conditional GET features of HTTP to obtain new payload only if the data cached by the Client is stale. The server determines whether the data has changed between the last time the client requested the payload and either provides the entire data or responds back with a HTTP status of 304, indicating UN-Modified payload. The client in turn can in turn as a result of a 304 returned from the server, respond the consumer with the data it has previously cached. This reduces the amount of data transferred between client and server and thus alleviates network bandwidth utilization. Conditional HTTP GET can be availed using either Etags or Last-Modified header attributes.

As an example of the above, let us look at a Jersey, JAX-RS example. In the example, we have two clients, a ProductClient that obtains information about Products and an OrderClient used to manage the life cycle of an Order. The Product Client will cache the Products until the time has come to re-fetch the products due to expiration while the OrderClient will cache the payload obtained an issue a Conditional GET to only obtain the payload if the data has changed on the server since its last request.

The ProductsResource as shown below for the sake of demonstration, sets the Products to expire 3 seconds after its invocation:
@GET 
@Produces("application/json") 
public Response getProducts() {
   ...
   ProductListDto productListDto = new ProductListDto(productDtos);
   Response.ResponseBuilder response = Response.ok(productListDto).type(MediaType.APPLICATION_JSON);

   // Expires 3 seconds from now..this would be ideally based 
   // of some pre-determined non-functional requirement.
   Date expirationDate = new Date(System.currentTimeMillis() + 3000);
   response.expires(expirationDate);

   return response.build();
}

The OrderResource on the other hand based of an etag determines if the order has been modified since the last GET request by the client and returns back a status of 304 or the entire order body as shown below:

@GET
@Produces("application/xml")
public Response getOrder(@Context HttpHeaders hh, @Context Request request) throws OrderNotFoundException {
 Order order = orderService.getOrder(orderId);
 
LOG.debug("Checking if there an Etag and whether there is a change in the order...");

 EntityTag etag = computeEtagForOrder(order);
 Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(etag);

 if (responseBuilder != null) {
     // Etag match
    LOG.debug("Order has not changed..returning unmodified response code");
    return responseBuilder.build();
 }
 
 LOG.debug("Returning full Order to the Client");
 OrderDto orderDto = (OrderDto) beanMapper.map(order, OrderDto.class);

 responseBuilder = Response.ok(orderDto).tag(etag);

 return responseBuilder.build();
}



From the Perspective of the ProductClient, it looks to see whether the cached data has expired before issuing a new request to the server as shown below:

public ProductListDto getProducts() {
  // Key into the cache
  String path = resource.getURI().getPath();
  CacheEntry entry = CacheManager.get(path);
  ProductListDto productList = null;
  if (entry != null) {
    LOG.debug("Product Entry in cache is not null...checking expiration date..");

    Date cacheTillDate = entry.getCacheTillDate();
    Date now = new Date();

    if (now.before(cacheTillDate)) {
      LOG.debug("Product List is not stale..using cached value");

      productList =  (ProductListDto) entry.getObject();
    } 
    else {
      LOG.debug("Product List is stale..will request server for new Product List..");
    }
   }

   if (productList == null) {
     LOG.debug("Fetching Product List from Service...");
     ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);

     if (response.getResponseStatus().equals(Status.OK)) {
       productList = response.getEntity(ProductListDto.class);
       String cacheDate = response.getMetadata().getFirst("Expires");
       
       if (cacheDate != null) {
         Date ccDate;

         try {
           ccDate = DATE_FORMAT.parse(cacheDate);
           entry = new CacheEntry(productList, null, ccDate);
           CacheManager.cache(path, entry);
         }
         catch (ParseException e) {
           LOG.error("Error Parsing returned cache date..no caching will occur", e);
         }
       }
     } 
     else {
       throw new RuntimeException("Error Getting Products....");
     }
  }
  return productList;
}

The Order Client on the other hand uses the etag and sends that as part of every request to the server as shown below:

public OrderDto getOrder(Long orderId) throws OrderNotFoundException, IOException {
  try {
    String path = resource.path(orderId.toString()).getURI().getPath();

    CacheEntry entry = CacheManager.get(path);
    Builder wr = resource.path(orderId.toString()).accept(MediaType.APPLICATION_XML);

    if (entry != null && entry.getEtag() != null) {
     // Set the etag
      wr.header("If-None-Match", entry.getEtag().getValue());
    }

    ClientResponse response = wr.get(ClientResponse.class);

    if (response.getResponseStatus().equals(Status.NOT_MODIFIED)) {
      LOG.debug("Order has not been modified..returning Cached Order...");
      return (OrderDto) entry.getObject();
    }
    else if (response.getResponseStatus().equals(Status.OK)) {
      LOG.debug("Obtained full Order from Service...Caching it..");
      OrderDto dto = response.getEntity(OrderDto.class);
      CacheManager.cache(path, new CacheEntry(dto, response.getEntityTag(), null));

      return dto;
    }
   else {
     LOG.debug("Order not found on server...removing from cache");
     CacheManager.remove(path);
     throw new UniformInterfaceException(response);
   }
  }
  catch (UniformInterfaceException e) {
    if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
      throw new OrderNotFoundException(e.getResponse().getEntity(String.class));
    }
    throw new RuntimeException(e);
  }
}

Seeing the above in action, for the Products; we obtain Products in the first call, this should result in caching of the same, the second request executed immediately after the first should obtain the cached Products. Sleeping for sometime will allow the data to become stale and a subsequent request should re-fetch the data. The logs when the tests are run look like:

1 Request, cache Products:
20:37:33 DEBUG - com.welflex.client.CacheManager.cache(14) | Caching Object with key [/IntegrationTest/products]

2. Request, Product Cache still good:
20:37:33 DEBUG - com.welflex.client.CacheManager.get(19) | Getting Object from Cache for Key:/IntegrationTest/products
20:37:33 DEBUG - com.welflex.client.ProductClientImpl.getProducts(49) | Product Entry in cache is not null...checking cache till date
20:37:33 DEBUG - com.welflex.client.ProductClientImpl.getProducts(54) | Product List is not stale..using cached value

3. Request, Products have expired:
20:37:43 DEBUG - com.welflex.client.CacheManager.get(19) | Getting Object from Cache for Key:/IntegrationTest/products
20:37:43 DEBUG - com.welflex.client.CacheManager.get(21) | Object in Cache for Key [/IntegrationTest/products] is :com.welflex.client.CacheEntry@1bf3d87
20:37:43 DEBUG - com.welflex.client.ProductClientImpl.getProducts(49) | Product Entry in cache is not null...checking cache till date
20:37:43 DEBUG - com.welflex.client.ProductClientImpl.getProducts(57) | Product List is stale..will request server for new Product List..
20:37:43 DEBUG - com.welflex.client.ProductClientImpl.getProducts(62) | Fetching Product List from Service...


From the Order Client Perspective, the first request to obtain the order results in the Order being cached with the etag. When a subsequent request is sent, the server only responds back with a status of 304, i.e, un-modified and then the Order Client responds back with the cached copy. After this second request, the order is updated and the etag is no longer valid therefore a subsequent GET of the order results in the full order being fetched and re-cached as shown below:

1. First time Order is retreived, Order is cached:
Retrieving the order...
22:33:13 DEBUG - com.welflex.client.CacheManager.get(19) | Getting Object from Cache for Key:/IntegrationTest/orders/3443274629940897628
22:33:13 DEBUG - com.welflex.client.OrderClientImpl.getOrder(68) | Obtained full Order from Service...Caching it..

2. Second Request, Order not changed on Server, 304 returned to Client:
22:33:13 DEBUG - com.welflex.order.rest.resource.OrderResource.getOrder(68) | Order Resource 22:33:13 DEBUG - com.welflex.order.rest.resource.OrderResource.getOrder(79) | Order has not changed..returning unmodified response code
22:33:13 DEBUG - com.welflex.client.OrderClientImpl.getOrder(64) | Order has not been modified..returning Cached Order...

3. Issue a PUT to update the Order, thus changing it:
Updating the order...
22:33:13 DEBUG - com.welflex.order.rest.resource.OrderResource.updateOrder(106) | Enter Update Order, Id=3443274629940897628

4. Retrieve the Order the etag should no longer be valid:
Retrieving the order..should not obtained cached copy...
22:33:13 DEBUG - com.welflex.order.rest.resource.OrderResource.getOrder(73) | Checking if there an Etag and whether there is a change in the order...
22:33:13 DEBUG - com.welflex.order.rest.resource.OrderResource.getOrder(83) | Returning full Order to the Client
22:33:13 DEBUG - com.welflex.client.OrderClientImpl.getOrder(68) | Obtained full Order from Service...Caching it..


Attached HERE is the sample Maven Jersey JAX-RS sample that will allow you to witness the above. The caching implemented is primitive at best and the attached code is only an EXAMPLE. One could easily delegate the caching to some caching framework such as ehcache, oscache, jcs etc. One can even potentially get more exotic and think of Aspects that will intercept calls to GET and transparently provide the caching.

To execute the example, from the command line, at the root of the project, execute a "mvn install". Note that one needs JDK5.X+ in order to execute the code.

Caching is a very critical feature of REST. Without using the same is like saying one is doing RES without the T. As always, if a reader of this blog has any comments, I'd appreciate the same. If I am wrong, I would like comments on the same as that will help me improve..or else forever hold ur breath :-) If you cannot run the example, ping me...

Saturday, November 22, 2008

Home town of the Boss, jax-rs, jersey, spring, maven

I have previously tried jax-rs implementations by Restlet and JBoss RESTEasy. You can find the following same at :


One implementation that I had been postponing was Sun's RI, i.e., jersey. Trying to save 'hopefully' the best for last ;-). The name of the implementation has a part of the Boss's town of birth after all! Born in the USA! I am not born in the USA, but love it as much as my own country and is my home away from home! Moving on...

As before, I tweaked the simple Order Web Service example to use jersey.
  • Support for a Client API to communicate with Rest Resources.
  • Very Easy Spring integration.
  • Sun's RI, i.e., from the source
  • Support for exceptions
  • Very good support for JSON Media Type
  • Maven
  • Good set of examples
  • Automatic WADL generator
  • IOC
  • Embedded deployment using Grizzly
  • Filters on client and server side
  • Utilities for working with URI
One of the things that has impressed me about jersey is their out of the box JSON support. Being able to support JSON format without having to create a new javax.ws.rs.ext.Provider is rather convenient. By default the JSON convention is JSONJAXBContext.JSON_NOTATION. One can quite easily change the same to use Jettison or Badgerfish convention.

I was easily able to enable JSON representation for my Product resource by defining the Product data transfer objects with JAXB annotations, adding a @Produces("application/json") in the ProuductsResource class and ensuring that I have the jersey-json jar in my build.



ProductDTO.java
@XmlType(name = "product")

@XmlRootElement(name = "product")
public class ProductDTO implements Serializable {

....
}

ProductListDTO.java
@XmlRootElement(name = "productList")

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "products", propOrder = {"productDTOs"})

public class ProductListDTO implements Iterable<ProductDTO> {
....
}

ProductsResource.java
@GET @Produces("application/json")
public ProductListDTO getProducts() {

...
}






<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>

<version>${jersey-version}</version>
</dependency>




There is an excellent tutorial, Configuring JSON for RESTful Web Services in Jersey 1.0 by Jakub Podlesak that you can read more about.

To support Spring integration, the web applications deployment descriptor has been modified to use the Jersey Spring Servlet. All the Spring managed beans defined by the @Component, @Service, @Resource annotations are automatically wired.



<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>

</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<servlet>
<servlet-name>JerseySpring</servlet-name>

<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>





On the Client Side, I was pleasantly surprised by the ease in which I could invoke REST calls. As mentioned in my RESTEasy blog, jax-rs has no requirements for a client side specifications. However, those implementations that do provide one will be the ones to gain more adoption IMO. The jersey implementation provides a DSL like implementation to talk to the services. Very "VERBY", if there is no word in the dictionary like "VERBY", I stake my claim on the same :-). I modified the client from the RESTEasy implementation of the Order Service to use Jersey Client support as follows :



public class OrderClientImpl implements OrderClient {

private final WebResource resource;

/**
* @param uri Server Uri
*/

public OrderClientImpl(String uri) {

ClientConfig cc = new DefaultClientConfig();
// Include the properties provider

Client delegate = Client.create(cc);
// Note that the Resource has been created here

resource = delegate.resource(uri).path("order");

}

public OrderDTO createOrder(OrderDTO orderDTO) throws IOException {

return resource.accept(MediaType.APPLICATION_XML).type(MediaType.APPLICATION_XML)

.post(OrderDTO.class, orderDTO);
}

public void deleteOrder(Long orderId) throws OrderException {

resource.path(orderId.toString()).type(MediaType.APPLICATION_XML).delete();
}

public OrderDTO getOrder(Long orderId) throws OrderNotFoundException, IOException {

try {
return resource.path(orderId.toString())

.type("application/xml").accept("application/xml").get(OrderDTO.class);

} catch (UniformInterfaceException e) {

if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {

throw new OrderNotFoundException(e.getResponse().getEntity(String.class));

}
throw new RuntimeException(e);
}
}

public void updateOrder(OrderDTO orderDTO, Long id) {

resource.path(id.toString()).type("application/xml").put(orderDTO);

}
}





As you can see from the above the use of the Jersey client API is rather straight forward and intuitive. One point to note is that Jersey provides a Exception framework for easily handling common exception cases like 404 etc. There are some classes that enable this support, com.sun.jersey.api.NotFoundException and com.sun.jersey.api.WebApplicationException that one can use. As I did not want to tie my data transfer object maven project to jersey in particular, I did not use jersey exceptions but instead stuck with my custom Exception Provider.

Running the Example:
The Example can be downloaded from HERE.

This example has been developed using maven 2.0.9 and jdk1.6.X. Unzip the project using your favorite zip tool, and from the root level execute a "mvn install". Doing so will execute a complete build and run some integration tests. One interesting thing to try is to start the jetty container from the webapp project using "mvn jetty:run" and then access the jersey generated WADL from http://localhost:9090/SpringTest-webapp/application.wadl

Now that you have the WADL, you should be able to use tools like SOAPUI or poster-extension (a Firefox plugin) to test your RESTful services as well.

It would be interesting to see how wadl2java and the maven plugin provided there in can be used to create a separate client project to talk to the web services.

The jersey web site has pretty good documentation about using jax-rs. It is not thorough but getting there. There are a good set of examples that one can download and try as well. It is my understanding that NetBeans has good tooling support for jersey as well.

So now that I have tried Restlet, different jax-rs implementations and jax-ws what would be my direction if I were to take a decision on what to use for my SOA project? Some food for my next blog :-)

Again, if the example fails to run, ping me...Enjoy!