May 15th, 2012

Aplicaciones RESTful con Apache CXF (Parte 1/2).

Juan Pablo Warmerdam

¿Cuántas veces encontramos la necesidad de exponer como servicios distintas funciones de nuestras aplicaciones?  O peor aún, ¿cuántas veces nos vemos en la necesidad de consumir este tipo de servicios?

Es en estas situaciones donde solemos hacer uso de los Web Services, y en particular SOAP y sus estándares asociados(WSDL, WS-Security, WS-Notification, etc), que aunque cumplen con su propósito suelen traer bajo el brazo una serie de inconvenientes que no se pueden obviar (complejidad extra – ya sea en los XML para la comunicación, como en los clientes-, perdida de performance, dificultad para debbugear, etc.) .  Muchos de ustedes, seguramente, habrán usado CXF para configurarlos, y aunque con eso es probable que se hayan evitado ciertas dificultades en la configuración, el resto de los aspectos “negativos” no desaparecen tan rápido.

Es por esto que en este post quiero mostrarles como utilizar CXF para exponer o consumir servicios sin agregar complejidad adicional utilizando servicios RESTful.

Una brevísima definición

La Transferencia de Estado Representacional (Representational State Transfer) o REST es una técnica de arquitectura software para sistemas hipermedia distribuidos, como la World Wide Web.  El término se originó en el año 2000, en una tesis doctoral sobre la web escrita por Roy Fielding, uno de los principales autores de la especificación del protocolo HTTP.

Si bien el término REST se refería originalmente a un conjunto de principios de arquitectura , en la actualidad se usa en el sentido más amplio para describir cualquier interfaz web simple que utiliza XML y HTTP, sin las abstracciones adicionales de los protocolos basados en patrones de intercambio de mensajes como el protocolo SOAP.

Un concepto importante en REST es la existencia de recursos, que pueden ser accedidos utilizando un identificador global (URI). Para manipular estos recursos, los componentes de la red (clientes y servidores) se comunican a través de una interfaz estándar (HTTP) e intercambian representaciones de estos recursos (los archivos que se descargan y se envían).

Los sistemas que siguen los principios REST se llaman con frecuencia RESTful  y en general se adhieren a una serie de principios:

  • Darle a cada recurso un ID (un URI)
  • Usar métodos estándar (GET, PUT, POST, etc.)
  • Tener múltiples representaciones para los recursos (XML, JSON, etc)
  • Mantener una comunicación stateless

Esto es, cada uno de nuestros servicios será convertido en un recurso independiente el cual podrá ser accedido por una URI especifica por medio de HTTP estándar sin mantener estado de la comunicación/uso de los mismos.

CXF y JAX-RS

Apache CXF es un framework  open source que permite el desarrollo de servicios para diferentes protocolos (SOAP, XML/HTTP, RESTful HTTP, o CORBA) sobre diferentes transportes (HTTP, JMS o JBI), para ello utiliza una serie de APIs, como JAX-WS y JAX-RS, etc.

JAX-RS es la librería que se encarga de estandarizar la forma  en que los servicios RESTful son implementados en Java.

Configuración básica

Antes de empezar a ver en detalle como publicar (o consumir) nuestros servicios,  veamos la configuración básica de CXF/JAX-RS usando Spring.

En el archivo web.xml, necesitamos registrar uno o más CFXServlet(s) y linkearlo a un applicationContext.xml


<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/applicationContext.xml</param-value>
	</context-param>

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

	<servlet>
		<servlet-name>CXFServlet</servlet-name>
		<display-name>CXF Servlet</display-name>
		<servlet-class>
			org.apache.cxf.transport.servlet.CXFServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

En el archivo applicationContext.xml configuramos nuestros servicios:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jaxrs="http://cxf.apache.org/jaxrs"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

  <jaxrs:server id="customerService">
    <jaxrs:serviceBeans>
      <ref bean="customerBean" />
    </jaxrs:serviceBeans>
  </jaxrs:server>

  <bean id="customerBean" class="demo.jaxrs.server.CustomerService" />
</beans>

 

Creación de recursos

Para poder acceder a nuestros servicios, necesitamos convertirlos en recursos que puedan ser accedidos por medio de una URI, para ello, usaremos anotaciones provistas por JAXRS.

Veamos todo con un ejemplo:

package demo.jaxrs.server;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

@Path("/customerservice/")
@Produces("application/xml")
public class CustomerService {

    public CustomerService() {
    }

    @GET
    public Customers getCustomers() {
        ......
    }

    @GET
    @Path("/customers/{id}")
    @Produces("application/json")
    public Customer getCustomer(@PathParam("id") String id) {
        ......
    }

    @PUT
    @Path("/customers/{id}")
    @Consumes("application/xml")
    public Response updateCustomer(@PathParam("id") Long id, Customer customer) {
        ......
    }

    @POST
    @Path("/customers")
    public Response addCustomer(Customer customer) {
        ......
    }

    @DELETE
    @Path("/customers/{id}/")
    public Response deleteCustomer(@PathParam("id") String id) {
        ......
    }

    @Path("/order/{orderId}/")
    public Order getOrder(@PathParam("orderId") String orderId) {
       ......
    }
    @GET
    @Path("/order/{orderid}/{search:.*}")
    public Item findItem(@PathParam("id") Long customerId, 
                         @PathParam("orderid") Long orderId,
                         @PathParam("search") String searchString,
                         @PathParam("search") List<PathSegment> searchList) {
        ......
    }

}

Se puede ver a simple vista que cualquier clase puede ser convertida en un servicio al utilizar anotaciones .

La anotación @Path se aplica a clases o métodos y el valor del mismo es un path de URL relativo que acepta el uso de templates de URI y puede incluir el uso de expresiones regulares. Cuando no se define en un método, se toma desde la clase, por ejemplo:  CustomerService se encargara de responder servicios que comiencen con /customerservice.  En este caso, cuando se invoque a http://localhost/customerservice, el método getCustomers() será el seleccionado. Los métodos updateCustomer(), deleteCustomer() y addCustomer() se usarán para responder pedidos POST, PUT y DELETE que comiencen con http://localhost/customerservice/customer, mientras que getOrder()  será encargado de responder a recursos que tengan el /customerservices/order/.

En algunos métodos del ejemplo, se usa un template {id} como parte del @Path. Además, el método findItems() utiliza una expresión regular. Esto permite que pedidos como ‘GET /customerservices/order/2/price/2000/weight/2′,  sean respondidos por el método  findItems().

En este caso, List<PathSegment> puede ser utilizado para obtener todos los segmentos del path ‘ price/2000/weight/2′ capturados por la expresión regular.

La anotación @Produces se utiliza para especificar el formato de la respuesta. Cuando no se define en un método, lo hereda de su clase. Lo mismo aplica para @Consumes, solo que se refiere al formato del body que se utiliza en la entrada.

En el ejemplo, el método getCustomers() hereda la anotación @Produces de su clase, mientras que getCustomer() la sobreescribe con su propio valor.

En la segunda parte: Pasaje de parámetros, sub-recursos, manejo de excepciones, etc..

Dejar un comentario