1. Introducción a REST. Diseño y creación de servicios RESTful

En esta sesión vamos a introducir los conceptos de servicio Web y servicio Web RESTful, que es el tipo de servicios con los que vamos a trabajar. Explicaremos el proceso de diseño del API de un servicio Web RESTful, y definiremos las URIs que constituirán los "puntos de entrada" de nuestra aplicación REST. Finalmente ilustraremos los pasos para implementar, desplegar y probar un servicio REST, utilizando Maven, IntelliJ, y el servidor de aplicaciones Wildfly. También nos familiarizaremos con Postman, una herramienta para poder probar de forma sencilla los servicios web directamente desde el navegador.

1.1. ¿Qué es un servicio Web?

El diseño del software tiende a ser cada vez más modular. Las aplicaciones están formadas por una serie de componentes reutilizables (servicios), que pueden encontrarse distribuidos a lo largo de una serie de máquinas conectadas en red.

El WC3 (World Wide Web Consortium) define un servicio Web como un sistema software diseñado para soportar interacciones máquina a máquina a través de la red. Dicho de otro modo, los servicios Web proporcionan una forma estándar de interoperar entre aplicaciones software que se ejecutan en diferentes plataformas. Por lo tanto, su principal característica su gran interoperabilidad y extensibilidad así como por proporcionar información fácilmente procesable por las máquinas gracias al uso de XML. Los servicios Web pueden combinarse con muy bajo acoplamiento para conseguir la realización de operaciones complejas. De esta forma, las aplicaciones que proporcionan servicios simples pueden interactuar con otras para "entregar" servicios sofisticados añadidos.

Historia de los servicios Web

Los servicios Web fueron "inventados" para solucionar el problema de la interoperabilidad entre las aplicaciones. Al principio de los 90, con el desarrollo de Internet/LAN/WAN, apareció el gran problema de integrar aplicaciones diferentes. Una aplicación podía haber sido desarrollada en C++ o Java, y ejecutarse bajo Unix, un PC, o un computador mainframe. No había una forma fácil de intercomunicar dichas aplicaciones. Fué el desarrollo de XML el que hizo posible compartir datos entre aplicaciones con diferentes plataformas hardware a través de la red, o incluso a través de Internet. La razón de que se llamasen servicios Web es que fueron diseñados para residir en un servidor Web, y ser llamados a través de Internet, típicamente via protocolos HTTP, o HTTPS. De esta forma se asegura que un servicio puede ser llamado por cualquier aplicación, usando cualquier lenguaje de programación, y bajo cualquier sistema operativo, siempre y cuándo, por supuesto, la conexión a Internet esté activa, y tenga un puerto abierto HTTP/HTTPS, lo cual es cierto para casi cualquier computador que disponga de acceso a Internet.

A nivel conceptual, un servicio es un componente software proporcionado a través de un endpoint accesible a través de la red. Los servicios productores y consumidores utilizan mensajes para intercambiar información de invocaciones de petición y respuesta en forma de documentos auto-contenidos que hacen muy pocas asunciones sobre las capacidades tecnológicas de cada uno de los receptores.

¿Qué es un endpoint?

Los servicios pueden interconectarse a través de la red. En una arquitectura orientada a servicios, cualquier interacción punto a punto implica dos endpoints: uno que proporciona un servicio, y otro de lo consume. Es decir, que un endpoint es cada uno de los "elementos", en nuestro caso nos referimos a servicios, que se sitúan en ambos "extremos" de la red que sirve de canal de comunicación entre ellos. Cuando hablamos de servicios Web, un endpoint se especifica mediante una URI.

A nivel técnico, los servicios pueden implementarse de varias formas. En este sentido, podemos distinguir dos tipos de servicios Web: los denominados servicios Web "grandes" ("big" Web Services), los llamaremos servicios Web SOAP, y servicios "ligeros" o servicios Web RESTful.

Los servicios Web SOAP se caracterizan por utilizar mensajes XML que siguen el estándar SOAP (Simple Object Access Protocol). Además contienen una descripción de las operaciones proporcionadas por el servicio, escritas en WSDL (Web Services Description Language), un lenguaje basado en XML.

Los servicios Web RESTful, por el contrario, pueden intercambiar mensajes escritos en diferentes formatos, y no requieren el publicar una descripción de las operaciones que proporcionan, por lo que necesitan una menor "infraestructura" para su implementación. Nosotros vamos a centrarnos en el uso de estos servicios.

1.1.1. Servicios Web RESTful

Son un tipo de Servicios Web, que se adhieren a una serie de restricciones arquitectónicas englobadas bajo las siglas de REST, y que utilizan estándares Web tales como URIs, HTTP, XML, y JSON.

El API Java para servicios Web RESTful (JAX-RS) permite desarrollar servicios Web RESTful de forma sencilla. La versión más reciente del API es la 2.0, cuya especificación está publicada en el documento JSR-339, y que podemos descargar desde https://jcp.org/en/jsr/detail?id=339. A lo largo de estas sesiones, veremos cómo utilizar JAX-RS para desarrollar servicios Web RESTful. Dicho API utiliza anotaciones Java para reducir los esfuerzos de programación de los servicios.

1.2. Fundamentos de REST

El término REST proviene de la tesis doctoral de Roy Fielding, publicada en el año 2000, y significa REpresentational State Transfer (podemos acceder a la tesis original en: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm). REST es un conjunto de restricciones que, cuando son aplicadas al diseño de un sistema, crean un estilo arquitectónico de software. Dicho estilo arquitectónico se caracteriza por seguir los siguientes principios:

  • Debe ser un sistema cliente-servidor

  • Tiene que ser sin estado, es decir, no hay necesidad de que los servicios guarden las sesiones de los usuarios (cada petición al servicio tiene que ser independiente de las demás)

  • Debe soportar un sistema de cachés: la infraestructura de la red debería soportar caché en diferentes niveles

  • Debe ser un sistema uniformemente accesible (con una interfaz uniforme): Esta restricción define cómo debe ser la interfaz entre clientes y servidores. La idea es simplificar y desacoplar la arquitectura, permitiendo que cada una de sus partes puede evolucionar de forma independiente. Una interfaz uniforme se debe caracterizar por:

    • Estar basada en recursos: La abstracción utilizada para representar la información y los datos en REST es el recurso, y cada recurso debe poder ser accedido mediante una URI (Uniform Resource Identifier).

    • Orientada a representaciones: La interacción con los servicios tiene lugar a través de las representaciones de los recursos que conforman dicho servicio. Un recurso referenciado por una URI puede tener diferentes formatos (representaciones). Diferentes plataformas requieren formatos diferentes. Por ejemplo, los navegadores necesitan HTML, JavaScript requiere JSON (JavaScript Object Notation), y una aplicación Java puede necesitar XML.

    • Interfaz restringida: Se utiliza un pequeño conjunto de métodos bien definidos para manipular los recursos.

    • Uso de mensajes auto-descriptivos: cada mensaje debe incluir la suficiente información como para describir cómo procesar el mensaje. Por ejemplo, se puede indicar cómo "parsear" el mensaje indicando el tipo de contenido del mismo (xml, html, texto,…​)

    • Uso de Hipermedia como máquina de estados de la aplicacion (HATEOAS): Los propios formatos de los datos son los que "dirigen" las transiciones entre estados de la aplicación. Como veremos más adelante con más detalle, el uso de HATEOAS (Hypermedia As The Engine Of Application State), va a permitir transferir de forma explícita el estado de la aplicacion en los mensajes intercambiados, y por lo tanto, realizar interacciones con estado.

  • Tiene que ser un sistema por capas: un cliente no puede "discernir" si está accediendo directamente al servidor, o a algún intermediario. Las "capas" intermedias van a permitir soportar la escalabilidad, así como reforzar las políticas de seguridad

A continuación analizaremos algunas de las abstracciones que constituyen un sistema RESTful: recursos, representaciones, URIs, y los tipos de peticiones HTTP que constituyen la interfaz uniforme utilizada en las transferencias cliente/servidor

1.2.1. Recursos

Un recurso REST es cualquier cosa que sea direccionable (y por lo tanto, accesible) a través de la Web. Por direccionable nos referimos a recursos que puedan ser accedidos y transferidos entre clientes y servidores. Por lo tanto, un recurso es una correspondencia lógica y temporal con un concepto en el dominio del problema para el cual estamos implementando una solución.

Algunos ejemplos de recursos REST son:

  • Una noticia de un periódico

  • La temperatura de Alicante a las 4:00pm

  • Un valor de IVA almacenado en una base de datos

  • Una lista con el historial de las revisiones de código en un sistema CVS

  • Un estudiante en alguna aula de alguna universidad

  • El resultado de una búsqueda de un ítem particular en Google

Aun cuando el mapeado de un recurso es único, diferentes peticiones a un recurso pueden devolver la misma representación binaria almacenada en el servidor. Por ejemplo, consideremos un recurso en el contexto de un sistema de publicaciones. En este caso, una petición de la "última revisión publicada" y la petición de "la revisión número 12" en algún momento de tiempo pueden devolver la misma representación del recurso: cuando la última revisión sea efectivamente la 12. Por lo tanto, cuando la última revisión publicada se incremente a la versión 13, una petición a la última revisión devolverá la versión 13, y una petición de la revisión 12, continuará devolviendo la versión 12. En definitiva: cada uno de los recursos puede ser accedido directamente y de forma independiente, pero diferentes peticiones podrían "apuntar" al mismo dato.

Debido a que estamos utilizando HTTP para comunicarnos, podemos transferir cualquier tipo de información que pueda transportarse entre clientes y servidores. Por ejemplo, si realizamos una petición de un fichero de texto de la CNN, nuestro navegador mostrará un fichero de texto. Si solicitamos una película flash a YouTube, nuestro navegador recibirá una película flash. En ambos casos, los datos son transferidos sobre TCP/IP y el navegador conoce cómo interpretar los streams binarios debido a la cabecera de respuesta del protocolo HTTP Content-Type. Por lo tanto, en un sistema RESTful, la representación de un recurso depende del tipo deseado por el cliente (tipo MIME), el cual está especificado en la petición del protocolo de comunicaciones.

1.2.2. Representación de los recursos

La representación de los recursos es lo que se envía entre los servidores y clientes. Una representación muestra el estado temporal del dato real almacenado en algún dispositivo de almacenamiento en el momento de la petición. En términos generales, es un stream binario, juntamente con los metadatos que describen cómo dicho stream debe ser consumido por el cliente y/o servidor (los metadatos también puden contener información extra sobre el recurso, como por ejemplo información de validación y encriptación, o código extra para ser ejecutado dinámicamente).

A través del ciclo de vida de un servicio web, pueden haber varios clientes solicitando recursos. Clientes diferentes son capaces de consumir diferentes representaciones del mismo recurso. Por lo tanto, una representación puede tener varias formas, como por ejemplo, una imagen, un texto, un fichero XML, o un fichero JSON, pero tienen que estar disponibles en la misma URL.

Para respuestas generadas para humanos a través de un navegador, una representación típica tiene la forma de página HTML. Para respuestas automáticas de otros servicios web, la legibilidad no es importante y puede utilizarse una representación mucho más eficiente como por ejemplo XML.

El lenguaje para el intercambio de información con el servicio queda a elección del desarrollador. A continuación mostramos algunos formatos comunes que podemos utilizar para intercambiar esta información:

Table 1. Ejemplos de formatos utilizados por los servicios REST
Formato Tipo MIME

Texto plano

text/plain

HTML

text/html

XML

application/xml

JSON

application/json

De especial interés es el formato JSON. Se trata de un lenguaje ligero de intercambio de información, que puede utilizarse en lugar de XML (que resulta considerablemente más pesado) para aplicaciones AJAX. De hecho, en Javascript puede leerse este tipo de formato simplemente utilizando el método eval().

1.2.3. Direccionabilidad de los recursos: URI

Una URI, o Uniform Resource Identifier, en un servicio web RESTful es un hiper-enlace a un recurso, y es la única forma de intercambiar representaciones entre clientes y servidores. Un servicio web RESTful expone un conjunto de recursos que identifican los objetivos de la interacción con sus clientes.

El conjunto de restricciones REST no impone que las URIs deban ser hiper-enlaces. Simplemente hablamos de hiper-enlaces porque estamos utilizando la Web para crear servicios web. Si estuviésemos utilizando un conjunto diferente de tecnologías soportadas, una URI RESTful podría ser algo completamente diferente. Sin embargo, la idea de direccionabilidad debe permanecer.

En un sistema REST, la URI no cambia a lo largo del tiempo, ya que la implementación de la arquitectura es la que gestiona los servicios, localiza los recursos, negocia las representaciones, y envía respuestas con los recursos solicitados. Y lo que es más importante, si hubiese un cambio en la estructura del dispositivo de almacenamiento en el lado del servidor (por ejemplo, un cambio de servidores de bases de datos), nuestras URIs seguirán siendo las mismas y serán válidas mientras el servicio web siga estando "en marcha" o el contexto del recurso no cambie.

Sin las restricciones REST, los recursos se acceden por su localización: las direcciones web típicas son URIs fijas. Si por ejemplo renombramos un fichero en el servidor, la URI será diferente; si movemos el fichero a un directorio diferente, la URI también será diferente.

El formato de una URI se estandariza como sigue:

scheme://host:port/path?queryString#fragment

En donde:

  • scheme es el protocolo que estamos utilizando para comunicarnos con el servidor. Para servicios REST, normalmente el protocolo será http o https.

  • El término host es un nombre DNS o una dirección IP.

  • A continuación se puede indicar de forma opcional un puerto (mediante :port), que es un valor numérico. El host y el port representan la localización de nuestro recurso en la red.

  • Seguidamente aparece una expresión path, que es un conjunto de segmentos de texto delimitados por el carácter \ (pensemos en la expresión path como en una lista de directorios de un fichero en nuestra máquina).

  • Esta expresión puede ir seguida, opcionalmente por una queryString. El carácter ?) separa el path de la queryString. Esta última es una lista de parámetros representados como pares nombre/valor. Cada par está delimitado por el carácter &.

La última parte de la URI es el fragment, delimitado por el carácter #. Normalmente se utiliza para "apuntar" a cierto "lugar" del documento al que estamos accediendo.

En una URI, no todos los caracteres están permitidos, de forma que algunos caracteres se codificarán de acuerdo a las siguientes reglas:

  • Los caracteres a-z, A-Z, 0-9, ., -, *, y _, permanecen igual

  • El caracter "espacio" se convierte en el carácter +

  • El resto de caracteres se codifican como una secuencia de bits siguiendo un esquema de codificación hexadecimal, de forma que cada dos dígitos hexadecimales van precedidos por el carácter %.

Un ejemplo de URI podría ser éste:

http://expertojava.ua.es/recursos/clientes?apellido=Martinez&codPostal=02115

En el ejemplo anterior el host viene dado por expertojava.ua.es, el path o ruta de acceso al recurso es /recursos/clientes, y hemos especificado los parámetros apellido y codPostal, con los valores Martinez y 02115 respectivamente.

Si por ejemplo, en nuestra aplicación tenemos información de clientes, podríamos acceder a la lista correspondiente mediante una URL como la siguiente:

http://expertojava.ua.es/recursos/clientes

Esto nos devolverá la lista de clientes en el formato que el desarrollador del servicio haya decidido. Hay que destacar, por lo tanto, que en este caso debe haber un entendimiento entre el consumidor y el productor del servicio, de forma que el primero comprenda el lenguaje utilizado por el segundo.

La URL anterior nos podría devolver un documento como el siguiente:

<?xml version="1.0"?>
<clientes>
  <cliente>http://expertojava.ua.es/recursos/clientes/1"<cliente/>
  <cliente>http://expertojava.ua.es/recursos/clientes/2"<cliente/>
  <cliente>http://expertojava.ua.es/recursos/clientes/4"<cliente/>
  <cliente>http://expertojava.ua.es/recursos/clientes/6"<cliente/>
</clientes>

En este documento se muestra la lista de clientes registrados en la aplicación, cada uno de ellos representado también por una URL. Accediendo a estas URLs, a su vez, podremos obtener información sobre cada curso concreto o bien modificarlo.

1.2.4. Uniformidad y restricciones de las interfaces

Ya hemos introducido los conceptos de recursos y sus representaciones. Hemos dicho que los recursos son correspondencias (mappings) de los estados reales de las entidades que son intercambiados entre los clientes y servidores. También hemos dicho que las representaciones son negociadas entre los clientes y servidores a través del protocolo de comunicación en tiempo de ejecución (a través de HTTP). A continuación veremos con detalle lo que significa el intercambio de estas representaciones, y lo que implica para los clientes y servidores el realizar acciones sobre dichos recursos.

El desarrollo de servicios web REST es similar al desarrollo de aplicaciones web. Sin embargo, la diferencia fundamental entre el desarrollo de aplicaciones web tradicionales y las más modernas es cómo pensamos sobre las acciones a realizar sobre nuestras abstracciones de datos. De forma más concreta, el desarrollo moderno está centrado en el concepto de nombres (intercambio de recursos); el desarrollo tradicional está centrado en el concepto de verbos (acciones remotas a realizar sobre los datos). Con la primera forma, estamos implementando un servicio web RESTful; con la segunda un servicio similar a una llamada a procedimiento remoto- RPC). Y lo que es más, un servicio RESTful modifica el estado de los datos a través de la representación de los recursos (por el contrario, una llamada a un servicio RPC, oculta la representación de los datos y en su lugar envía comandos para modificar el estado de los datos en el lado del servidor). Finalmente, en el desarrollo moderno de aplicaciones web limitamos la ambigüedad en el diseño y la implementación debido a que tenemos cuatro acciones específicas que podemos realizar sobre los recursos: Create, Retrieve, Update y Delete (CRUD). Por otro lado, en el desarrollo tradicional de aplicaciones web, podemos tener otras acciones con nombres o implementaciones no estándar.

A continuación indicamos la correspondencia entre las acciones CRUD sobre los datos y los métodos HTTP asociados:

Table 2. Operaciones REST sobre los recursos:
Acción sobre los datos Protocolo HTTP equivalente

CREATE

POST

RETRIEVE

GET

UPDATE

PUT

DELETE

DELETE

El principio de uniformidad de la interfaz de acceso a recursos es fundamental, y quizá el más difícil de seguir por los programadores acostumbrados al modelo RPC (Remote Procedure Call). La idea subyacente es utilizar únicamente un conjunto finito y claramente establecido de operaciones para la interacción con los servicios. Esto significa que no tendremos un parámetro "acción" en nuestra URI y que sólo utilizaremos los métodos HTTP para acceder a nuestros servicios. Cada uno de los métodos tiene un propósito y significado específicos, que mostramos a continuación:

GET

GET es una operación sólo de lectura. Se utiliza para "recuperar" información específica del servidor. También se trata de una operación idempotente y segura. Idempotente significa que no importa cuántas veces invoquemos esta operación, el resultado (que observaremos como usuarios) debe ser siempre el mismo. Segura significa que una operación GET no cambia el estado del servidor en modo alguno, es decir, no debe exhibir ningún efecto lateral en el servidor. Por ejemplo, el hecho de "leer" un documento HTML no debería cambiar el estado de dicho documento.

PUT

La operación PUT solicita al servidor el almacenar el cuerpo del mensaje enviado con dicha operación en la dirección proporcionada en el mensaje HTTP. Normalmente se modela como una inserción o actualización (nosotros la utilizaremos solamente como actualización). Es una propiedad idempotente. Cuando se utiliza PUT, el cliente conoce la identidad del recurso que está creando o actualizando. Es idempotente porque enviar el mismo mensaje PUT más de una vez no tiene ningún efecto sobre el servicio subyacente. Una analogía podría ser un documento de texto que estemos editando. No importa cuántas veces pulsemos el "botón" de grabar, el fichero que contiene el documento lógicamente será el mismo documento.

DELETE

Esta operación se utiliza para eliminar recursos. También es idempotente

POST

Post es la única operación HTTP que no es idempotente ni segura. Cada petición POST puede modificar el servicio de forma exclusiva. Se puede enviar, o no, información con la petición. También podemos recibir, o no, información con la respuesta. Para implementar servicios REST, es deseable enviar información con la petición y también recibir información con la respuesta.

Adicionalmente, podemos utilizar otras dos operaciones HTTP:

HEAD

Es una operación exactamente igual que GET, excepto que en lugar de devolver un "cuerpo de mensaje", solamente devuelve un código de respuesta y alguna cabecera asociada con la petición.

OPTIONS

Se utiliza para solicitar información sobre las opciones disponibles sobre un recurso en el que estamos interesados. Esto permite al cliente determinar las capacidades del servidor y del recurso sin tener que realizar ninguna petición que provoque una acción sobre el recurso o la recuperación del mismo.

PATCH

Se utiliza para para realiza reemplazos (actualizaciones) parciales de un documento, ya que la operación PUT sólo permite una actualización completa del recurso (y requiere indicar una representación completa del mismo) . Es útil cuando el recurso a modificar es complejo y solamente queremos actualizar parte de su contenido. En este caso solo necesitamos indicar la parte que queremos cambiar.

1.3. Diseño de servicios Web RESTful

El diseño de servicios RESTful no es muy diferente del diseño de aplicaciones web tradicionales: tenemos requerimientos de negocio, tenemos usuarios que quieren realizar operaciones sobre los datos, y tenemos restricciones hardware que van a condicionar nuestra arquitectura software. La principal diferencia reside en el hecho de que tenemos que "buscar", a partir de los requerimientos, cuáles van a ser los recursos que van a ser accedidos a través de los servicios, "sin preocuparnos" de qué operaciones o acciones específicas van a poderse realizar sobre dichos recursos (el proceso de diseño depende de los "nombres", no de los "verbos").

Podemos resumir los principios de diseño de servicios web RESTful en los siguientes cuatro pasos:

  1. Elicitación de requerimientos y creación del modelo de objetos: Este paso es similar al diseño orientado a objetos. El resultado del proceso puede ser un modelo de clases UML

  2. Identificación de recursos: Este paso consiste en identificar los "objetos" de nuestro modelo sin preocuparnos de las operaciones concretas a realizar sobre dichos objetos

  3. Definición de las URIs: Para satisfacer el principio de "direccionabilidad" de los recursos, tendremos que definir las URIs que representarán los endpoints de nuestros servicios, y que constituirán los "puntos de entrada" de los mismos

  4. Definición de la representación de los recursos: Puesto que los sistemas REST están orientados a la representación, tendremos que definir el formato de los datos que utilizaremos para intercambiar información entre nuestros servicios y clientes

  5. Definición de los métodos de acceso a los recursos: Finalmente, tendremos que decidir qué métodos HTTP nos permitirán acceder a las URIs que queremos exponer, así como qué hará cada método. Es muy importante que en este paso, nos ciñamos a las restricciones que definen los principios RESTful que hemos indicado en apartados anteriores.

1.4. Un primer servicio JAX-RS

Vamos a ilustrar los pasos anteriores con un ejemplo, concretamente definiremos una interfaz RESTful para un sistema sencillo de gestión de pedidos de un hipotético comercio por internet. Los potenciales clientes de nuestro sistema, podrán realizar compras, modificar pedidos existentes en nuestro sistema, así como visualizar sus datos personales o la información sobre los productos que son ofertados por el comercio.

1.4.1. Modelo de objetos

A partir de los requerimientos del sistema, obtenemos el modelo de objetos. El modelo de objetos de nuestro sistema de ventas por internet es bastante sencillo. Cada pedido en el sistema representa una única transacción de compra y está asociada con un cliente particular. Los pedidos estarán formados por una o más líneas de pedido. Las líneas de pedido representan el tipo y el número de unidades del producto adquirido.

Basándonos en esta descripción de nuestro sistema, podemos extraer que los objetos de nuestro modelo son: Pedido, Cliente, LineaPedido, y Producto. Cada objeto de nuestro modelo tiene un identificador único, representado por la propiedad id, dada por un valor de tipo entero. La siguiente figura muestra un diagrama UML de nuestro modelo:

Modelo de objetos del servicio RESTful

Estamos interesados en consultar todos los pedidos realizados, así como cada pedido de forma individual. También queremos poder realizar nuevos pedidos, así como actualizar pedidos existentes. El objeto ServicioPedidos representa las operaciones que queremos realizar sobre nuestos objetos Pedido, Cliente, LineaPedido y Producto.

1.4.2. Modelado de URIs

Lo primero que haremos para crear nuestra interfaz distribuida, es definir y poner nombre a cada uno de los endpoints de nuestro sistema. En un sistemam RESTful, los endpoints serán los recursos del sistema, que identificaremos mediante URIs.

En nuestro modelo de objetos queremos poder interactuar con Pedidos, Clientes, y Productos. Éstos serán, por lo tanto, nuestros recursos de nivel más alto. Por otro lado, estamos interesados en obtener una lista de cada uno de estos elementos de alto nivel, así como interactuar con los elementos indiduales de cada tipo. El objeto LineaPedido es un objeto agregado del objeto Pedido por lo que no lo consideraremos com un recurso de nivel superior. Más adelante veremos que podremos exponerlo como un subrecurso de un Pedido particular, pero por ahora, asumiremos que está "oculto" por el formato de nuestros datos. Según esto, una posible lista de URIs que expondrá nuestro sistema podría ser:

  • /pedidos

  • /pedidos/{id}

  • /productos

  • /productos/{id}

  • /clientes

  • /clientes/{id}

Fíjate que hemos representado como URIs los nombres en nuestro modelo de objetos. Recuerda que las URIS no deberían utilizarse como mini-mecanismos de RPC ni deberían identificar operaciones. En vez de eso, tenemos que utilizar una combinación de métodos HTTP y de datos (recursos) para modelar las operaciones de nuestro sistema RESTful

1.4.3. Definición del formato de datos

Una de las cosas más importantes que tenemos que hacer cuando definimos la interfaz RESTful es determinar cómo se representarán los recursos que serán accedidos por los usuarios de nuestro API REST. Quizá XML sea el formato más popular de la web y puede ser procesado por la mayor parte de los lenguajes de programación. Como veremos más adelante, JSON es otro formato popular, menos "verboso" que XML, y que puede ser interpretado directamente por JavaScript (lo cual es perfecto para aplicaciones Ajax por ejemplo). Por ahora, utilizaremos el formato XML en nuestro ejemplo.

Generalmente, tendríamos que definir un esquema XML para cada representación que queramos transimitir a traves de la red. Un esquema XML define la gramática del formato de datos. Por simplicidad, vamos a omitir la creación de esquemas, asumiendo que los ejemplos que proporcionamos se adhieren a sus correspondientes esquemas.

A continuación distinguiremos entre dos formatos de datos: uno para las operaciones de lectura y actualización, y otro para la operación de creación de recursos.

Formato de datos para operaciones de lectura y modificación de los recursos

Las representaciones de los recursos Pedido, Cliente, y Producto tendrán un elemento XML en común, al que denominaremos link:

<link rel="self" href="http://org.expertojava/..."/>

El elemento (o etiqueta) link indica a los clientes que obtengan un documento XML como representación del recurso, dónde pueden interaccionar en la red con dicho recurso en particular. El atributo self le indica al cliente qué relación tiene dicho enlace con la URI del recurso al que apunta (información contenida en el atributo href). El valor self indica que está "apuntando" a sí mismo. Más adelante veremos la utilidad del elemento link cuando agreguemos información en documentos XML "más grandes".

El formato de representación del recurso Cliente podría ser:

<cliente id="8">
   <link rel="self"
         href="http://org.expertojava/clientes/8"/>
   <nombre>Pedro</nombre>
   <apellidos>Garcia Perez</apellidos>
   <direccion>Calle del Pino, 5<direccion>
   <codPostal>08888</codPostal>
   <ciudad>Madrid</ciudad>
</cliente>

El formato de representación del recurso Producto podría ser:

<producto id="34">
   <link rel="self"
         href="http://org.expertojava/productos/34"/>
   <nombre>iPhone 6</nombre>
   <precio>800</precio>
   <cantidad>1</cantidad>
</producto>

Finalmente, el formato de la representación del recurso Pedido podría ser:

<pedido id="233">
  <link rel="self" href="http://org.expertojava/pedidos/233"/>
  <total>800</total>
  <fecha>December 22, 2014 06:56</fecha>
  <cliente id="8">
     <link rel="self"
                href="http://org.expertojava/clientes/8"/>
     <nombre>Pedro</nombre>
     <apellidos>Garcia Perez</apellidos>
     <direccion>Calle del Pino, 5<direccion>
     <codPostal>08888</codPostal>
     <ciudad>Madrid</ciudad>
  </cliente>
  <lineasPedido>
    <lineaPedido id="1">
      <producto id="34">
         <link rel="self"
             href="http://org.expertojava/productos/34"/>
         <nombre>iPhone 6</nombre>
         <precio>800</precio>
         <cantidad>1</cantidad>
      </producto>
    <lineaPedido/>
  <lineasPedido/>
</pedido>

El formato de datos de un Pedido tiene en un primer nivel la información del total, con el importe total del pedido, así como la fecha en la que se hizo dicho pedido. Pedido es un buen ejemplo de composición de datos, ya que un pedido incluye información sobre el Cliente y el Producto/s. Aquí es donde el elemento <link> puede resultar particularmente útil. Si el usuario está interesado en interaccionar con el Cliente que ha realizado el pedido, o en uno de los productos del mismo, se proporciona la URI necesaria para interactuar con cada uno de dichos recursos. De esta forma, cuando el usuario del API consulte un pedido, podrá además, acceder a información adicional relacionada con la consulta realizada.

Formato de datos para operaciones de creación de los recursos

Cuando estamos creando nuevos Pedidos, Clientes o Productos, no tiene mucho sentido incluir un atributo id y un elemento link en nuestro documento XML. El servidor será el encargado de crear los ids cuando inserte nuestro nuevo objeto en la base de datos. Tampoco conocemos la URI del nuevo objeto creado, ya que será el servidor el encargado de generarlo. Por lo tanto, para crear un nuevo Producto, el formato de la información podría ser el siguiente:

<producto>
   <link rel="self"
         href="http://org.expertojava/clientes/8"/>
   <nombre>iPhone</nombre>
   <precio>800</precio>
</producto>

1.4.4. Asignación de métodos HTTP

Finalmente, tendremos que decidir qué métodos HTTP expondremos en nuestro servicio para cada uno de los recursos, así como definir qué harán dichos métodos. Es muy importante no asignar funcionaldad a un método HTTP que "sobrepase" los límites impuestos por la especificación de dicho método. Por ejemplo, una operación GET sobre un recurso concreto debería ser de sólo lectura. No debería cambiar el estado del recurso cuando invoquemos la operación GET sobre él. Si no seguimos de forma estricta la semántica de los métodos HTTP, los clientes, así como cualquier otra herramienta administrativa, no pueden hacer asunciones sobre nuestros servicios, de forma que nuestro sistema se vuelve más complejo.

Veamos, para cada uno de los métodos de nuestro modelo de objetos, cuales serán las URIs y métodos HTTP que usaremos para representarlos.

Visualización de todos los Pedidos, Clientes o Productos

Los tres objetos de nuestro modelo: Pedidos, Clientes y Productos, son accedidos y manipulados de forma similar. Los usuarios pueden estar interesados en ver todos los Pedidos, Clientes o Productos en el sistema. Las siguientes URIs representan dichos objetos como un grupo:

  • /pedidos

  • /productos

  • /clientes

Para obtener una lista de Pedidos, Clientes o Productos, el cliente remoto realizara una llamada al método HTTP GET sobre la URI que representa el grupo de objetos. Un ejemplo de petición podría ser la siguiente:

GET /productos HTTP/1.1

Nuestro servicio responderá con los datos que representan todos los Pedidos de nuestro sistema. Una respuesta podría ser ésta:

HTTP/1.1 200 OK
Content-Type: application/xml

<productos>
   <producto id="111">
      <link rel="self" href="http://org.expertojava/productos/111"/>
      <nombre>iPhone</nombre>
      <precio>648.99</precio>
   </producto>
   <producto id="222">
      <link rel="self" href="http://org.expertojava/productos/222"/>
      <nombre>Macbook</nombre>
      <precio>1599.99</precio>
   </producto>
    ...
</productos>

Un problema que puede darse con esta petición es que tengamos miles de Pedidos, Clientes o Productos en nuestro sistema, por lo que podemos "sobrecargar" a nuestro cliente y afectar negativamente a los tiempos de respuesta. Para mitigar esta problema, permitiremos que el usuario especifique unos parámetros en la URI para limitar el tamaño del conjunto de datos que se va a devolver:

GET /pedidos?startIndex=0&size=5 HTTP/1.1
GET /productos?startIndex=0&size=5 HTTP/1.1
GET /clientes?startIndex=0&size=5 HTTP/1.1

En las órdenes anteriores, hemos definido dos parámetros de petición: startIndex, y size. El primero de ellos es un índice numérico que representa a partir de qué posición en la lista de Pedidos, Clientes o Productos, comenzaremos a enviar la información al cliente. El parámetro size especifica cuántos de estos objetos de la lista queremos que nos sean devueltos.

Estos parámetros serán opcionales, de forma que el cliente no tiene que especificarlos en su URI.

Obtención de Pedidos, Clientes o Productos individuales

Ya hemos comentado previamente que podríamos utilizar las siguientes URIs para obtener Pedidos, Clientes o Productos:

  • /pedidos/{id}

  • /productos/{id}

  • /clientes/{id}

En este caso usaremos el método HTTP GET para recuperar objetos individuales en el sistema. Cada invocación GET devolverá la información del correspondiente objeto. Por ejemplo:

GET /pedidos/233 HTTP/1.1

Para esta petición, el cliente está interesado en obtener una representación del Pedido con identificador 233. Las peticiones GET para Productos y Clientes podrían funcionar de forma similar. El mensaje de respuesta podría parecerse a algo como esto:

HTTP/1.1 200 OK
Content-Type: application/xml

<pedido id="233">...</pedido>

El código de respuesta es 200 OK, indicando que la petición ha tenido éxito. La cabecera Content-Type especifica el formato del cuerpo de nuestro mensaje como XML, y finalmente obtenemos la representación del Pedido solicitado.

Creación de un Pedido, Cliente o Producto

Para crear un Pedido, Cliente o Producto utilizaremos el método POST. En este caso, el cliente envía una representación del nuevo objeto que se prentende crear a la URI "padre" de su representación, y por lo tanto, podremos omitir el identificador del recurso. Por ejemplo:

Petición POST para crear un pedidio
POST /pedidos HTTP/1.1
Content-Type: application/xml

<pedido>
   <total>199.02</total>
   <fecha>December 22, 2008 06:56</fecha>
   ...
</pedido>

El servicio recibe el mensaje POST, procesa la XML, y crea un nuevo pedido en la base de datos utilizando un identificador generado de forma única. Si bien esta aproximación "funciona" perfectamente, se le pueden plantear varias cuestiones al usuario. ¿Qué ocurre si el usuario quiere visualizar, modificar o eliminar el pedido que acaba de crear? ¿Cuál es el identificador del nuevo recurso? ¿Cuál es la URI que podemos utilizar para interactuar con el nuevo recurso? Para resolver estas cuestiones, añadiremos alguna información al mensaje de respuesta HTTP. El cliente podría recibir un mensaje similar a éste:

Respuesta de una petición POST para crear un pedido
HTTP/1.1 201 Created
Content-Type: application/xml
Location: http://org.expertojava/pedidos/233

<pedido id="233">
    <link rel="self" href="http://org.expertojava/pedidos/233"/>
    <total>199.02</total>
    <fecha>December 22, 2008 06:56</fecha>
    ...
</pedido>

HTTP requiere que si POST crea un nuevo recurso, se debe responder con un código 201 Created. También se requieer que la cabecera Location en el mensaje de respuesta proporcione una URI al usuario que ha hecho la petición para que éste pueda interactuar con la Petición que acaba de crear (por ejemplo, para modificar dicho Pedido). Es opcional por parte del servidor devolver en la respuesta la representación del nuevo recurso creado. En nuestro ejemplo optamos por devolver una representación XML de la Peticion creada con el identificador del atributo así como el elemento link.

Actualización de un Pedido, Cliente o Producto

Para realizar modificaciones sobre los recursos que ya hemos creado utilizaremos el método PUT. En este caso, un ejemplo de petición podría ser ésta:

Petición PUT para modificar un pedidio
PUT /pedidos/233 HTTP/1.1
Content-Type: application/xml

<producto id="111">
   <nombre>iPhone</nombre>
   <precio>649.99</precio>
</producto>

Tal y como he hemos indicado anteriormente, la operación PUT es idempotente. Lo que significa que no importa cuántas veces solicitemos la petición PUT, el producto subyacente sigue permaneciendo con el mismo estado final.

Cuando un recurso se modifica mediante PUT, la especificación HTTP requiere que el servidor envíe un código de respuesta 200 OK, y un cuerpo de mensaje de respuesta, o bien el código 204 No Content, sin ningún cuerpo de mensaje en la respuesta.

En nuestro caso, devolveremos un código de estado 204 y un mensaje sin cuerpo de respuesta.

RECUERDA: Es importante NO confundir POST con PUT

Muchas veces se confunden los métodos PUT y POST. El significado de estos métodos es el siguiente:

  • POST: Publica datos en un determinado recurso. El recurso debe existir previamente, y los datos enviados son añadidos a él. Por ejemplo, para añadir nuevos pedidos con POST hemos visto que debíamos hacerlo con el recurso lista de pedidos (/pedidos), ya que la URI del nuevo pedido todavía no existe. La operación NO es idempotente, es decir, si añadimos varias veces el mismo alumno aparecerá repetido en nuestra lista de pedidos con URIs distintas.

  • PUT: Hace que el recurso indicado tome como contenido los datos enviados. El recurso podría no existir previamente, y en caso de que existiese sería sobrescrito con la nueva información. A diferencia de POST, PUT es idempotente: Múltiples llamadas idénticas a la misma acción PUT siempre dejarán el recurso en el mismo estado. La acción se realiza sobre la URI concreta que queremos establecer (por ejemplo, /pedidos/215), de forma que varias llamadas consecutivas con los mismos datos tendrán el mismo efecto que realizar sólo una de ellas.

Borrado de un Pedido, Cliente o Producto

Modelaremos el borrado de los recursos utilizando el método HTTP DELETE. El usuario simplemente invocará el método DELETE sobre la URI que representa el objeto que queremos eliminar. Este método hará que dicho recurso ya no exista en nuestro sistema.

Cuando eliminamos un recurso con DELETE, la especificación requiere que se envíe un código de respuesta 200 OK, y un cuerpo de mensaje de respuesta, o bien un código de respuesta 204 No Content, sin un cuerpo de mensaje de respuesta.

En nuestro caso, devolveremos un código de estado 204 y un mensaje sin cuerpo de respuesta.

Cancelación de un Pedido

Hasta ahora, las operaciones de nuestro modelo de objetos "encajan" bastante bien en la especificación de los correspondientes métodos HTTP. Hemos utilzado GET para leer datos, PUT para realizar modificaciones POST para crear nuevos recursos, y DELETE para eliminarlos. En nuestro sistema, los Pedidos pueden eliminarse, o también cancelarse. Ya hemos comentado que el borrado de un recurso lo "elimina completamente" de nuestra base de datos. La operación de cancelación solamente cambia el estado del Pedido, y lo sigue manteniendo en el sistema. ¿Cómo podríamos modelar esta operación?

Cuando modelamos una interfaz RESTful para las operaciones de nuestro modelo de objetos, deberíamos plantearnos la siguiente pregunta: ¿la operación es un estado del recurso? Si la respuesta es sí, entonces deberíamos modelar esta operación "dentro" del formato de los datos.

La cancelación de un pedido es un ejemplo perfecto de esto que acabamos de decir. La clave está en que esta operación, en realidad es un estado específico del Pedido: éste puede estar cancelado o no. Cuando un usuario accede a un Pedido, puede desear conocer si el Pedido ha sido o no cancelado. Por lo tanto, la información sobre la cancelación debería formar parte del formato de datos de un Pedido. Así, añadiremos un nuevo elemento a la información del Pedido:

<pedido id="233">
    <link rel="self" href="http://org.expertojava/pedidos/233"/>
    <total>199.02</total>
    <fecha>December 22, 2008 06:56</fecha>
    <cancelado>false</cancelado>
    ...
</pedido>

Ya que el estado "cancelado" se modela en el propio formato de datos, modelaremos la acción de cancelación con una operación HTTP PUT, que ya conocemos:

PUT /pedidos/233 HTTP/1.1
Content-Type: application/xml

<pedido id="233">
    <link rel="self" href="http://org.expertojava/pedidos/233"/>
    <total>199.02</total>
    <fecha>December 22, 2008 06:56</fecha>
    <cancelado>true</cancelado>
    ...
</pedido>

En este ejemplo, modificamos la representación del Pedido con el elemento <cancelado> con valor true.

Este "patrón" de modelado, no siempre "sirve" en todos los casos. Por ejemplo, imaginemos que queremos ampliar el sistema de forma que "borremos" del sistema todos los pedidos cancelados. No podemos modelar esta operación de la misma manera que la de cancelación, ya que esta operación no cambia el estado de nuestra aplicación (no es en sí misma un estado de la aplicación).

Para resolver este problema, podemos modelar esta nueva operación como un "subrecurso" de /pedidos, y realizar un borrado de los pedidos cancelados, mediante el método HTTP POST de dicho subrecurso, de la siguiente forma:

POST /pedidos/eliminacion HTTP/1.1

Un efecto interesante de lo que acabamos de hacer es que, puesto que ahora eliminacion es una URI, podemos hacer que la interfaz de nuestro servicios RESTful evolucionen con el tiempo. Por ejemplo, la orden GET /pedidos/eliminacion podría devolver la última fecha en la que se procedió a eliminar todos los pedidos cancelados, así como qué pedidos fueron cancelados. ¿Y si queremos añadir algún criterio a la hora de realizar el borrado de pedidos cancelados? Podríamos introducir parámetros para indicar que sólo queremos eliminar aquellos pedidos que estén cancelados en una fecha anterior a una dada. Como vemos, podemos mantener una interfaz uniforme y ceñirnos a las operaciones HTTP tal y como están especificadas, y a la vez, dotar de una gran flexiblidad a la interfaz de nuestro sistema RESTful. Hablaremos con más detalle de los subrecursos en la siguiente sesión.

1.4.5. Implementación del servicio: Creación del proyecto Maven

Vamos a utilizar Maven para crear la estructura del proyecto que contendrá la implementación de nuestro servicio Rest. Inicialmente, podemos utilizar el mismo arquetipo con el que habéis trabajado en sesiones anteriores. Y a continuación modificaremos la configuración del fichero pom.xml, para implementar nuestros servicios.

Una opción es generar la estructura del proyecto directamente desde línea de comandos. El comando es el siguiente (recuerda que debes escribirlo en una misma línea. Los caracteres "\" que aparecen en el comando no forman parte del mismo, simplemente indican que no se debe pulsar el retorno de carro):

mvn --batch-mode archetype:generate \
      -DarchetypeGroupId=org.codehaus.mojo.archetypes \
      -DarchetypeArtifactId=webapp-javaee7 \
      -DgroupId=org.expertojava -DartifactId=ejemplo-rest

En donde:

  • archetypeGroupId y archetypeArtifactId son los nombres del groupId y artifactId del arquetipo Maven que nos va a generar la "plantilla" para nuestro proyecto

  • groupId y artifactId son los nombres que asignamos como groupId y artifactId de nuestro proyecto. En este caso hemos elegido los valores org.expertojava y ejemplo-rest, respectivamente

Si utilizamos IntelliJ para crear el proyecto tenemos que:
  1. Crear un nuevo proyecto (New Project)

  2. Elegir el tipo de proyecto Maven

  3. Crear el proyecto Maven a partir de un arquetipo con las siguientes coordenadas:

    • GroupId: org.codehaus.mojo.archetypes

    • ArtifactId: webapp-javaee7

    • Version: 1.1

  4. Indicar las coordenadas de nuestro proyecto:

    • GroupId: org.expertojava

    • ArtifactId: ejemplo-rest

    • Version: 1.0-SNAPSHOT

  5. Confirmamos los datos introducidos

  6. Para finalizar, especificamos el nombre de nuestro proyecto en IntelliJ:

    • Project Name: ejemplo-rest (este valor también identificará el nombre del módulo en IntelliJ)

  7. Por comodidad, marcaremos Enable autoimport para importar automáticamente cualquier cambio en el proyecto

Una vez que hemos creado el proyecto con IntelliJ, el paso siguiente es cambiar la configuración del pom.mxl que nos ha generado el arquetipo, para incluir las propiedades, dependencias, plugins,…​, que necesitaremos para implementar nuestros recursos REST.

Como ya sabemos, el fichero pom.xml contiene la configuración que utiliza Maven para construir el proyecto. A continuación indicamos las modificaciones en el fichero pom.xml generado inicialmente, para adecuarlo a nuestras necesidades particulares:

  • Cambiamos las propiedades del proyecto (etiqueta <properties>) por:

Propiedades del proyecto
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
  • Indicamos las dependencias del proyecto (etiqueta <dependencies>, en donde se incluyen las librerías necesarias para la construcción del proyecto). En nuestro caso, necesitamos incluir la librería javax:javaee-web-api:7.0 que contiene el api estándar de javaee 7. Marcamos el ámbito de la librería (etiqueta <scope>) como provided. Con esto estamos indicando que sólo necesitaremos el jar correspondiente para "compilar" el proyecto, y por lo tanto no incluiremos dicho jar, en el fichero war generado para nuestra aplicación, ya que dicha librería ya estará disponible desde el servidor de aplicaciones en el que residirá nuestra aplicación.

Librerías utilizadas para construir el proyecto (dependencias)
<dependencies>
  <dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-web-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>
  • A continuación configuramos la construcción del proyecto (etiqueta <build>), de la siguiente forma (cambiamos la configuración original por la que mostramos a continuación):

Configuración de la construcción del proyecto
<build>
  <!-- Especificamos el nombre del war que será usado como context root
       cuando despleguemos la aplicación -->
  <finalName>${project.artifactId}</finalName>

  <plugins>
    <!-- Compilador de java. Utilizaremos la versión 1.7 -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
        <configuration>
            <source>1.7</source>
            <target>1.7</target>
        </configuration>
    </plugin>

    <!-- Servidor de aplicaciones wildfly -->
    <plugin>
      <groupId>org.wildfly.plugins</groupId>
      <artifactId>wildfly-maven-plugin</artifactId>
      <version>1.0.2.Final</version>
      <configuration>
          <hostname>localhost</hostname>
          <port>9990</port>
      </configuration>
    </plugin>

    <!-- Cuando generamos el war no es necesario
         que el fichero web.xml esté presente -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
    </plugin>
  </plugins>
</build>

1.4.6. Implementación del servicio: Recursos JAX-RS

Una vez que tenemos la estructura del proyecto, implementaremos los recursos de nuestra aplicación, que serán clases Java que utilizarán anotaciones JAX-RS para enlazar y mapear peticiones HTTP específicas a métodos java, los cuales servirán dichas peticiones. En este caso, vamos a ilustrar con un ejemplo, una posible implementación para el recurso Cliente. Tenemos que diferenciar entre las clases java que representarán entidades de nuestro dominio (objetos java que representan elementos de nuestro negocio, y que serán almacenados típicamente en una base de datos), de nuestros recursos JAX-RS, que también serán clases java anotadas, y que utilizarán objetos de nuestro dominio para llevar a cabo las operaciones expuestas en el API RESTful que hemos diseñado.

Así, por ejemplo, implementaremos las clases:

  • Cliente.java: representa una entidad del dominio. Contiene atributos, y sus correspondientes getters y setters

  • ClienteResource.java: representa las operaciones RESTful sobre nuestro recurso Cliente que hemos definido en esta sesión. Es una clase java con anotaciones JAX-RS que nos permitirá insertar, modificar, borrar, consultar un cliente, así como consultar la lista de clientes de nuestro sistema.

Clases de nuestro dominio (entidades): Cliente.java

La clase que representa nuestra entidad del dominio Cliente es una clase java plana, con sus correspondientes atributos, y getters y setters.

Implementación del dominio: cliente.java
package org.expertojava.domain;

@XmlRootElement(name="cliente")
@XmlAccessorType(XmlAccessType.FIELD)
public class Cliente {
    private int id;
    private String nombre;
    private String apellidos;
    private String direccion;
    private String codPostal;
    private String ciudad;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getNombre() { return nombre; }
    public void setNombre(String nom) { this.nombre = nom; }

    public String getApellidos() { return apellidos; }
    public void setApellidos(String apellidos) {
        this.apellidos = apellidos; }

    public String getDireccion() { return direccion; }
    public void setDireccion(String dir) { this.direccion = dir; }

    public String getCodPostal() { return codPostal; }
    public void setCodPostal(String cp) { this.codPostal = cp; }

    public String getCiudad() { return ciudad; }
    public void setCiudad(String ciudad) { this.ciudad = ciudad; }
}

Hemos anotado la clase Cliente con @XmlRootElement y @XmlAccessorType. Hablaremos de estas anotaciones en sesiones posteriores, las cuales se encargan del serializado/deserializado del cuerpo del mensaje (en formato xml o json) a nuestra clase java Cliente.

Clases de nuestro servicio RESTful: ClienteResource.java

Una vez definido el objeto de nuestro dominio que representará un objeto Cliente, vamos a ver cómo implementar nuestros servicio JAX-RS para que diferentes usuarios, de forma remota, puedan interactuar con nuestra base de datos de clientes.

La implementación del servicio es lo que se denomina una resource class, que no es más que una clase java que utiliza anotaciones JAX-RS.

Por defecto, una nueva instancia de nuestra clase de recursos se crea para cada petición a ese recurso. Es lo que se conoce como un objeto per-request. Esto implica que se crea un objeto Java para procesar cada petición de entrada, y se "destruye" automáticamente cuando la petición se ha servido. Per-request también implica "sin estado", ya que no se guarda el estado del servicio entre peticiones.

Comencemos con la implementación del servicio:

package org.expertojava.services;

import ...;

@Path("/clientes")
public class ClienteResource {

    private static Map<Integer, Cliente> clienteDB =
            new ConcurrentHashMap<Integer, Cliente>();
    private static AtomicInteger idContador = new AtomicInteger();

Podemos observar que ClientResource es una clase java plana,y que no implementa ninguna interfaz JAX-RS particular. La anotación javax.ws.rs.Path indica que la clase ClienteResource es un servicio JAX-RS. Todas las clases que queramos que sean "reconocidas" como servicios JAX-RS tienen que tener esta anotación. Fíjate que esta anotación tiene el valor /clientes. Este valor representa la raíz relativa de la URI de nuestro servicio RESTful. Si la URI absoluta de nuestro servidor es, por ejemplo: http://expertojava.org, los métodos expuestos por nuestra clase ClienteResource estarían disponibles bajo la URI http://expertojava.org/clientes.

En nuestra clase, definimos un Mapa para el campo ClienteDB, que almacenará en memoria a los objetos Cliente de nuestro sistema. Utilizamos un java.util.concurrent.ConcurrentHashMap como tipo de clienteDB ya que nuestro recurso será accedido concurrentemente por los usuarios de nuestro servicio rest. El campo idContador lo utilizaremos para generar nuevos identificadores de nuestros objetos Cliente creados. El tipo de este campo es java.util.concurrent.atomic.AtomicInteger para garantizar que siempre generaremos un identificador único aunque tengamos peticiones concurrentes.

Justificación del caracter static de los atributos

Como nuestros objetos serán de tipo per-request, el runtime de JAX-RS creará una instancia de ClienteResource para cada peción que se realice sobre nuestro servicio. La máquina virtual de java ejecutará cada petición a nuestro servicio en un hilo (thread) diferente, permitiendo así el acceso concurrente a nuestro recurso. Puesto que hemos decidido almacenar en memoria la información de los clientes, necesitamos que los atributos clienteDB y idContador sean static, para que todas las instancias de ClienteResource tengan acceso a la lista de clientes en memoria y no haya problemas de concurrencia. En realidad, lo que estamos haciendo con ésto es permitir que el servicio guarde el estado entre peticiones. En un sistema real, ClienteResource probablemente interactúe con una base de datos para almacenar y recuperar la información de los clientes, y por lo tanto, no necesitaremos guardar el estado entre peticiones.

Una mejor solución sería no utilizar variables estáticas, y definir nuestro servicio como singleton. Si hacemos ésto, solamente se crearía una instancia de clienteResource y estaríamos manteniendo el estado de las peticiones. En la siguiente sesión explicaremos cómo configurar un servicio como singleton. Por simplicidad, de momento optaremos por la opción de que los objetos RESTful sean per-request.

Creación de clientes

Para implementar la creación de un nuevo cliente utilizamos el mismo modelo que hemos diseñado previamente. Una petición HTTP POST envía un documento XML que representa al cliente que queremos crear.

El código para crear nuevos clientes en nuestro sistema podría ser éste:

@POST (1)
@Consumes("application/xml") (2)
public Response crearCliente(Cliente cli) { (3)
  //el parámetro cli se instancia con los datos del cliente del body del mensaje HTTP
  idContador++;
  cli.setId(idContador.incrementAndGet());
  clienteDB.put(cli.getId(), cli);   (4)
  System.out.println("Cliente creado " + cli.getId()); (5)
  return Response.created(URI.create("/clientes/"
                + cli.getId())).build();  (6)
    }
1 se recibe una petición POST
2 el cuerpo de la petición debe tener formato xml
3 contiene la información del documento xml del cuerpo de la petición de entrada
4 se añade el nuevo objeto Cliente a nuestro "mapa" de clientes (clienteDB)
5 este método se ejectua en el servidor, por lo que el mensaje sólo será visible, por ejemplo, si consultamos los mensajes generados por el servidor durante la ejecución el método devuelve un código de respuesta 201 Created, junto con una cabecera Location apuntando a la URI absoluta del cliente que acabamos de crear

Vamos a explicar la implementación con más detalle.

Para enlazar peticiones HTTP POST con el método crearCliente(), lo anotamos con la anotación @javax.ws.rs.POST. La anotación @Path, combinada con la anotación @POST, enlaza todas las peticiones POST dirigidas a la URI relativa /clientes al método Java crearCliente().

La anotación javax.ws.rs.Consumes aplicada a crearCliente() especifica qué media type espera el método en el cuerpo del mensaje HTTP de entrada. Si el cliente incluye en su petición POST un media type diferente de XML, se envía un código de error al cliente.

El método crearCliente() tiene un parámetro de tipo Cliente. En JAX-RS, cualquier parámetro no anotado con anotaciones JAX-RS se considera que es una representación del cuerpo del mensaje de la petición de entrada HTTP. Las anotaciones que hemos introducido en la clase Cliente de nuestro dominio, realizan el trabajo de "convertir" el documento xml contenido en el cuerpo de la petición htpp de entrada en una instancia de nuestra clase Cliente.

Solamente UNO de los parámetros del método Java puede representar el cuerpo del mensaje de la petición HTTP. Esto significa que el resto de parámetros deben anotarse con alguna anotación JAX-RS, que veremos más adelante.

El método crearCliente() devuelve una respuesta de tipo javax.ws.rs.core.Response. El método estático Response.created() crea un objeto Response que contiene un código de estado 201 Created. También añade una cabecera Location con un valor similar a: http://expertojava.org/clientes/123, dependiendo del valor del valor de base de la raíz de la URI del servidor y el identificador generado para el objeto Cliente (en este caso se habría generado el identificador 123). Más adelante explicaremos con detalle el uso de la clase Response.

Consulta de clientes

A continuación mostramos un posible código para consultar la información de un cliente:

@GET
@Path("{id}")
@Produces("application/xml")
public Cliente recuperarClienteId(@PathParam("id") int id) {
  final Cliente cli = clienteDB.get(id);
  if (cli == null) {
      throw new WebApplicationException(Response.Status.NOT_FOUND);
  }
  return new Cliente(cli.getId(), cli.getNombre(), cli.getApellidos(),
              cli.getDireccion(), cli.getCodPostal(), cli.getCiudad());
}

En este caso, anotamos el método recuperarClienteId() con la anotación @javax.ws.rs.GET para enlazar las operaciones HTTP GET con este método Java.

También anotamos recuperarClienteId() con la anotación @javax.ws.rs.PRODUCES. Esta anotación indica a JAX-RS que valor tiene la cabecera HTTP Content-Type en la respuesta proporcionada por la operación GET. En este caso, estamos indicando que será de tipo application/xml.

En la implementación del método utilizamos el parámetro id para consultar si existe un objeto Cliente en nuestro mapa clienteDB. Si dicho cliente no existe, lanzaremos la excepción javax.ws.rs.WebApplictionException. Esta excepción provocará que el código de respuesta HTTP sea 404 Not Found, y significa que el recurso cliente requerido no existe. Discutiremos más adelante el tema del manejo de excepciones.

Modificación de clientes

Vamos a mostrar cómo sería el código para modificar un cliente:

@PUT
@Path("{id}")
@Consumes("application/xml")
public void modificarCliente(@PathParam("id") int id,
                            Cliente nuevoCli) {

  Cliente actual = clienteDB.get(id);
  if (actual == null)
      throw new WebApplicationException(Response.Status.NOT_FOUND);

  actual.setNombre(nuevoCli.getNombre()); actual.setApellidos(nuevoCli.getApellidos());
  actual.setDireccion(nuevoCli.getDireccion());
  actual.setCodPostal(nuevoCli.getCodPostal());
  actual.setCiudad(nuevoCli.getCiudad());
}

Anotamos el método modificarCliente() con @javax.ws.rs.PUT para enlazar las peticiones HTTP PUT a este método. Al igual que hemos hecho con recuperarClienteId(), el método modificarCliente() está anotado adicionalmente con @Path, de forma que podamos atender peticiones a través de las URIs /clientes/{id}.

El método modificarCliente() tiene dos parámetros. El primero es un parámetro id que representa el objeto Cliente que estamos modificando. Al igual que ocurría con el método recuperarClienteId(), utilizamos la anotación @PathParam para extraer el identificador a partir de la URI de la petición de entrada. El segundo parámetro es un objeto Cliente, que representa el cuerpo del mensaje de entrada, ya que no tiene ninguna anotación JAX-RS.

El método intenta encontrar un objeto Cliente en nuestro mapa clienteDB. Si no existe, provocamos una WebApplicationException que enviará una respuesta al usuario con el código 404 Not Found. Si el objeto Cliente existe, modificamos nuestro objeto Cliente existente con los nuevos valores que obtenemos de la petición de entrada.

1.4.7. Construcción y despliegue del servicio

Una vez implementado nuestro servicio RESTful, necesitamos poner en marcha el proceso de construcción. El proceso de construcción compilará,…​, empaquetará, …​, y finalmente nos permitirá desplegar nuestro servicio en el servidor de aplicaciones.

Para poder, empaquetar nuestro servicio RESTful como un war, que se desplegará en el servidor de aplicaciones, vamos a incluir un "proveedor" de servicios JAX-RS, en el descriptor de despliegue de nuestra aplicación (fichero web.xml). En la siguiente sesión justificaremos la existencia de dicho "proveedor" (que será un servlet) y explicaremos el modelo de despliegue de los servicios JAX-RS. Los pasos a seguir desde IntelliJ para configurar el despliegue de nuestro servicio son:

  • Añadimos el directorio WEB-INF como subdirectorio de webapp

  • Nos vamos a File→Project Structure…​→Facets→Web, y añadimos el fichero web.xml (en el panel Deployment descriptors, pulsamos sobre +, y seleccionamos web.xml). Editamos este fichero para añadir el servlet que servirá las peticiones de nuestros servicios REST, indicando cuál será la ruta en la que estarán disponibles dichos servicios (en nuestro ejemplo indicaremos la ruta "/rest/"). Dicha ruta es relativa a la ruta del contexto de nuestra aplicación, y que por defecto, es el nombre del artefacto ".war" desplegado, que hemos indicado en la etiqueta <finalName> dentro del <build> del fichero de configuración de Maven (pom.xml).

Contenido del fichero web.xml:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <!-- One of the way of activating REST Services is adding these lines,
    the server is responsible for adding the corresponding servlet automatically,
    if the src folder has the Annotations to receive REST invocation-->
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
</web-app>

A continuación ya estamos en disposición de iniciar la construcción del proyecto con Maven para compilar, empaquetar y desplegar nuestro servicio en Wildfly.

Si utilizamos el terminal, la secuencia de pasos para empaquetar y desplegar nuestro proyecto serían:

cd ejemplo-rest  (1)
mvn package  (2)
./usr/local/wildfly-8.2.1.Final/bin/standalone.sh  (3)
mvn wildfly:deploy  (4)
1 Nos situamos en el directorio que contiene el pom.xml de nuestro proyecto
2 Empaquetamos el proyecto (obtendremos el .war)
3 Arrancamos el servidor wildfly
4 Desplegamos el war generado en el servidor wildfly
Secuencia correcta de acciones

En ejecuciones posteriores, después de realizar modificaciones en nuestro código, es recomendable ejecutar "mvn clean" previamente al empaquetado del proyecto.

Por lo tanto, y suponiendo que el servidor de aplicaciones ya está en marcha, la secuencia de acciones (comandos maven) que deberíamos realizar para asegurarnos de que vamos a ejecutar exactamente la aplicación con los últimos cambios que hayamos introducido son:

  • mvn wildfly:undeploy

  • mvn clean

  • mvn package

  • mvn wildfly:deploy

También podríamos realizar todas estas acciones con un único comando maven:

  • mvn wildfly:undeploy clean package wildfly:deploy

Si utilizamos IntelliJ, añadiremos un nuevo elemento de configuración de ejecución desde Run→Edit Configurations. Pulsamos el icono + y añadimos la configuración de tipo JBosss Server→Local. Podemos ponerle por ejemplo como nombre "Wilfdly start". A continuación configuramos la ruta del servidor wildfly como: /usr/local/wildfly-8.2.1.Final.

Cuando lancemos este elemento de ejecución desde IntelliJ, automáticamente se construirá el proyecto (obtendremos el war), y arrancaremos wildfly. Para desplegar el war, utlizaremos la ventana Maven Projects y haremos doble click sobre ejemplo-rest→Plugins→wildfly→wildfly:deploy

1.4.8. Probando nuestro servicio

Podemos probar nuestro servicio de varias formas. Vamos a mostrar como hacerlo directamente desde línea de comandos, utilizando IntelliJ, o bien utilizando la herramienta Postman (que tenéis disponible desde el navegador Chrome).

Invocación del servicio desde línea de comandos

Utilizaremos la herramienta curl. Por ejemplo, para realizar una inserción de un cliente, el comando sería:

curl -i -H "Accept: application/xml" -H "Content-Type: application/xml" -X POST -d @cliente.xml http://localhost:8080/ejemplo-rest/rest/clientes/

En donde:

-i

También se puede utilizar la opción equivalente --include. Indica que se debe incluir las cabeceras HTTP en la respuesta recibida. Recuerda que la petición POST devuelve en la cabecera Location el enlace del nuevo recurso creado (puede hacerlo en una cabedera Location, o como un campo <link> del elemento creado en el cuerpo del mensaje, lo veremos más adelante). Esta información será necesaria para poder consultar la información del nuevo cliente creado.

-H

Indica un par cabecera:_valor_. En nuestro caso lo utilizamos para especificar los valores de las cabeceras HTTP Accept y Content-Type

-X

Indica el método a invocar (GET, POST, PUT,…​)

-d

También se puede utilizar --data. Indica cuáles son los datos enviados en el mensaje de entrada en una petición POST. Si los datos especificados van precedidos por @, estamos indicando que dichos datos están en un fichero. Por ejemplo, en la orden anterior, escribimos en el fichero cliente.xml los datos del cliente que queremos añadir en nuestro sistema.

El contenido del fichero cliente.xml podría ser éste:

<?xml version="1.0" encoding="UTF-8"?>
<clientes>
  <cliente>
    <nombre>Pepe </nombre>
    <apellidos>Garcia Lopez</apellido1>
    <direccion>Calle del pino, 3</apellido2>
    <codPostal>0001</codPostal>
    <ciudad>Alicante</ciudad>
  </cliente>
</clientes>

Finalmente, en la orden indicamos la URI a la que queremos acceder, en este caso:

http://localhost:8080/ejemplo-rest/rest/clientes/

Una vez insertado el cliente, podemos recuperar el cliente, utilizando el enlace que se incluye en la cabecera de respuesta Location

curl -i -H "Accept: application/xml" -H "Content-Type: application/xml" -X GET http://localhost:8080/ejemplo-rest/rest/clientes/1
Invocación del servicio desde IntelliJ

IntelliJ nos proporciona una herramienta para probar servicios REST, desde Tools→Test RESTful Web Service Desde esta nueva ventana podremos invocar al servicio REST indicando el tipo de petición HTTP, así como las cabeceras y cuerpo de la petición.

La siguiente figura muestra la elaboración de una petición POST a nuestro servicio REST:

Petición POST realizada desde Intellij

A continuación mostramos la ejecución de una petición GET:

Resultado de ejecución de una petición GET ejecutada desde Intellij

Cuando realizamos una petición POST debemos indicar el contenido del cuerpo del mensaje. En la siguiente figura observamos que tenemos varias opciones disponibles, como por ejemplo "teclear" directamente dicho contenido (opción Text), o bien "subir" dicha información desde un fichero en nuestro disco duro (opción File Contents). Podemos ver que hemos elegido esta última opción para probar nuestro servicio.

Resultado de ejecución de una petición GET ejecutada desde Intellij
Invocación del servicio desde Postman

Otra alternativa sencilla para probar nuestro servicio REST es la herramienta postman, que podemos lanzar desde el navegador, en nuestro caso: Chrome.

Accederemos a la aplicación desde la barra de marcadores, seleccionando "Aplicaciones", y y a continuación, pulsaremos sobre el icono "Postman".

El aspecto de la herramienta es el que mostramos a continuación:

Resultado de ejecución de una petición GET ejecutada desde Postman

Postman, a diferencia de las alternativas anteriores, nos permitirá guardar un "historial" de peticiones, de forma que podamos repetir la ejecución de nuestros tests, exactamente de la misma forma, aunque no de forma automática, sino que tenemos que lanzar "manualmente" cada test que queramos volver a ejecutar.

También podemos crear "colecciones", que no son más que carpetas que contienen un conjunto de peticiones de nuestro "historial". Por ejemplo, podemos crear la colección s1-rest-ejercicio1 en donde guardaremos todas las peticiones que hayamos hecho sobre el ejercicio 1 de la primera sesión de rest.

Podéis crearos una cuenta gratuita para almacener y gestionar vuestras peticiones rest. Una vez que tengáis creadas varias colecciones, Postman nos permite "guardarlas" en nuestro disco duro en formato json.

Podemos guardar nuestras colecciones en formato JSON

1.5. Ejercicios

Antes de empezar a crear los proyectos, debes descargarte el repositorio git java_ua/ejercicios-rest-expertojava en el que vas a implementar los ejercicios relativos a la asignatura de Servicios REST. El proceso es el mismo que el seguido en sesiones anteriores:

  1. Accedemos al repositorio y realizamos un Fork en nuestra cuenta personal (así podremos tener una copia con permisos de escritura)

  2. Realizamos un Clone en nuestra máquina:

$ git clone https://bitbucket.org/<alumno>/ejercicios-rest-expertojava

De esta forma se crea en nuestro ordenador el directorio ejercicios-rest-expertojava y se descarga en él un proyecto IntelliJ en el que iremos añadiendo MÓDULOS para cada uno de los ejercicios. Contiene también el fichero gitignore, así como diferentes módulos con las plantillas que vayamos a necesitar para realizar los ejercicios.

A partir de este momento se puede trabajar con dicho proyecto y realizar Commit y Push cuando sea oportuno:

$ cd ejercicios-rest-expertojava
$ git add .
$ git commit -a -m "Mensaje de commit"
$ git push origin master

Los MÓDULOS IntelliJ que iremos añadiendo, tendrán todos el prefijo sx-, siendo x el número de la sesión correspondiente (por ejemplo s1-ejercicio, s2-otroEjercicio,…​).

1.5.1. Servicio REST ejemplo (0 puntos)

Para familiarizarnos con las peticiones http POST, PUT, GET, DELETE se proporciona el MÓDULO s1-ejemplo-rest, con la implementación de un servicio rest que podéis probar, bien desde línea de comandos con la utilidad curl, desde el navegador con la herramienta postman , o bien desde IntelliJ con el cliente REST incluido en el IDE.

En el directorio src/main/resources de dicho módulo tenéis un fichero de texto (instrucciones.txt) con las instrucciones para construir, desplegar y probar la aplicación de ejemplo.

1.5.2. Servicio REST saludo (1 punto)

Vamos a implementar un primer servicio RESTful muy sencillo. Para ello seguiremos las siguientes indicaciones:

  • Creamos un MÓDULO Maven con IntelliJ (desde el directorio ejercicios-rest-expertojava) con el arquetipo webapp-javaee7, tal y como hemos visto en los apuntes de la sesión. Las coordenadas del artefacto Maven serán:

    • GroupId: org.expertojava

    • ArtifactId: s1-saludo-rest

    • version: 1.0-SNAPSHOT

  • Configuramos el pom.mxl del proyecto para poder compilar, empaquetar y desplegar nuestro servicio. Consulta los apuntes para ver cuál debe ser el contenido de las etiquetas <properties>, <dependencies> y <build>.

  • Creamos la carpeta WEB-INF y añadimos el fichero de configuración web.xml tal y como hemos visto en los apuntes (esto será necesario para configurar el despliegue). En este caso queremos mapear los servicios REST, contenidos en el paquete org.expertojava, al directorio /recursos dentro de nuestro contexto (recuerda que el contexto de nuestra aplicación web vendrá dado por el valor de la etiqueta <finalName>, anidada dentro de <build>).

  • Creamos un recurso de nombre HolaMundoResource, que se mapee a la dirección /holamundo. Implementar un método, de forma que al acceder a él por GET nos devuelva en texto plano (text/plain) el mensaje "Hola mundo!". Una vez desplegada la aplicación en el servidor WildFly, prueba el servicio mediante la utilidad Postman desde Chrome. Comprobar que la invocación:

    GET http://localhost:8080/saludo-rest/holamundo

Devuelve como cuerpo del mensaje: "Hola mundo!"

  • Vamos a añadir un segmento variable a la ruta. Implementa un método GET nuevo, de forma que si accedemos a /holamundo/nombre, añade el nombre indicado al saludo (separado por un espacio en blanco y seguido por "!!").

Una vez desplegada la aplicación en el servidor WildFly, prueba el servicio mediante la utilidad "Test RESTFul Web Service" de IntelliJ, o con Postman. Comprobar que la invocación:

GET http://localhost:8080/saludo-rest/holamundo/pepe

Devuelve como cuerpo del mensaje: "Hola mundo! pepe!!"

  • Hacer que se pueda cambiar el saludo mediante un método PUT. El nuevo saludo llegará también como texto plano en el cuerpo de la petición, y posteriores invocaciones a los métodos GET utilizarán el nuevo saludo. Almacenaremos el nuevo saludo en una variable estática de nuestro recurso. ¿Qué pasa si no lo es? (lo hemos explicado en los apuntes, puedes hacer la prueba para ver qué ocurre si la variable no es estática).

Una vez desplegada la aplicación en el servidor WildFly, prueba el servicio con Postman, o bien mediante la utilidad "Test RESTFul Web Service" de IntelliJ. Realizar las siguientes invocaciones (en este orden):

PUT http://localhost:8080/saludo-rest/holamundo/
y en el cuerpo del mensaje: "Buenos días"
GET http://localhost:8080/saludo-rest/holamundo
GET http://localhost:8080/saludo-rest/holamundo/pepe

La segunda invocación debe devolver como cuerpo del mensaje: "Buenos dias" Al ejecutar la tercera invocación el cuerpo del mensaje de respuesta debería ser: "Buenos dias Pepe!!"

1.5.3. Servicio REST foro (1 punto)

Vamos a implementar un servicio RESTful que contemple las cuatro operaciones básicas (GET, PUT, POST y DELETE). Se trata de un foro con en el que los usuarios pueden intervenir, de forma anónima, en diferentes conversaciones.

Primero debes crear un nuevo módulo Maven, configurar el pom.xml, así como el fichero web.xml, de la misma forma que hemos hecho en el ejercicio anterior, pero para este ejercicio:

  • Las coordenadas del módulo Maven serán:

    • GroupId: org.expertojava

    • ArtifactId: s1-foro-rest

    • version: 1.0-SNAPSHOT

  • Nuestros servicios REST estarán disponibles en la URI http://localhost:8080/s1-foro-rest/

El foro estará formado por diferentes mensajes. Por lo tanto el modelo del dominio de nuestra aplicación estará formado por la clase Mensaje, que contendrá un identificador, y una cadena de caracteres que representará el contenido del mensaje (recuerda que debes implementar los correspondientes getters y setters).

Por simplicidad, vamos a almacenar los mensajes de nuestro foro en memoria. Estos estarán disponibles desde la clase DatosEnMemoria, que contendrá la variable estática:

static Map<Integer, Mensaje> datos = new HashMap<Integer, Mensaje>();

Los servicios que proporcionará el foro estarán implementados en la clase MensajeResource. Se accederá a ellos través de la ruta relativa a la raíz de nuestros servicios: "/mensajes". Concretamente podremos realizar las siguientes operaciones:

  • Añadir un nuevo mensaje al foro con la URI relativa a la raíz de nuestros servicios: "/mensajes". El texto del mensaje estará en el cuerpo de la petición y el tipo MIME asociado será text/plain (contenido de la cabecera Content-type de la petición HTTP). Nuestra respuesta debe incluir en la cabecera Location de la respuesta HTTP, la URI del nuevo recurso creado. Utiliza para ello la clase Response tal y como hemos mostrado en el código de ejemplo proporcionado para el ejercicio anterior. Hablaremos con detalle sobre esta clase en sesiones posteriores.

Recuerda que para acceder al cuerpo de la petición basta con definir un parámetro de tipo String. JAX-RS automáticamente lo instanciará a partir del cuerpo de la petición y lo convertirá en un objeto de tipo String.

  • Modificar un mensaje determinado con un identificador con valor id, a través de la URI relativa a la raíz de nuestros servicios: /mensajes/id (id debe ser, por tanto, un segmento de ruta variable). Si no existe ningún mensaje con el identificador id, se lanzará la excepción: WebApplicationException(Response.Status.NOT_FOUND)

  • Borrar un mensaje determinado con un identificador con valor id, a través de la URI relativa a la raíz de nuestros servicios: /mensajes/id. Igual que en el caso anterior, si el identificador proporcionado no se corresponde con el de ningún mensaje del foro, se lanzará la excepción: WebApplicationException(Response.Status.NOT_FOUND)

  • Consultar todos los mensajes del foro (la URI relativa será: /mensajes). El resultado se mostrará en tantas líneas como mensajes. Cada mensaje irá precedido de su identificador. También se informará del número total de mensajes en el foro. (La respuesta será una cadena de caracteres. Al final del ejercicio mostramos un ejemplo de mensaje de respuesta para esta operación)

  • Consultar un mensaje determinado con un identificador con valor id, a través de la URI relativa a la raíz de nuestros servicios: /mensajes/id. Si el identificador proporcionado no se corresponde con el de ningún mensaje del foro, se lanzará la excepción: WebApplicationException(Response.Status.NOT_FOUND)

Prueba el servicio utilizando Postman, o el cliente de IntelliJ para servicios REST, con las siguientes entradas:

  • Crea los mensajes "Mensaje numero 1", "Mensaje numero 2", Mensaje numero 3", en este orden

  • Consulta los mensajes del foro. El resultado debe ser:

    "1: Mensaje numero 1

    2: Mensaje numero 2

    3: Mensaje numero 3

    Numero total de mensajes = 3"

  • Cambia el mensaje con identificador 2 por: "Nuevo mensaje numero 2"

  • Consulta los mensajes del foro. El resultado debe ser:

    "1: Mensaje numero 1

    2: Nuevo Mensaje numero 2

    3: Mensaje numero 3

    Numero total de mensajes = 3"

  • Borra el mensaje con identificador 3

  • Consulta el mensaje con el identificador 3. Se debe obtener una respuesta 404 Not Found

  • Consulta los mensajes del foro. El resultado debe ser:

    "1: Mensaje numero 1

    2: Nuevo Mensaje numero 2

    Numero total de mensajes = 2"

  • Añade el mensaje "Mensaje final". Vuelve a consultar los mensajes, el resultado debe ser:

    "1: Mensaje numero 1

    2: Nuevo Mensaje numero 2

    4: Mensaje final

    Numero total de mensajes = 3"

Para evitar problemas con el id generado si hemos borrado mensajes, lo más sencillo es que el identificador vaya incrementándose siempre con cada nuevo mensaje. Esto puede hacer que "queden huecos" en la numeración, como en el ejemplo anterior.