martes, 4 de enero de 2011

RESTful parte 3: Manejando colecciones de objetos y objetos complejos

Comenzamos este año nuevo con la continuación del (creo yo) más esperado tema de tutorial: RESTful. Y esta vez hablaremos sobre el manejo de colecciones y objetos complejos. Por ahora será de manera básica y veremos poco a poco cómo hacerlo más y más complejo.

Manejo de colecciones

Para comenzar, tomaremos el mismo proyecto que vimos en el último post y agregaremos otro recurso llamado PersonaResource con el path apuntando por /listaPersonas.
¿Cómo hacer esto? Bien fácil:
  1. Crear una clase llamada PersonaResource dentro del paquete com.apuntesdejava.rest
  2. Agregar las siguientes anotaciones al inicio de la declaración de la clase:
    @Stateless
    @Path("/listaPersonas")
Es decir, al final tendremos el código para la clase de la siguiente manera:
package com.apuntesdejava.rest;

import javax.ejb.Stateless;
import javax.ws.rs.Path;

@Stateless
@Path("/listaPersonas")
public class PersonasResource {
}

Guardando objetos en la colección

Esto es realmente fácil. Es como cualquier método en Java que recibe un objeto y lo guarda en el arreglo:
//...
    static List<Persona> personas = new ArrayList<Persona>();

    @POST
    @Consumes({"application/xml", "application/json"})
    public Response guardar(Persona p) {
        p.setIdPersona(personas.size() + 1); //se le autoasigna un id al objeto
        personas.add(p);
        return Response.ok(p).build();
    }
//...
Sabemos que el objeto vendrá en formato JSON o XML. Si tenemos dudas, probemos con el "Test RESTful Web services" de NetBeans.

Probaremos enviando la siguiente cadena en el método POST.
{"nombre_persona":"Juan Perez",
"edad":"35",
"trabajador":"true",
"fechaNacimiento":"1976-01-01"}

Y veremos que en la respuesta nos devuelve con el ID de la persona autogenerada.

<?xml version="1.0" encoding="UTF-8"?>
   <persona id_persona="1">
       <fechaNacimiento>1976-01-01T00:00:00-05:00</fechaNacimiento>
       <nombre_persona>Juan Perez</nombre_persona>
       <sexo>0</sexo>
       <trabajador>true</trabajador>
   </persona> 

Como se puede en esta imagen, está el envío de la data, y la respuesta del servidor.
Esto es bastante fácil, porque es lo mismo que se vió en el anterior post. Ahora veremos como se obtiene una lista.

Obteniendo objetos en la colección

Lo bueno de utilizar RESTful en Java es que no requiere declarar métodos extraños ni clases adicionales (hasta ahora). Entonces, si queremos que nuestra clase tenga un método que devuelva una lista de objetos de una lista en base a un criterio (por ejemplo, los que tengan uno determinado texto), podría ser como el que sigue:
    public List<Persona> buscar(String nombre) {
        List<Persona> lista = new ArrayList<Persona>();
        for (Persona persona : personas) {
            if (persona.getNombre().indexOf(nombre) >= 0) {
                lista.add(persona);
            }

        }
        return lista;
    }
¿Cierto? Bien, ahora lo convertiremos en servicio RESTful agregando la anotación GET, además de qué tipos va a devolver.
    @GET
    @Produces({"application/xml", "application/json"})
    public List<Persona> buscar( String nombre) {
        List<Persona> lista = new ArrayList<Persona>();
        for (Persona persona : personas) {
            if (persona.getNombre().indexOf(nombre) >= 0) {
                lista.add(persona);
            }

        }
        return lista;
    }

Pero como va a recibir un parámetro, por método @GET (es decir, como parte del URL.. o query string) entonces hay que darle un nombre usando @QueryParam.

    @GET
    @Produces({"application/xml", "application/json"})
    public List<Persona> buscar(@QueryParam("nombre") String nombre) {
        List<Persona> lista = new ArrayList<Persona>();
        for (Persona persona : personas) {
            if (persona.getNombre().indexOf(nombre) >= 0) {
                lista.add(persona);
            }

        }
        return lista;
    }

Ahora, probemos con el "Test REST" de NetBeans (previo registro de objetos, claro está).


Notemos que el nombre del parametro query no necesariamente tiene que ser el mismo nombre del parámetro del método de Java. Es decir, esto es totalmente válido:
    @GET
    @Produces({"application/xml", "application/json"})
    public List<Persona> buscar(@QueryParam("name") String nombre) {
//...
    }

Objetos complejos

Ahora bien, ya nos podemos imaginar que si permite exportar una simple colección de la manera más simple, también se puede exportar un objeto compuesto (es decir, un objeto que tenga propiedades que son otros objetos).
Pero antes de seguir, quiero recordar que esta parte del tutorial es aún básico. Por ahora no pretendamos poner un objeto que tenga una referencia cíclica, es decir, que tenga una propiedad que es otro objeto y que este tenga otra propiedad que apunte al primer objeto. Sí se puede hacer, pero por ahora no lo veremos porque para ello hay que hacer algunas modificaciones adicionales.

Crearemos una clase llamada Telefono que tendrá las propiedades area y numero.

public class Telefono {

    private String numero;
    private String area;

    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }

    public String getNumero() {
        return numero;
    }

    public void setNumero(String numero) {
        this.numero = numero;
    }
}

Y, en nuestra clase Persona agregaremos una colección de la clase recién creada:

public class Persona {
//....
    private List<Telefono> telefonos;

    public List<Telefono> getTelefonos() {
        return telefonos;
    }

    public void setTelefonos(List<Telefono> telefonos) {
        this.telefonos = telefonos;
    }
//....

Ahora, probemos con la siguiente cadena JSON (no olvidar que se selecciona POST (application/json)) para registrar nuestro objeto:

{
  "nombre_persona":"Carl",
  "telefonos"     :[
                    {"area":"51","numero":"12345"},
                    {"area":"54","numero":"98765"}
                   ]
}

Y al obtener la lista de los objetos, se obtiene sin ningún problema. Ya sea en XML...


... o en JSON...


Si quieres conocer más sobre los formatos de JSON, visita aquí: http://www.json.org/json-es.html

Ya haré un post dedicado únicamente a los clientes de RESTful, tanto como para probar como para hacer una aplicación desktop, javascript, javafx, mobile, etc.

El siguiente post, cómo manejar los java.util.Map y después, los métodos @DELETE y @PUT

El código fuente

Aquí está el infaltable código fuente del proyecto

http://kenai.com/projects/apuntes/downloads/download/PersonaRESTWeb%252FPersonasRESTWeb.tar.gz

Bendiciones!


7 comentarios:

  1. Hola! muchas gracias por tu nuevo post sobre RESTFul. La verdad es que yo tengo problemas con @DELETE, ya que aunque le indique como parámetro un @QueryParam para indicar el ID del objeto que quiero borrar, siempre toma el valor de 0 y nunca el que le paso realmente. Tengo una "solución" usando @PUT y haciendo un borrado lógico (indicando una fecha de borrado en vez de borrar el objeto de bbdd), pero no sé si hay una solución fácil para el @DELETE que no estoy viendo. Muchas gracias y un saludo! Laura

    ResponderEliminar
  2. Hola! Excelente este post de restful, me ha servido enormemente, pero tengo un predicamente, ya cree todos los ws que necesito, con los GET no tengo problema, envio un String y me retornan los objetos que necesito en formato JSON y los recibo bien en el cliente. Pero tengo problema con el POST, deseo enviar a el ws un objeto, yo cree el ws y lo pruebo con el testeador que me genera NetBeans, tomo el objeto, lo convierto en json con la libreria flexJson y la salida la copio y la pego en la interfaz del tester que genera Netbeans y bien, pero no he podido crear un cliente que le envie este objeto al ws, lo envio como objeto y marca error, y lo envio como json (el mismo que pego en el test) y me marca este error.

    Exception in thread "main" com.sun.jersey.api.client.UniformInterfaceException: POST http://localhost:8080/GashTest/resources/auditoria?auditoria=%7B%22class%22:%22com.gash.entidades.Auditoria%22,%... 5%7D returned a response status of 500
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:563)
    at com.sun.jersey.api.client.WebResource.post(WebResource.java:219)
    at werest.ClienteAuditoria.registrar(ClienteAuditoria.java:46)
    at gashagenttest.Main.main(Main.java:57)

    Me puedes regalar una indicación de que puede ser o un link donde encuentre como crear un cliente en java que envíe objetos por post. Muchas gracias, exitos y que sigas creciendo tu blog, mantengo pendiente de él. Pablo

    ResponderEliminar
  3. Hola Laura
    falta una parte que quiero llegar (pero te adelantaste) que cada objeto puede ser accedido desde un url unico, sin parametros query (de esos con ?param1=val1&param2=val2...) algo así como http://... /rest/producto/01

    El método @DELETE se hace a ese url unico. Creo que el siguiente post trataré de este url unico y cómo manejar los @DELETE.

    ResponderEliminar
  4. De acuerdo, entonces esperaré de momento :)

    ResponderEliminar
  5. Ya he encontrado la solución navegando un poco, por si a alguien le interesa: http://docs.jboss.org/resteasy/docs/1.0.0.GA/userguide/pdf/RESTEasy_Reference_Guide.pdf

    ResponderEliminar
  6. Saludos... le agradecería si me indica donde puedo encontrar un cliente para consumir este tipo de servicios que retorna una lista de objetos muchas gracias.

    ResponderEliminar
  7. companero una pregunta por lo que puedo ver cuando haces el get de la lista de empleados pero que la etiqueta que usa es habra alguna posibilidad de cambiarla por otra etiqueta ?

    muchas gracias de manera adelantada

    ResponderEliminar

Si quieres hacer una pregunta más específica, hazla en los foros que tenemos habilitados en Google Groups


Ah! solo se permiten comentarios de usuarios registrados. Si tienes OpenID, bienvenido! Puedes obtener su OpenID, aquí: http://openid.net/