Its been some time since I posted regarding REST. Restlet 2.0 is on its way, I figured its time I updated the example I had created some time ago to a Restlet 2.0 milestone release. If you are continuing to read this BLOG, I would recommend a read of the Restlet 1.1 BLOG I mentioned prior to reading this one.
So whats new with Restlet 2.0? A read of the Resource API refactoring page is recommended.
One of the major features that I liked about JAX-RS over the core Restlet API while working with the former is that it is annotation driven versus class driven. Automatic content negotiation and the calling of the appropriate method in your Server Resource class for different Content-Types is really best left to the container. A method intended to serve content should not have if/else blocks for different content, something one would need to do in Restlet 1.1. Restlet 2.0 seems to take the same into consideration by providing the "best of both worlds"
There were major changes that occurred on Restlet version 1.2 that the team at Restlet decided to change the release to a 2.0. Further details of the change can be obtained at http://blog.noelios.com/2009/05/27/restlet-2-0-m3-released/
Restlet 2.0 has a major change in the way Resources are viewed. In particular a split into a ClientResource and a ServerResource. Details of the same can be viewed on the Restlet Resource API refactoring WIKI page. In addition, there has been a refactoring of certain core classes to different packages. I also noticed that the Restlet API and Restlet engine are shipped as a single jar org.restlet.jar. I could not find the com.neolios packages any more. Finally, there is a jse and j2ee edition of Restlet available.
So, eager to try out the Restlet 2.0 annotations, I reworked my example to use Restlet 2.0 while simplifying the Spring integration present.
Compared to my previous example, I have reduced my Spring applicationContext.xml to be more thinner by using a more annotated approach.
<context:component-scan base-package="com.welflex"> </context:component-scan> <!-- Spring Application. Note there are no mapping of resources here --> <bean id="application" class="org.restlet.Application"> <property name="name" value="orderApplication"></property> <property name="root" ref="root" /> <!-- Added to handle Exceptions centrally --> <property name="statusService" ref="applicationStatusService"/> </bean> <bean id="root" name="router" class="com.welflex.order.rest.SpringBeanRouter"/> <bean id="beanMapper" class="net.sf.dozer.util.mapping.DozerBeanMapper"> <property name="mappingFiles"> <list> <value>dozerBeanMapping.xml</value> </list> </property> </bean>The OrdersResource has changed to:
@Component(value = "/orders") @Scope(value = BeanDefinition.SCOPE_PROTOTYPE) public class OrdersResource extends ServerResource { ...... @Post public JaxbRepresentation<OrderDTO> createOrder(Representation rep) { ... return new JaxbRepresentation<OrderDTO>(MediaType.APPLICATION_XML, dto); } }Note that the OrderResource class now extends ServerResource unlike in my previous example which extended the Resource class. Also note that the mapping of the resources to paths are defined in the OrderResource class itself unlike in the xml file in my previous example. The POST method, called createOrder() accepts and returns XML. The return type is a JaxbRepresentation. If we desired, we could have another method in the code that accepts JSON content and returns JSON content that is titled createJSONRepresentation(). Depending on the content type requested, the appropriate method would be called. This is much better than having an if/else block in an acceptRepresentation(Variant v) method. Take a look at the Products Resource, that returns a JSON representation when a GET method is invoked. Note that this method will only return JSON content.
@Component(value = "/products") @Scope(value = BeanDefinition.SCOPE_PROTOTYPE) public class ProductsResource extends ServerResource { ...... @Get public JsonRepresentation getProducts() { ..... return new JsonRepresentation(jsonString); } }Pretty nice, I would say. To spoil the party, one of the things I liked about the JAXRS implementations was that I could annotate a Resource with the media type it supported and if it were a JAXB object it would automatically marshall the XML, if it were JSON, it would automatically marshal the same as well. The same can be accomplished in Restlet by using a Converter Service that converts a java object to and from a media type. There are plans for adding JAXB converters amongst others in 2.0 M5. In short, by using a Converter Service, I would like the createOrder() method to have the following signature, i.e., method returns a JAXB Object, however the container knows how to marshal that into XML back to the caller:
@Post("xml":"xml") public OrderDTO createOrder(OrderDTO inOrder) { return outOrderDTO; }So what about the ClientResource class? How does that work? Well with Restlet 2.0, one can continue to use the Restlet Client class or use the ClientResource class. The ClientResource is not thread safe. An example of how the ClientResource can be used is shown below in the OrderClientImpl class:
public class OrderClientImpl implements OrderClient { private static final String ORDERS_RESOURCE_URI = "/orders"; private final String baseUri; public OrderClientImpl(String baseUri) { this.baseUri = baseUri; } public OrderDTO createOrder(OrderDTO order) throws IOException { ClientResource ordersResource = new ClientResource(baseUri + ORDERS_RESOURCE_URI); try { return ordersResource.post(order, OrderDTO.class); } catch (ResourceException e) { throw new IOException(e); } } public void updateOrder(OrderDTO order) throws OrderException { JaxbRepresentation<OrderDTO> orderCmd = new JaxbRepresentation<OrderDTO>( MediaType.APPLICATION_XML, order); ClientResource ordersResource = new ClientResource(baseUri + ORDERS_RESOURCE_URI + "/" + String.valueOf(order.getOrderId())); try { ordersResource.put(orderCmd); } catch (ResourceException e) { throw new OrderException("Order Update Failed", e); } } public OrderDTO getOrder(Long orderId) throws OrderNotFoundException, IOException { ....} } public void deleteOrder(Long orderId) throws OrderException {....} }I quite like the use of ClientResource versus the Restlet Client as I feel its more RESTful, i.e., talking to a Resource using HTTP verbs. However, again we witness the JAXB representation creeping in as we saw with the service due to the lack of a converter. I decided to create my own Converter for JAXB to see if I can get it to work and simplify the code base. One of the things I wanted to add just for my pleasure is a way to propagate server stack traces to the client. This is NOT what one would typically include in a representation of a resource as one would not want clients getting details of the working of a server, however, as this is my playground, anything goes ;-). After some tinkering, the following is my Jaxb Converter, it assumes that all JAXB root level objects have the annotation @XmlRootElement:
public class CustomXmlConverter extends XmlConverter { ...... @Override public List<Class<?>> getObjectClasses(Variant source) { List<Class<?>> result = super.getObjectClasses(source); result = addObjectClass(result, JaxbRepresentation.class); result = addObjectClass(result, Object.class); return result; } @Override public List<VariantInfo> getVariants(Class<?> source) { List<VariantInfo> result = super.getVariants(source); if (source.getAnnotation(XmlRootElement.class) != null || source.isAssignableFrom(JaxbRepresentation.class)) { result = addVariant(result, VARIANT_APPLICATION_ALL_XML); result = addVariant(result, VARIANT_APPLICATION_XML); result = addVariant(result, VARIANT_TEXT_XML); } return result; } @SuppressWarnings("unchecked") @Override public <T> T toObject(Representation source, Class<T> target, UniformResource resource) throws IOException { ..... if (target.getAnnotation(XmlRootElement.class) != null) { return new JaxbRepresentation<T>(source, target).getObject(); } throw new IllegalStateException("Should not have got here"); } @Override public Representation toRepresentation(Object source, Variant target, UniformResource resource) throws IOException { Representation result = null; if (source.getClass().getAnnotation(XmlRootElement.class) != null) { result = new JaxbRepresentation<Object>(source); } else { result = super.toRepresentation(source, target, resource); } return result; } ..... }Great, so we have a Converter, how do we make Restlet use this converter for XML. In my common maven module (shared between client and web), I define a file in META-INF/services/org.restlet.engine.converter.ConverterHelper, and in that file, I have a single entry defining my custom converter. Now when the client and service code use the common library, they will use the Custom Converter I have defined for JAXB. Thus, to illustrate the change, my new resources for Orders and Order are shown below:
@Component(value = "/orders") @Scope(value = BeanDefinition.SCOPE_PROTOTYPE) public class OrdersResource extends ServerResource { ..... @Post("xml") public OrderDTO createOrder(OrderDTO dto) { .... return createdOrderDto; } ... } @Component(value = "/orders/{id}") @Scope(value = BeanDefinition.SCOPE_PROTOTYPE) public class OrderResource extends ServerResource { @Put(value = "xml") public void updateOrder(OrderDTO orderDTO) { try { String id = (String) getRequest().getAttributes().get("id"); orderDTO.setOrderId(Long.parseLong(id)); persistOrder(orderDTO); } catch (RuntimeException e) { getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); throw new OrderException("Failed to update an order", e); } } ..... }We are no longer returning Representations in the above code. Quite similar to JAXRS, wouldn't you say ;-)? On the Client end, the OrderClientImpl no longer deals with Representations but only OrderDTOs as shown below:
public class OrderClientImpl implements OrderClient { .... public OrderDTO createOrder(OrderDTO order) throws OrderException { ClientResource ordersResource = new ClientResource(baseUri + ORDERS_RESOURCE_URI); try { return ordersResource.post(order, OrderDTO.class); } catch (ResourceException e) { throw new OrderException("Error Creating an Order", e); } } public void updateOrder(OrderDTO order) throws OrderException { ClientResource ordersResource = new ClientResource(baseUri + ORDERS_RESOURCE_URI + "/" + String.valueOf(order.getOrderId())); try { ordersResource.put(order); } catch (ResourceException e) { throw new OrderException("Order Update Failed", e); } } }I like the simplicity of the above and cannot wait to see the release of Restlet 2.0 that has the additional converters built in. For those interested, I have a maven project example available for download. The project provided uses the custom JAXB converter. To execute the project, one would need to use JDK1.6.X and maven 2.0.9 or higher. To view an integration test in action execute, "mvn install" from the root level of the project. The result should be something like:
------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.welflex.order.IntegrationTest log4j:WARN No appenders could be found for logger (com.welflex.client.ProductClientImpl). log4j:WARN Please initialize the log4j system properly. Aug 28, 2009 8:59:55 PM org.restlet.engine.http.StreamClientHelper start INFO: Starting the HTTP client 20:59:55 DEBUG - com.welflex.order.rest.ProductsResource.getProducts(44) | Getting Products in JSON Format Number of Available Products:3 Product Id:663123 Product Id:9912123 Storing the order... Aug 28, 2009 8:59:55 PM org.restlet.engine.http.StreamClientHelper start INFO: Starting the HTTP client 20:59:56 DEBUG - com.welflex.order.rest.OrdersResource.createOrder(45) | Call to post an order:.......... Updating the order... Order successfully Retrieving the order... Deleting the order.. 20:59:56 DEBUG - com.welflex.order.rest.OrderResource.getOrder(86) | Requested order with id:5499731937442305515 20:59:56 DEBUG - com.welflex.order.rest.ApplicationStatusService.getStatus(21) | In Order Status Service :class org.restlet.resource.ResourceException Expected Order to not be found. Look at the server stack we got back com.welflex.exception.OrderNotFoundException: Error obtaining Order at com.welflex.client.OrderClientImpl.getOrder(OrderClientImpl.java:60) at com.welflex.order.IntegrationTest.testLifeCycle(IntegrationTest.java:131) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)More on Restlet 2.0 once its released. For now, maybe I should probably look toward cleaning up the JaxbConverter and submitting it to Restlet or better still head in the search of kindred spirits, if you know what I mean ;-)
8 comments:
Do you have a Sample application which has spring and restlet integration with serverresources ?
The application uses Spring with Server Resources. In addition it is using a patch of the SpringBeanRouter that I found on the web as the default one that came with Restlet with the Mile stone release did not work. I beleive they are in the process of fixing the same.
Hi Sanjay,
I need integration of Spring 2.5, Restlet 2.0(JAX-RS) along with configuration files, I want to use SpringRouter along with JAXRS Resources and application. Want to use annotation in resouce files for Path etc......
Thanks
Will you please provide me the code without maven?
@Rajkumar, the source provided has nothing to do with maven. It only uses maven to obtain the dependencies. So, you should be able to convert the same to a non-maven project.
Sanjay is there a way to have relative paths or to set a guard?
Thanks your example! Great help
Not sure I understand what you mean by relative paths. An example of using Guards should be available on the Restlet WIKI http://wiki.restlet.org/docs_2.0/13-restlet/27-restlet/46-restlet.html
I guess my question was more for Spring.
When you name a bean starting with backslash the class is automatically attached to that URL. Example:
@Component("/test")
If I want to put a guard in "/test/users" and keep using URL definition in the classes I should use something like relative paths.
I've searched a lot and found not answer and I guess it can't be done. But it's OK, I can use a SpringRouter and set it in the applicationContext.xml
Post a Comment