Articles

Blog de Spring

En un post anterior, escribí sobre las capacidades REST que añadimos a la versión 3.0 de Spring @MVC. Más tarde, Alef escribió sobre el uso de la funcionalidad introducida para añadir una vista Atom a la aplicación Pet Clinic. En este post, me gustaría presentar las capacidades del lado del cliente que añadimos en el Hito 2.

RestTemplate

El RestTemplate es la clase central de Spring para el acceso HTTP del lado del cliente. Conceptualmente, es muy similar a la JdbcTemplate, JmsTemplate, y las otras plantillas que se encuentran en el Spring Framework y otros proyectos de la cartera. Esto significa, por ejemplo, que el RestTemplate es seguro para los hilos una vez construido, y que puedes usar callbacks para personalizar sus operaciones.

Métodos de RestTemplate

Los principales puntos de entrada de la plantilla reciben el nombre de los seis métodos HTTP principales:

HTTP RestTemplate
DELETE delete(String, String…)
GET getForObject(String, Class, String…)
HEAD headForHeaders(String, String…)
OPTIONS optionsForAllow(String, String…)
POST postForLocation(String, Object, String…)
PUT put(String, Object, String…)

Los nombres de estos métodos indican claramente qué método HTTP invocan, mientras que la segunda parte del nombre indica lo que se devuelve. Por ejemplo, getForObject() realizará un GET, convertirá la respuesta HTTP en un tipo de objeto de su elección, y devuelve ese objeto. postForLocation hará un POST, convirtiendo el objeto dado en una petición HTTP, y devuelve la cabecera de la respuesta HTTP Location donde se puede encontrar el objeto recién creado. Como puedes ver, estos métodos intentan aplicar las mejores prácticas de REST.

Plantillas URI

Cada uno de estos métodos toma una URI como primer argumento. Ese URI puede ser una plantilla URI, y se pueden usar variables para expandir la plantilla a un URI normal. Las variables de la plantilla se pueden pasar de dos formas: como un array de argumentos de variables String, o como un Map<String, String>. La variante string varargs expande las variables de la plantilla dadas en orden, por lo que


String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

realizará un GET sobre http://example.com/hotels/42/bookings/21. La variante map expande la plantilla en base al nombre de la variable, por lo que es más útil cuando se utilizan muchas variables, o cuando una misma variable se utiliza varias veces. Por ejemplo:


Map<String, String> vars = new HashMap<String, String>();vars.put("hotel", "42");vars.put("booking", "21");String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);

también realizará un GET en http://example.com/hotels/42/rooms/42.

HttpMessageConverters

Los objetos pasados y devueltos por los métodos getForObject(), postForLocation(), y put() y son convertidos a peticiones HTTP y desde respuestas HTTP por HttpMessageConverters. Los convertidores para los principales tipos mime y tipos Java están registrados por defecto, pero también puedes escribir tu propio convertidor y conectarlo a la RestTemplate. ¡En el siguiente ejemplo, le mostraré cómo se hace.

Utilización del RestTemplate para recuperar fotos de Flickr

En lugar de repasar los distintos métodos del RestTemplate, le mostraré cómo utilizarlo para recuperar fotos de Flickr, la aplicación para compartir fotos en línea de Yahoo! Esta aplicación de ejemplo busca en Flickr las fotos que coinciden con un término de búsqueda determinado. A continuación, muestra estas fotos utilizando una sencilla interfaz de usuario Swing. Para ejecutar la aplicación usted mismo, deberá crear una cuenta en Flickr y solicitar una clave API.

Búsqueda de fotos

Flickr expone varias API para manipular su vasta biblioteca de fotos. El método flickr.photos.search te permite buscar fotos, emitiendo una petición GET en http://www.flickr.com/services/rest?method=flickr.photos.search&api+key=xxx&tags=penguins, donde introduces tu clave API y lo que quieres buscar (pingüinos en este caso). Como resultado, obtienes de vuelta un documento XML, describiendo las fotos que se ajustan a tu consulta. Algo así como:


<photos page="2" pages="89" perpage="10" total="881"><photo owner="" secret="a123456" server="2" title="test_04"ispublic="1" isfriend="0" isfamily="0" /><photo owner=""secret="b123456" server="2" title="test_03"ispublic="0" isfriend="1" isfamily="1" /><photo owner=""secret="c123456" server="2" title="test_01"ispublic="1" isfriend="0" isfamily="0" /><photo owner=""secret="d123456" server="2" title="00_tall"ispublic="1" isfriend="0" isfamily="0" /></photos>

Usando el RestTemplate, recuperar dicho documento es bastante trivial:


final String photoSearchUrl = "http://www.flickr.com/services/rest?method=flickr.photos.search&api+key={api-key}&tags={tag}&per_page=10";Source photos = restTemplate.getForObject(photoSearchUrl, Source.class, apiKey, searchTerm);

donde apiKey y searchTerm son dos Strings dados en la línea de comandos. Este método utiliza el SourceHttpMessageConverter para convertir la respuesta XML HTTP en un javax.xml.transform.Source (Tenga en cuenta que el SourceHttpMessageConverter se introdujo poco después de que lanzáramos Spring 3.0 M2, por lo que tendrá que obtener una instantánea reciente (o la próxima M3) para utilizarlo. El proyecto de ejemplo disponible a continuación está configurado para recuperarlos a través de Maven).

Recuperando las fotos

A continuación, vamos a utilizar una expresión XPath para recuperar todos los elementos de las fotos del documento. Para ello, vamos a utilizar el XPathTemplate de Spring Web Services. Vamos a ejecutar las expresiones //photo, devolviendo todos los elementos de la foto que se produzcan en cualquier parte del documento. El NodeMapper es una interfaz de callback, cuyo método mapNode() será invocado para cada elemento de la foto en el documento. En este caso, recuperamos los atributos server, id y secret de este elemento, y los utilizamos para rellenar un Map. Finalmente, usamos el RestTemplate de nuevo, para recuperar la foto como un java.awt.image.BufferedImage. Así, cuando la evaluación XPath se hace, el imageList resultante contendrá una imagen para cada foto en el documento XML.


List<BufferedImage> imageList = xpathTemplate.evaluate("//photo", photos, new NodeMapper() { public Object mapNode(Node node, int i) throws DOMException { Element photo = (Element) node; Map<String, String> variables = new HashMap<String, String>(3); variables.put("server", photo.getAttribute("server")); variables.put("id", photo.getAttribute("id")); variables.put("secret", photo.getAttribute("secret")); String photoUrl = "http://static.flickr.com/{server}/{id}_{secret}_m.jpg"; return restTemplate.getForObject(photoUrl, BufferedImage.class, variables); }});

Por ejemplo, dado el documento XML dado anteriormente, el imageList contendrá 4 imágenes. La URL de la primera imagen recuperada será http://static.flickr.com/2/2636_ a123456_m.jpg, la segunda es http://static.flickr.com/2/2635_ b123456_m.jpg, etc.

Convertir las imágenes

Hay una cosa más que hay que hacer para que el código funcione: tendremos que escribir un HttpMessageConverter que sea capaz de leer de la respuesta HTTP, y crear un BufferedImagea partir de ella. Hacerlo con la API de E/S de imágenes de Java es bastante sencillo, sólo tenemos que implementar el método read() definido en la interfaz HttpMessageConverter. En general, nuestro sencillo conversor tiene el siguiente aspecto:


public class BufferedImageHttpMessageConverter implements HttpMessageConverter<BufferedImage> { public List<MediaType> getSupportedMediaTypes() { return Collections.singletonList(new MediaType("image", "jpeg")); } public boolean supports(Class<? extends BufferedImage> clazz) { return BufferedImage.class.equals(clazz); } public BufferedImage read(Class<BufferedImage> clazz, HttpInputMessage inputMessage) throws IOException { return ImageIO.read(inputMessage.getBody()); } public void write(BufferedImage image, HttpOutputMessage message) throws IOException { throw new UnsupportedOperationException("Not implemented"); }}

Nota que no hemos implementado write() porque no estamos subiendo imágenes, sólo descargándolas. Ahora sólo tenemos que enchufar este convertidor en el RestTemplate. Lo hacemos en el contexto de la aplicación Spring:


<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.springsource.samples.resttemplate.FlickrClient"> <constructor-arg ref="restTemplate"/> <constructor-arg ref="xpathTemplate"/> </bean> <bean class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/> <bean class="com.springsource.samples.resttemplate.BufferedImageHttpMessageConverter"/> </list> </property> </bean> <bean class="org.springframework.xml.xpath.Jaxp13XPathTemplate"/></beans>

Mostrar las fotos

La etapa final es mostrar las fotos en una sencilla GUI. Para ello, utilizamos Swing:


JFrame frame = new JFrame(searchTerm + " photos");frame.setLayout(new GridLayout(2, imageList.size() / 2));for (BufferedImage image : imageList) { frame.add(new JLabel(new ImageIcon(image)));}frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.pack();frame.setVisible(true);

que nos da lo siguiente:

Penguins

En general, espero que este post os haya mostrado lo sencillo que puede ser utilizar el RestTemplate para interactuar con servidores HTTP. En poco menos de 30 líneas de código Java, hemos creado una GUI que muestra imágenes del pájaro favorito de todos: ¡el pingüino! Echa un vistazo a la RestTemplate y dinos lo que piensas

Descargas

Un proyecto Maven que contiene el código anterior se puede descargar aquí. Ten en cuenta que el proyecto está basado en una compilación nocturna de Spring. El próximo Milestone 3 de Spring contendrá también las clases necesarias.

Dejar una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *