====== 10.6 Controlador ====== Llevamos ya 5 temas sobre REST y aun no hemos visto como hacerlo funcionar aunque ya se ha explicado en que consiste. Por fin en este tema veremos como implementar un controlador que siga la arquitectura de REST. Para llegar a este punto hemos tenido primero que aprender: * A manejar datos en formato JSON. En el tema [[unidades:10_servidor:02_json]] * A configurar una aplicación para que funciones Spring. En el tema [[unidades:10_servidor:03_spring]] * A configurar su sistema de inyección de dependencias. En el tema [[unidades:10_servidor:04_inyecciondependencias]] * A programar todo el acceso a la base de datos. En el tema [[unidades:10_servidor:05_basededatos]] Para ir explicando todo lo relacionado con el controlador vamos a ir haciendo el ejemplo poco a poco con la clase ''SeguroMedicoController''. La clase empezará estando vacía package es.cursohibernate.seguros.presentacion.controller; public class SeguroMedicoController { } y vamos a ir añadiendo código poco a poco. Con todo ésto ya estamos preparado para crear un controlador REST con las funcionalidades que vimos en el tema [[unidades:10_servidor:01_rest]]. En ese tema mostramos una tabla con las URLs que había que implementar. Como cada una de aquellas URL va a ser un método Java de nuestro controlador podemos modificar aquella tabla añadiendo el nombre de los métodos Java que vamos a crear y también vamos a poner las URL de ''SeguroMedico'' ^ Método Java ^ Descripción ^ URL ^ Método HTTP ^ JSON Enviado ^ JSON Retornado ^ | **''read''** | Leer un seguro médico| ''/api/SeguroMedico/{idSeguroMedico}'' | GET | Ninguno | Seguro medico leido | | **''find''** | Buscar seguros médicos| ''/api/SeguroMedico/?columnaBusqueda1=valor1&columnaBusqueda2=valor2&....'' | GET | Ninguno | Array de Seguros medicos| | **''insert''** | Añadir un seguro médico| ''/api/SeguroMedico'' | POST | Seguro medico a insertar | Seguro medico insertado | | **''update''** | Actualizar un seguro médico| ''/api/SeguroMedico/{idSeguroMedico}'' | PUT | Seguro medico a actualizar | Seguro medico actualizado | | **''delete''** | Borrar un seguro médico| ''/api/SeguroMedico/{idSeguroMedico}'' | DELETE | Ninguno | Ninguno | ===== Controlador ===== Pero empecemos desde el principio y veamos en que consiste un controlador. Un controlador es una clase Java a la que se le llama cierto método de ella cuando se pide al navegador cierta URL. Para que esto ocurra es necesario que la clase tenga 3 características. * Anotacion @Controller * Paquete de la clase * value de la anotación @RequestMapping ==== Anotacion @Controller ==== Es necesario que le digamos a Spring que clases Java son controladores. Para ello debemos incluir la anotación ''org.springframework.stereotype.Controller'' a nivel de clase. package es.cursohibernate.seguros.presentacion.controller; import org.springframework.stereotype.Controller; @Controller public class SeguroMedicoController { } * Línea 5: Añadimos la anotación ''@Controller'' ==== Paquete de la clase ==== El paquete donde se encuentra la clase debe ser un paquete o subpaquete del definido en el fichero ''dispatcher-servlet.xml'', concretamente en el atributo ''base-package'' definido en el tag ''''. La línea sirve para decirle a Spring el paquete a partir del cual buscar clases que estén anotadas con cierta anotaciones, entre ellas la anotación ''@Controller'' y así sepa que controladores hay. En caso de que nuestra clase no estuviera dentro de ese paquete o alguno de sus subpaquetes nunca se encontraría la clase. ==== value de la anotación @RequestMapping ==== La última de las características que debe tener la clase es tener por lo menos un método anotado con la anotación ''@RequestMapping'' y que dicha anotación tenga un atributo llamado ''value'' con parte de la URI que especifica cuando será llamado ese método. package es.cursohibernate.seguros.presentacion.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SeguroMedicoController { @RequestMapping(value="/SeguroMedico") public void read() { } } * Línea 9: Indicamos que este método debe ser llamado si parte de la URI pedida es ''/SeguroMedico'' === URL === El atributo ''value'' solo es una parte de la URL, ¿cual es la URL que lleva hasta ese método? La URL que en nuestro ejemplo llamará a este método es: ''http://localhost:8084/seguros/api/SeguroMedico''. Vamos a partir en trozos la URL para ver de donde viene cada parte. * ''http://localhost:8084'' : Es el host y el puerto donde está instalado el Tomcat. * ''/seguros'' : Es el contexto de la aplicación. Su nombre se define en el fichero ''context.xml'' , en el atributo ''path'' del tag ''''. * ''/api'' : El la URI que siempre maneja Spring. La definimos en el fichero ''web.xml'' , en el tag '''' * ''/SeguroMedico'' : Es la parte que finalmente definimos en la anotación ''@RequestMapping'' del método a llamar Por lo tanto la URL , en nuestro ejemplo, de cualquier método de un controlador siempre empezará por ''http://localhost:8084/seguros/api'' Recuerda que parte de la URL que se usa al llamar a un controlador incluye la parte común gestionada por Spring que en nuestro caso es ''/api'' ===== Datos de entrada ===== Pasemos a ver ahora como podemos obtener los valores de entrada que vienen por la conexión HTTP. ==== Método HTTP ==== Para saber el método HTTP usado podríamos pensar que hay un método Java que nos dice que método HTTP utilizado, aunque dicho método existe no lo vamos a usar. Spring permite de una forma declarativa hacer que llamen a nuestro método Java solo cuando el es cierto método HTTP. La forma es añadir a la anotación ''@RequestMapping'' otro atributo llamado ''method'' con el método HTTP que debe tener la petición para que llamen a ese método. Veamoslo mejor con un ejemplo: package es.cursohibernate.seguros.presentacion.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @RequestMapping(value="/SeguroMedico",method=RequestMethod.GET) public void read() { } } * Línea 10: Ahora solo se llamará al método Java ''read'' no solo según la URL sino tambien si el método HTTP solo es ''GET''. Como podemos ver en la anotación ''@RequestMapping'' hay un parámetro llamado ''method'' en el que indicamos que cuando se llame a esa URL con ese método HTTP llamemos a dicho el método Java. ==== Parámetros de la URL ==== Algunas URL incluyen una parte variable con el ''idSeguroMedico'', ¿como podemos desde Java acceder al valor de dicha parte variable de la URL? Para poder acceder debemos hacer 2 cosas: * Indicar en la URL que esa parte es variable, poniendo entre ''%%{ %%}'' la parte variable * Añadiendo un parámetro al método de Java y usando una anotación ''@PathVariable'' con el nombre de la parte variable para que lo relacione con la URL. Veamos el ejemplo: package es.cursohibernate.seguros.presentacion.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @RequestMapping(value="/SeguroMedico/{idSeguroMedico}",method=RequestMethod.GET) public void read(@PathVariable("idSeguroMedico") int idSeguroMedico) { } } * Línea 11: Se ha puesto entre ''%%{ %%}'' la parte de ''idSeguroMedico'' para indicar que esa parte es variable. * Línea 12: Ahora el método Java tiene un parámetro de tipo ''int'' que se llama ''idSeguroMedico'' y lo relacionamos con la URL con la anotación ''@PathVariable("idSeguroMedico")''. Siendo el valor que hay en ''@PathVariable'' exactamente el mismo que hay entre ''%%{ %%}'' de la URL. Ahora tenemos en Java el parámetro ''idSeguroMedico'' que obtendrá su valor a partir de la URL, concretamente de lo que hay entre llaves ''{idSeguroMedico}'' La propia variable Java se puede llamar como queramos pero lo que hay dentro de ''@PathVariable'' debe ser exactamente el mismo texto que hay entre ''%%{ %%}'' de la URL. En AngularJS también había partes variable en las rutas y se usaban los dos puntos ":" para indicar que era variable. ==== Accediendo al JSON de entrada ==== Por último nos queda acceder al texto que nos envían por HTTP con los métodos ''POST'' y ''PUT'', es cual será el JSON de entrada. Acceder a él es tan sencillo como añadir otro parámetro al método Java de tipo ''String'' con la anotación ''@RequestBody''. En nuestro ejemplo vamos ahora a añadir un nuevo método Java llamado ''insertar'' que se llame por ''POST'' para poder acceder de esa forma al JSON de entrada. package es.cursohibernate.seguros.presentacion.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @RequestMapping(value="/SeguroMedico/{idSeguroMedico}",method=RequestMethod.GET) public void read(@PathVariable("idSeguroMedico") int idSeguroMedico) { } @RequestMapping(value="/SeguroMedico",method=RequestMethod.POST) public void insert(@RequestBody String jsonEntrada) { } } * Línea 17 y 18: Creamos el nuevo método que será llamado con la URI "/SeguroMedico", con el método HTTP ''POST'' * Línea 18: El parámetro ''jsonEntrada'' contendrá el String con el cuerpo de la petición ya que tiene la anotación ''@RequestBody''. Ahora tenemos el parámetro ''jsonEntrada'' un String con el JSON ((o XML ,etc,)) que nos han enviado en el cuerpo de la petición HTTP. ==== El formato de los datos ==== Por ahora hemos limitado la llamada a los métodos Java en función de la URL y del método HTTP, pero también deberíamos limitar la llamada a dichos métodos en función del formato en el que nos envían los datos y en función del formato de los datos en lo que quieren que les enviemos. ya comentamos en [[unidades:10_servidor:01_rest#el_formato_de_los_datos]] Que al hacernos una petición HTTP se suelen enviar 2 cabeceras: * ''Content-Type'' : El cliente indica el formato de los datos que el propio cliente está enviado en el cuerpo de la petición ,es decir el formato de los datos que el servidor obtendrá mediante el ''@RequestBody''. * ''Accept'' : El cliente indica el formato en el que desea que el servidor le retorne los datos que está solicitando.Sin embargo posteriormente el servidor puede hacer lo que quiera y enviarlos en el formato que quiera y para ello en la respuesta definirá la cabecera ''Content-Type'', aunque por ejemplo no sería adecuado que al servidor se le solicitara JSON y él retornara XML. Así que si nuestros servicios REST solo van a aceptar JSON y retornar JSON, ¿no deberíamos restringir que solo llamaran a los métodos Java si el cliente nos envia o nos solicita datos en formato JSON? Para eso la anotación ''@RequestMapping'' dispone de otros 2 atributos: * ''consumes'' : El método solo será llamado si el cliente indica mediante ''Content-Type'' un formato definido en ''consumes''. * ''produces'' : El método solo será llamado si el cliente indica mediante ''Accept'' un formato definido en ''produces''. Ahora debemos modificar nuestros 2 métodos para indicar que los formatos que aceptan / devuelven son en formato JSON. package es.cursohibernate.seguros.presentacion.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @RequestMapping(value="/SeguroMedico/{idSeguroMedico}",method=RequestMethod.GET,produces = "application/json") public void read(@PathVariable("idSeguroMedico") int idSeguroMedico) { } @RequestMapping(value="/SeguroMedico",method=RequestMethod.POST,consumes = "application/json",produces = "application/json") public void insert(@RequestBody String jsonEntrada) { } } * Línea 12: El método ''read'' solo será llamado si el cliente acepta ((Cabecera ''Accept'')) que el servidor le retorne los datos en formato ''"application/json"''. No hemos indicado el atributo ''consumes'' ya que al método ''read'' no se le envian datos en el cuerpo de la petición. * Línea 17: El método ''insert'' solo será llamado si el cliente acepta ((Cabecera ''Accept'')) que el servidor le retorna los datos en formato ''"application/json"'' y también si el tipo de datos ((Cabecera ''Content-Type'')) que envía al servidor está en formato ''"application/json"''. en este caso si que se ponen ambos atributos que ya el método si que acepta y retorna datos en formato ''"application/json"''. Ahora si enviamos una petición con un ''Content-Type'' o un ''Accept'' no válido , Spring no retornará un error ''406 Not Acceptable''. Una cosa interesante es que si el cliente no pone el tipo en ''Content-Type'' o en ''Accept'' pero solo hay un método Java para esa URL y ese método HTTP si que llamará al método Java. Esto último permite gracias al protocolo HTTP tener por ejemplo 2 métodos que sea iguales excepto en el formato de los datos que retornan. en el siguiente ejemplo según la cabecera ''Accept'' se llamaría a uno u otro. @RequestMapping(value="/SeguroMedico/{idSeguroMedico}",method=RequestMethod.GET,produces = "application/json") public void readJSON(@PathVariable("idSeguroMedico") int idSeguroMedico) { } @RequestMapping(value="/SeguroMedico/{idSeguroMedico}",method=RequestMethod.GET,produces = "application/xml") public void readXML(@PathVariable("idSeguroMedico") int idSeguroMedico) { } Desgraciadamente si probamos el ejemplo con la petición ''/SeguroMedico/5'' no dará el siguiente error: Ambiguous handler methods mapped for HTTP path '/SeguroMedico/5' Para soluciones el error deberemos añadir las siguientes 2 líneas en el fichero ''dispatcher-servlet.xml'': ==== HttpServletRequest ==== Por último Spring también nos permite acceder a todos los datos que teníamos en los Servlets mediante la clase ''HttpServletRequest'', para ello solo tenemos que declarar un parámetro en cada método de la clase ''HttpServletRequest''. package es.cursohibernate.seguros.presentacion.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.GET, produces = "application/json") public void read(HttpServletRequest httpServletRequest, @PathVariable("idSeguroMedico") int idSeguroMedico) { } @RequestMapping(value = "/SeguroMedico", method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public void insert(HttpServletRequest httpServletRequest, @RequestBody String jsonEntrada) { } } * Líneas 14 y 17: Declaramos un parámetro de la clase ''HttpServletRequest'' para poder tener acceso a toda la información de la petición. ===== Datos de salida ===== Antes de empezar a ver como retornar la información al cliente , vamos a modificar los 2 métodos para generar la nueva información que queremos retornar en función de los datos de entrada. package es.cursohibernate.seguros.presentacion.controller; import es.cursohibernate.seguros.dominio.SeguroMedico; import es.cursohibernate.seguros.persistencia.BussinessException; import es.cursohibernate.seguros.persistencia.SeguroMedicoDAO; import es.cursohibernate.seguros.presentacion.json.JsonTransformer; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @Autowired JsonTransformer jsonTransformer; @Autowired SeguroMedicoDAO seguroMedicoDAO; @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.GET, produces = "application/json") public void read(HttpServletRequest httpServletRequest, @PathVariable("idSeguroMedico") int idSeguroMedico) { try { SeguroMedico seguroMedico=seguroMedicoDAO.get(idSeguroMedico); String jsonSalida=jsonTransformer.toJson(seguroMedico); } catch (BussinessException ex) { } catch (Exception ex) { } } @RequestMapping(value = "/SeguroMedico", method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public void insert(HttpServletRequest httpServletRequest, @RequestBody String jsonEntrada) { try { SeguroMedico seguroMedico=(SeguroMedico)jsonTransformer.fromJson(jsonEntrada, SeguroMedico.class); seguroMedicoDAO.insert(seguroMedico); String jsonSalida=jsonTransformer.toJson(seguroMedico); } catch (BussinessException ex) { } catch (Exception ex) { } } } * Línea 19: Declaramos la propiedad ''jsonTransformer'' y Spring inyectará la implementación. * Línea 22: Declaramos la propiedad ''jsonTransformer'' y Spring inyectará la implementación. * Línea 27: En función del ''idSeguroMedico'' obtenemos el objeto de la clase ''SeguroMedico'' de la base de datos mediante el DAO. * Línea 28: Transformamos el objeto en un String en formato JSON * Línea 30: Este código se ejecutará si por un motivo que no es un error de programación no se han podido obtener los datos y debemos notificar un mensaje al usuario, es cuando se produce una excepción de tipo ''BussinessException''. * Línea 32: Este código se ejecutará si ha habido un error en el programa, es cuando se produce una excepción de tipo ''Exception''. * Línea 40: Transformamos el JSON de entrada en un objeto de la clase ''SeguroMedico''. * Línea 41: Insertamos el nuevo objeto de la clase ''SeguroMedico'' en la base de datos mediante el DAO. * Línea 42: El objeto recién insertado lo transformamos otra vez en un String en formato JSON para retornarlo. * Línea 44: Este código se ejecutará si por un motivo que no es un error de programación no se han podido obtener los datos y debemos notificar un mensaje al usuario, es cuando se produce una excepción de tipo ''BussinessException''. * Línea 46: Este código se ejecutará si ha habido un error en el programa, es cuando se produce una excepción de tipo ''Exception''. Gracias a todo el trabajo que hemos hecho previamente de preparar todas las clases que íbamos a necesitar, el trabajo del controlador es realmente muy sencillo. Ahora pasemos a ver como retornar los datos. ==== Estado HTTP ==== Cada petición HTTP siempre retorna un valor indicado el éxito o fracaso de dicha petición. Si todo funciona correctamente se suele retornar un 200 OK, pero veamos la lista de estados que vamos a usar: ^ Número ^ Nombre ^ Significado ^ | 200 | OK | Todo ha funcionado correctamente y retornamos los datos | | 204 | No Content | Todo ha funcionado correctamente pero **NO** retornamos datos ya que no es necesario. Se usa en el DELETE en el que no se retorna nada | | 400 | Bad Request | Hay errores en los datos que ha enviado el usuario. Se retorna un objeto ''List'' | | 500 | Internal Server Error | Error del servidor. Se produce cuando hay un error en el código. Se retorna la excepción que se ha producido | ¿Como retornamos esos valores desde Java? Simplemente con el método ''httpServletResponse.setStatus(int sc);'' httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); Esto hace que debemos incluir como parámetro de los métodos otro objeto de la clase ''HttpServletResponse''. package es.cursohibernate.seguros.presentacion.controller; import es.cursohibernate.seguros.dominio.SeguroMedico; import es.cursohibernate.seguros.persistencia.BussinessException; import es.cursohibernate.seguros.persistencia.SeguroMedicoDAO; import es.cursohibernate.seguros.presentacion.json.JsonTransformer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @Autowired JsonTransformer jsonTransformer; @Autowired SeguroMedicoDAO seguroMedicoDAO; @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.GET, produces = "application/json") public void read(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @PathVariable("idSeguroMedico") int idSeguroMedico) { try { SeguroMedico seguroMedico = seguroMedicoDAO.get(idSeguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); } catch (BussinessException ex) { httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } @RequestMapping(value = "/SeguroMedico", method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public void insert(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @RequestBody String jsonEntrada) { try { SeguroMedico seguroMedico = (SeguroMedico) jsonTransformer.fromJson(jsonEntrada, SeguroMedico.class); seguroMedicoDAO.insert(seguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); } catch (BussinessException ex) { httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } * Línea 26: Declaramos un parámetro de la clase ''HttpServletResponse'' para poder tener acceso a todos los métodos relativos a la respuesta. * Línea 31: Como hemos obtenido de forma exitosa los datos retornamos un ''200 OK''. * Línea 34: Si se produce una ''BussinessException'' es que algun de los datos de entrada era erróneo y por lo tanto se retorna un ''400 Bad Request''. * Línea 37: Si se produce cualquier otro tipo de excepción es que ha sido un error del programa y retornamos un ''500 Internal Server Error'' * Línea 44: Declaramos un parámetro de la clase ''HttpServletResponse'' para poder tener acceso a todos los métodos relativos a la respuesta. * Línea 50: Como hemos obtenido de forma exitosa los datos retornamos un ''200 OK''. * Línea 53: Si se produce una ''BussinessException'' es que algun de los datos de entrada era erróneo y por lo tanto se retorna un ''400 Bad Request''. * Línea 56: Si se produce cualquier otro tipo de excepción es que ha sido un error del programa y retornamos un ''500 Internal Server Error'' Personalmente he tenido problemas en AngularJS retornando un ''200 OK'' cuando retorno un ''null''. Ese caso se da en: SeguroMedico seguroMedico = seguroMedicoDAO.get(idSeguroMedico); Si ''seguroMedico'' es ''null'' en vez de retornar un 200 y transformar el ''null'' a JSON es mas correcto en ese caso retornar un ''204 No Content''. ==== Los datos de respuesta ==== Como ya habíamos visto en el tema anterior retornar los datos es tan sencillo como llamar el método ''httpServletResponse.getWriter().println(String);'' Ahora el código quedará: package es.cursohibernate.seguros.presentacion.controller; import es.cursohibernate.seguros.dominio.SeguroMedico; import es.cursohibernate.seguros.persistencia.BussinessException; import es.cursohibernate.seguros.persistencia.SeguroMedicoDAO; import es.cursohibernate.seguros.presentacion.json.JsonTransformer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @Autowired JsonTransformer jsonTransformer; @Autowired SeguroMedicoDAO seguroMedicoDAO; @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.GET, produces = "application/json") public void read(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @PathVariable("idSeguroMedico") int idSeguroMedico) { try { SeguroMedico seguroMedico = seguroMedicoDAO.get(idSeguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.getWriter().println(jsonSalida); } catch (BussinessException ex) { httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } @RequestMapping(value = "/SeguroMedico", method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public void insert(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @RequestBody String jsonEntrada) { try { SeguroMedico seguroMedico = (SeguroMedico) jsonTransformer.fromJson(jsonEntrada, SeguroMedico.class); seguroMedicoDAO.insert(seguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.getWriter().println(jsonSalida); } catch (BussinessException ex) { httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } * Linea 32 y 52: Llamamos al método '' httpServletResponse.getWriter().println(String)'' para retornar el String en formato JSON con los datos. No vemos ahora como retornar cuando se produce una excepción ya que lo veremos en un apartado específico para cada uno. ==== Content-Type ==== Por último nos falta ver como retornar el ''Content-Type'' de los datos que se devuelven. Para ello simplemente debemos llamar al método ''httpServletResponse.setContentType(String)'' con el valor de la cabecera ''Content-Type''. package es.cursohibernate.seguros.presentacion.controller; import es.cursohibernate.seguros.dominio.SeguroMedico; import es.cursohibernate.seguros.persistencia.BussinessException; import es.cursohibernate.seguros.persistencia.SeguroMedicoDAO; import es.cursohibernate.seguros.presentacion.json.JsonTransformer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class SeguroMedicoController { @Autowired JsonTransformer jsonTransformer; @Autowired SeguroMedicoDAO seguroMedicoDAO; @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.GET, produces = "application/json") public void read(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @PathVariable("idSeguroMedico") int idSeguroMedico) { try { SeguroMedico seguroMedico = seguroMedicoDAO.get(idSeguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.setContentType("application/json; charset=UTF-8"); httpServletResponse.getWriter().println(jsonSalida); } catch (BussinessException ex) { httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } @RequestMapping(value = "/SeguroMedico", method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public void insert(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @RequestBody String jsonEntrada) { try { SeguroMedico seguroMedico = (SeguroMedico) jsonTransformer.fromJson(jsonEntrada, SeguroMedico.class); seguroMedicoDAO.insert(seguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.setContentType("application/json; charset=UTF-8"); httpServletResponse.getWriter().println(jsonSalida); } catch (BussinessException ex) { httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } * Líneas 32 y 53 : usando el método ''httpServletResponse.setContentType(String)'' indicamos que el formato de los datos que se retornan es JSON. Hay que fijarse que hemos puesto la línea justo antes de enviar los datos. ==== BussinessException ==== Veamos ahora como se retorna el resultado cuando se produce una ''BussinessException''. Como ya se explicó en el tema anterior , esta excepción contiene el método ''getBussinessMessages()'' que retorna una lista de objetos ''BussinessMessage''. Es justamente esta lista lo que queremos retornar al usuario. Como la página web que estas ahora mismo leyendo ya se está haciendo un poco larga , vamos a poner el código solo del tratamiento de la excepción ''BussinessException''. } catch (BussinessException ex) { List bussinessMessage=ex.getBussinessMessages(); String jsonSalida = jsonTransformer.toJson(bussinessMessage); httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); httpServletResponse.setContentType("application/json; charset=UTF-8"); try { httpServletResponse.getWriter().println(jsonSalida); } catch (IOException ex1) { Logger.getLogger(SeguroMedicoController.class.getName()).log(Level.SEVERE, null, ex1); } } * Linea 2: Obtenemos la lista de los mensajes que es lo que realmente queremos retornar. * Línea 3: Tranformamos la lista de mensaje en un String en formato JSON * Línea 5: Establecemos que el estado es ''400 Bad Request''. * Línea 6: Establecemos que es formato de los datos es JSON * Línea 8: Devolvemos los datos. Por desgracia el método ''httpServletResponse.getWriter().println(jsonSalida)'' lanza una excepción así que la tratamos en la línea 10 generando una línea de log ya que no podemos retornar la información al usuario ya que ha fallado justamente eso. ==== Exception ==== Veamos ahora como se retorna el resultado cuando se produce una ''Exception''. En este caso no vamos a retornar un JSON sino simplemente un texto plano con la traza de la excepción. La forma de retornar la excepción por la conexión HTTP es la siguiente: ex.printStackTrace(httpServletResponse.getWriter()); Como la página web que estas ahora mismo leyendo ya se está haciendo un poco larga , vamos a poner el código solo del tratamiento de la excepción ''Exception''. httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); httpServletResponse.setContentType("text/plain; charset=UTF-8"); try { ex.printStackTrace(httpServletResponse.getWriter()); } catch (IOException ex1) { Logger.getLogger(SeguroMedicoController.class.getName()).log(Level.SEVERE, null, ex1); } * Línea 1: Establecemos que el estado es ''500 Internal Server Error''. * Línea 2: Establecemos que el formato de los datos es texto plano * Línea 4: Aqui es donde se llama al método ''printStackTrace()'' que permite retornar los datos de la excepción. Por desgracia como el método puede lanzar una excepción la tratamos en la línea 6 generando una línea de log ya que no podemos retornar la información al usuario ya que ha fallado justamente eso. ===== El resto de métodos del controlador ===== Por fin hemos acabado de ver todo lo necesario para crearnos nuestros propios controladores, a continuación vamos a ver como son el resto de los métodos del controlador. Como ya hemos explicado paso a paso un par de métodos, en lo que queda solo vamos a resaltar las líneas mas importantes. ==== find ==== @RequestMapping(value = "/SeguroMedico", method = RequestMethod.GET, produces = "application/json") public void find(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { try { List segurosMedicos = seguroMedicoDAO.findAll(); String jsonSalida = jsonTransformer.toJson(segurosMedicos); httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.setContentType("application/json; charset=UTF-8"); httpServletResponse.getWriter().println(jsonSalida); } catch (BussinessException ex) { List bussinessMessage=ex.getBussinessMessages(); String jsonSalida = jsonTransformer.toJson(bussinessMessage); httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); httpServletResponse.setContentType("application/json; charset=UTF-8"); try { httpServletResponse.getWriter().println(jsonSalida); } catch (IOException ex1) { Logger.getLogger(SeguroMedicoController.class.getName()).log(Level.SEVERE, null, ex1); } } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } Por simplificar el DAO y el controlador no se ha hecho que se puedan filtrar los datos, por lo que siempre se muestran todos los seguros médicos. ==== update ==== @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json") public void update(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @RequestBody String jsonEntrada, @PathVariable("idSeguroMedico") int idSeguroMedico) { try { SeguroMedico seguroMedico = (SeguroMedico) jsonTransformer.fromJson(jsonEntrada, SeguroMedico.class); seguroMedicoDAO.update(idSeguroMedico,seguroMedico); String jsonSalida = jsonTransformer.toJson(seguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_OK); httpServletResponse.setContentType("application/json; charset=UTF-8"); httpServletResponse.getWriter().println(jsonSalida); } catch (BussinessException ex) { List bussinessMessage=ex.getBussinessMessages(); String jsonSalida = jsonTransformer.toJson(bussinessMessage); httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); httpServletResponse.setContentType("application/json; charset=UTF-8"); try { httpServletResponse.getWriter().println(jsonSalida); } catch (IOException ex1) { Logger.getLogger(SeguroMedicoController.class.getName()).log(Level.SEVERE, null, ex1); } } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); httpServletResponse.setContentType("text/plain; charset=UTF-8"); try { ex.printStackTrace(httpServletResponse.getWriter()); } catch (IOException ex1) { Logger.getLogger(SeguroMedicoController.class.getName()).log(Level.SEVERE, null, ex1); } } } ==== delete ==== @RequestMapping(value = "/SeguroMedico/{idSeguroMedico}", method = RequestMethod.DELETE, produces = "application/json") public void delete(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @PathVariable("idSeguroMedico") int idSeguroMedico) { try { seguroMedicoDAO.delete(idSeguroMedico); httpServletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); } catch (BussinessException ex) { List bussinessMessage=ex.getBussinessMessages(); String jsonSalida = jsonTransformer.toJson(bussinessMessage); httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); httpServletResponse.setContentType("application/json; charset=UTF-8"); try { httpServletResponse.getWriter().println(jsonSalida); } catch (IOException ex1) { Logger.getLogger(SeguroMedicoController.class.getName()).log(Level.SEVERE, null, ex1); } } catch (Exception ex) { httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } * Línea 6: Destacar que no se retorna nada y por lo tanto el estado HTTP es ''204 No Content'' ===== REST Client ===== Para probar nuestra API REST no es necesario que creemos una aplicación en JavaScript con AngularJS , antes de hacer todo eso podemos probarla desde el propio navegador. Firefox dispone de un gran plugin llamado [[https://addons.mozilla.org/es/firefox/addon/restclient/|REST Client]] {{:unidades:10_servidor:restclient.png?nolink|}} Este plugin nos permite cambiar todos los parámetros de una petición HTTP como: * Metodo * URL * Cuerpo * Cabeceras Y ver todo lo que retorna el servidor: * Cabeceras * Estado * Cuerpo de la respuesta. Por ello es una manera ideal de depurar nuestro API REST y lo recomiendo mientras estamos desarrollando. ===== Comentarios finales ===== Aunque ya se ha comentado varias veces, la forma de hacer este controlador no es la mas adecuada si usas Spring. El motivo de ello es que Spring dispone de muchas utilidades que nos pueden ayudar a reducir tanto código repetido. Por ejemplo el tratamiento de errores debería estar solo una única vez y no repetido tantas veces. Un tutorial al respecto está en [[http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc|Exception Handling in Spring MVC]] Otra posible forma de mejorar el código sin depender tanto de Spring sería usar el patrón //Template// al estilo de [[http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html|JdbcTemplate]] Aun así el motivo de haberlo hecho de esta forma es para que aprendas todo lo que es necesario hacer y ahora ya estés en disposición de ver los problemas y busques información sobre como mejorarlo. ===== Ejemplo ===== El ejemplo de esta unidad es exactamente lo que acabamos de contar pero en un nuevo proyecto llamado "seguros". Este ejemplo se encuentra en git en [[https://github.com/logongas/cursoangularjs/tree/master/seguros]] ===== Referencias ===== * [[http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html|16. Web MVC framework]] * [[http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc|Exception Handling in Spring MVC]]