Apuntes Grails. 201507. Jose A. Duran GRAILS – Apuntes - 3333445- 1 Creación proyecto – VISTAZO RAPIDO 1.2 Crear 'domain controllers' 1.3 Completar entidades 'domain controllers' 1.3.1 Indicar campos y relaciones 1.3.2 Condiciones para validación de los campos, constraints 1.4 Creación controlador y vistas básicas. 1.5 Revisar configuración BBDD / generación .war - 778999- 2 3 4 5 6 7 Controladores Vistas Librería etiquetas Servicios Consola Plantillas -10-10-10-10-10-10- 8 Configuración 8.1 config.groovy 8.1.1 log4j Utilizado para la generación de trazas. 8.1.2 GORM Gestor de persistencia de Grails. 8.2 DataSource.groovy 8.3 BuidConfig.groovy -11-11-12-12-12-13-14-15-16-17-18-18-18-18-18-19-20-21-21-22-23-23-24-24-25- 9 Modelo de datos GORM 9.1 Validaciones 9.1.1 Reglas disponibles 9.1.2 Mensajes de error 9.2 Creación de relaciones 9.2.1 Uno-A-Uno 9.2.2 Tiene-un 9.2.3 Uno-A-Muchos 9.2.4 Muchos-A-Muchos 9.3 Mapeo composiciones 9.4 Operaciones GORM 9.4.1 Actualizar 9.4.2 Eliminar 9.4.3 Bloqueos de datos 9.5 Consultas 9.5.1 Dynamic Finders 9.5.2 Criteria 9.5.2.1 Métodos Criteria 9.5.2.2 Nodos 9.5.2.3 Ordenar y otros 9.5.2.4 Proyecciones 9.5.3 Consultas con nombre 9.5.4 Consultas Hibernate o HQL 9.5.5 Eventos de Persistencia 9.5.6 Cache GORM/Hibernate -26-27-28-29-30-30-30-31- 10 Controladores 10.1 Ámbitos en el controlador 10.2 Método Render 10.3 Encadenar acciones 10.4 Interceptors 10.5 Procesar datos de entrada 10.5.1 Data Binding 10.5.2 Recibir ficheros Página 1 de 68 Apuntes Grails. 201507. Jose A. Duran -32- 10.5.3 Evitar doble post -32- 10.5.4 Objetos Command -33- 11 Servicios -34- 11.1 Creación de instancias -34- 11.2 Métodos transaccionales -36-36-36-37-37-37-38-38-38-41-41-41-42- 12 Vistas. GSP o Groovy Server Pages 12.1 Etiquetas GSP 12.1.1 Etiquetas para manejo variables 12.1.2 Etiquetas logicas e iteración 12.1.3 Etiquetas para filtrar colecciones 12.1.4 Etiquetas para enlazar páginas y recursos 12.1.5 Etiquetas para formularios 12.2 Etiquetas AJAX 12.3 Eventos Javascript 12.4 Generar XML o JSON en el servidor 12.4.1 Usar etiquetas como métodos 12.5 Crear TagLibs 12.6 Uso librerías de etiquetas JSP -43- 13 Layouts: Sitemesh -44- 13.1 Selección del Layout -45-46-46-46-47- 14 Definiendo estructura URL 14.1 URL Maping y etiqueta Link 14.2 Códigos de error 14.3 Capturar métodos HTTP 14.4 Patrones URL -48- 15 Web Flows -50- 16 Filtros -50- 16.1 Filtro XSS -52-52-53-53-53- 17 Pruebas (NO USADO) 17.1 Test unitarios. 17.2 El método mock 17.3 Test integración 17.4 Test funcional -54- 18 Internacionalización o i18n (solo cat) -54- 18.1 Funcionamiento -55- 19 Seguridad -55- 19.1 Tipos de ataques -56- 20. Desarrollo de Plugins -58- 20.1 Instalar el Plugin -59-59-61-63- 21 Servicios WEB SOAP-REST 21.1 SOAP servir 1 dato. 21.2 SOAP devolver objeto. 21.3 REST -65- 22 Consumir servicios SOAP -68- 23 GROOVY Página 2 de 68 Apuntes Grails. 201507. Jose A. Duran 1 Creación proyecto – VISTAZO RAPIDO $> grails create-app nombreProyecto 1.2 Crear 'domain controllers' $> grails create-domain-class usuario $> grails create-domain-class truco $> grails create-domain-class comentario 1.3 Completar entidades 'domain controllers' 1.3.1 Indicar campos y relaciones class Usuario { static hasMany = [trucos:Truco, comentarios:Comentario ] String nombre String email Date fechaAlta static constraints = { Usuario } } --------------------------------------------------class Truco { N static belongsTo = [usuario:Usuario] static hasMany = [comentarios:Comentario] List comentarios Date fecha String titulo String texto boolean denunciado static constraints = { } 1 --------------------------------------------------class Comentario { static belongsTo = [truco:Truco,autor:Usuario] String texto Date fecha static constraints = { } Comentario } Página 3 de 68 N 1 Truco N 1 Apuntes Grails. 201507. Jose A. Duran 1.3.2 Condiciones para validación de los campos, constraints class Usuario { String nombre String email Date fechaAlta static hasMany = [trucos:Truco, comentarios:Comentario ] static constraints = { nombre(size:3..50) email(email:true) } } --------------------------------------------------class Truco { List comentarios Date fecha String titulo String texto boolean denunciado static belongsTo = [usuario:Usuario] static hasMany=[comentarios:Comentario] static constraints = { titulo(size:10..1000) texto(maxSize:999999) } --------------------------------------------------class Comentario { static belongsTo = [truco:Truco, autor:Usuario] String autor String texto Date fecha static constraints = { texto(maxSize:999999) } } 1.4 Creación controlador y vistas básicas. Podemos pedir que se nos cree en automático los controladores y vistas necesarias $ grails generate-all comentario $ grails generate-all truco $ grails generate-all usuario O de forma manual. Nota. En versiones actuales de Grails para que el scaffolding funcione en los controladores, incluirlo (true) y tapar action 'index' para que liste. class UsuarioController { def scaffold = true // def index () {} } Página 4 de 68 Apuntes Grails. 201507. Jose A. Duran 1.5 Revisar configuración BBDD / generación .war Grails incluye un gestos de BD empotrado, HSQLDB, y un controlador de BD. Para acceder basta entrar en ../dbconsole y revisar los campos. Deben corresponder con el contenido en /conf/DataSource.groovy. Este fichero contiene varios entornos predefinidos, development, test, production. Para generar el fichero .war, usado para la instalación en server, basta con: $ grails war - genera un war de mi aplicación configurada para el entorno de desarrollo. $ grails prod war - genera el war configurado para producción, usando el entorno definido. Para que los datos en pruebas y desarrollo se guarden debemos eliminar el parámetro 'mem' (memoria) del fichero DataSource.groovy así se usaran ficheros en la BD. Previo. development { dataSource { dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', '' url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE" } } Actualizado. development { dataSource { dbCreate = "update" // one of 'create', 'create-drop', 'update', 'validate', '' url = "jdbc:h2:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE" } } Página 5 de 68 Apuntes Grails. 201507. Jose A. Duran En el ejemplo anterior podemos ver en el dbconsole los key creados, por defecto crea siempre un ID, y los externos FK_xxxxx y como estos apuntan a la tabla USUARIO campo ID Nota. Si intentamos entrar un registro que no cumple con las restricciones del campo, por ejemplo email, nos devuelve un aviso. Página 6 de 68 Apuntes Grails. 201507. Jose A. Duran 2 Controladores Son los responsables de recibir las solicitudes del usuario, aplicar la lógica de negocio y devolver la salida (vistas). Supuestos: a - Un controlador con el mismo nombre que una 'domain class' tiene predefinida a esta como su clase por defecto. b - El sistema agrega 'Controller' al nombre del controlador por defecto. Necesario para que grails sepa que es un controlador y responder con la URL preformada del tipo '.../controller/action'. C - Los controladores incluyen las acciones (action) relacionadas con el controlador. Listado, entrada de datos, borrar, etc ... class UsuarioController { def index () { ............. } def entrada () { ............. } } Creando un controlador de prueba y realizando un 'render' obtenemos una salida html directa del mismo. class PruebaController { def accion = { render "Controlador de pruebas..." } } 3 Vistas Debe mostrar al usuario la salida y acciones disponibles. Se usan páginas .gsp, una versión de .jsp simplificada permite intercalar HTML y tags estilo JSTL. Si en el anterior ejemplo comentamos la línea render. class PruebaController { def accion = { //render "Controlador de pruebas..." } } Y creamos una página gsp, ubicándola dentro de las vistas y con el nombre del action existente en el controlador, /views/prueba.gsp, el sistema la enlazara por defecto al abrir esa acción en ese controlador. <html> <head> <title> Prueba/accion </title> </head> <body> <g:set var="miVariable"> Hoy es: ${new Date()} </g:set> <p> Lo que tengo que decir es: <strong> ${miVariable} </strong> </body> </html> Página 7 de 68 </p> Apuntes Grails. 201507. Jose A. Duran Y obtendremos el HTML Como observamos se ha generado la variable miVariable, en la etiqueta <g:set con el valor de fecha actual. Después se muestra ese valor en el body con ${miVariable}. Las vistas permiten la ejecución de javascript. <html> <head> <title> Prueba/accion </title> <h3 id="header">Esto es JavaScript</h3> <script> document.body.appendChild(document.createTextNode('TACHAN !!!')); var h1 = document.getElementById('header'); // contiene la referencia al tag <h1> h1 = document.getElementsByTagName('h3')[0]; // accediendo al mismo elemento <h1> </script> </head> <body> <g:set var="miVariable"> Hoy es: ${new Date()} </g:set> <p> Lo que tengo que decir es: <strong> ${miVariable} </strong> </p> </body> </html> 4Librería etiquetas Grails usa una librería de etiquetas simplificada, se pueden añadir/crear nuevos ficheros, p.e. SimpleTagLib de etiquetas en la ubicación /grails-app/taglib/SimpleTagLib.groovy def mitag = {attrs, body -> out << attrs.estilo out << body() } Lo añadimos al gsp <g:mitag estilo="Estiloso" /> Y lo tenemos en tiempo ejecución. Página 8 de 68 Apuntes Grails. 201507. Jose A. Duran 5 Servicios Se aconseja no incluir lógica de negocio en la capa de control y usar para ello los servicios. Se alojan en la carpeta /services/UserService.groovy. El sistema le agrega Service al nombre automáticamente. Para activar el servicio basta con declarar la variable userService en el controlador, y tendremos disponibles la clase en cuestión. UserService.groovy import grails.transaction.Transactional @Transactional class UserService { def serviceMethod() {} public activarUsuario(String u){ //lógica del servicio return ("por aqui he pasado " + u) } } UserController.groovy def userService def activar = { render userService.activarUsuario(".YO.") } 6 Consola Grails cuenta con ejecución desde consola siempre con el formato $> grails [comando] [extension] Algunos comandos, clean console install-plugin list-plugin set-version shell install-dependence stats war -> -> -> -> -> -> -> elimina todas las clases compiladas consola propia de grails instala plugin lista plugin disponibles y estado adjudicamos versión terminal groovy descarga librería a la cache local grails install-dependency mysql:mysql-connector-java:5.1.5 -> estadística, no. Ficheros y líneas de código -> generamos fichero war 7 Plantillas Si instalamos las plantillas con $ > grails install-templates grails utilizará la copia local de estas para generar los componentes. Está integrado en /src/templates/* , manejan los artefactos, modelos de datos y aplicación JavaEE. En las plantillas se utilizan variables que se refieren al artefacto en cuestión. @artifact.package@class @artifact.name@ { def index() { } } Grails usa la copia local, así que modificando este fichero tendremos por defecto nuestra plantilla personalizada. Página 9 de 68 Apuntes Grails. 201507. Jose A. Duran 8 Configuración En grails-app/conf tenemos una serie de ficheros con los que configurar Grails. 8.1 config.groovy Configuración general, lo definido aquí estará disponible para toda la aplicación mediante grails.Aplication.config. Así definiendo la variable, com.imaginaworks.miParametro = "dato" será accesible con grailsApplication.config.com.imaginaworks.miParametro en toda la aplicación. Existen parámetros ya pre-definidos • grails.config.locations – Ubicaciones en las que queremos guardar otros archivos de configuración para que se fundan con el principal. • grails.enable.native2ascii – Si el valor es true, Grails usará native2ascii para convertir los archivos properties a unicode (el compilador y otras utilidades Java sólo puede manejar archivos que contengan caracteres Latin-1 y/o Unicode) • grails.views.default.codec – El formato por defecto para nuestras GSPs. Puede valer 'none' (por defecto),'html' o 'base64' • grails.views.gsp.encoding – Codificación por defecto de nuestros archivos GSP (por defecto es 'utf8') • grails.mime.file.extensions – Habilita el uso de la extensión en la url para fijar el content type de la respuesta (por ejemplo, si añadimos '.xml' al final de cualquier url Grails fijará automáticamente el content type a 'text/xml', ignorando la cabecera Accept del navegador) • grails.mime.types – Un Map con los tipos mime soportados en nuestra aplicación • grails.serverURL – La parte "fija" de nuestros enlaces cuando queramos que Grails genere rutas absolutas • grails.war.destFile – Ruta en la que grails war debería generar el archivo WAR de nuestra aplicación • grails.war.dependencies – Permite personalizar la lista de librerías a incluir en el archivo WAR. • grails.war.java5.dependencies – Permite personalizar la lista de librerías a incluir en el archivo WAR para el JDK1.5 y superiores. • grails.war.copyToWebApp – Permite controlar qué archivos de la carpeta webapp de nuestro proyecto se deben copiar al archivo WAR. • grails.war.resources – Permite personalizar el proceso de generación del WAR, especificando tareas previas en una closure mediante código Ant builder. 8.1.1 log4j Utilizado para la generación de trazas. 8.1.2 GORM Gestor de persistencia de Grails. 8.2 DataSource.groovy Parámetros de conexión a utilizar en el entorno. Indica la BD de producción, test y desarrollo. 8.3 BuidConfig.groovy Aspectos del proceso de compilación, rutas de trabajo y resolución de dependencias. Podemos pedir a Grails que acceda a los repositorios Maven para descargar los archivos JAR que necesite nuestra aplicación sin necesidad de utilizar Ivy o Maven explícitamente. Página 10 de 68 Apuntes Grails. 201507. Jose A. Duran 9 Modelo de datos GORM Compuesto por las 'domain class', clases de dominio o entidades. Se usa GORM como gestor de persistencia, controla el ciclo de vida de las entidades y proporciona métodos dinámicos. GORM esta construido, basándose en Groovy, sobre Hibernate. Hibernate se ocupa de trasladar a SQL las sentencias necesarias. Hibernate, trabaja en lotes o sesiones. Las sesiones de Hibernate reservan un espacio en memoria para los objetos que se están manipulando y las sentencias SQL. Cuando termina esta sesión se lanzan las consultas a la BD y hace permanentes los datos en memoria. La entidades están en grails-app/domain al crealas se genera el esqueleto básico: class [nombre] { static constraints = { } } Aquí informaremos de: a - Los campos necesarios. b - Métodos de búsqueda y modificación. c - Grails añade un campo ID y VERSION automáticamente a la tabla. Este campo ID hace que la sentencia def u = Usuario.get(3) funcione y recuperemos el registro de usuario con la ID = 3. 9.1 Validaciones Podemos restringir los valores a las propiedades(campos) de cada entidad(tabla) con el uso de la propiedad estática constraints (restricciones). En constraints de definen las reglas de validación de cada campo. class Persona { String nombre String apellidos String userName String userPass String email Integer edad static constraints = { nombre(blank:false,size:5..50) apellidos(blank:false,size:5..50) userName(blank:false,size:6..12) userPass(blank:false,size:6..12) email(email:true) edad(min:18) } } Antes de guardar GORM comprobará que se cumplen las restricciones especificadas. Con el método failOnError en save podemos capturar la excepción en caso de no pasar la validación try{ persona.save(failOnError:true) } catch(ValidationException x){ //La validación ha fallado. } Página 11 de 68 Apuntes Grails. 201507. Jose A. Duran 9.1.1 Reglas disponibles • blank – tendremos que poner bank:false si el campo no admite cadenas de texto vacías. • creditCard – obliga a que un campo String contenga valores de número de tarjeta de crédito válidos. • display – fijando su valor a false, evitamos que la propiedad se muestre al generar las vistas para Scaffolding. • email – obliga a que un campo de texto tenga una dirección de correo electrónico válida. • inList – obliga a que el valor del campo esté entre los de una lista cerrada. Por ejemplo: departamento(inList:['Sistemas','Desarrollo']) • matches – obliga a que el valor del campo satisfaga una determinada expresión regular: telefonoMovil(matches:"6[1-9]{8}") • max – garantiza que el valor de este campo no será mayor que el límite propuesto. • maxSize – obliga a que el valor tenga un tamaño menor que el indicado. • min – garantiza que el valor del campo no será menor que el límite propuesto. • minSize – obliga a que el valor tenga un tamaño mayor que el indicado. • notEqual – El valor de la propiedad no podrá ser igual al indicado. • nullable – Por defecto, Grails no permite valores nulos en las propiedades de una entidad. Para cambiar este comportamiento habrá que fijar nullable:true en las propiedades que deseemos. • range – Permite restringir los valores posibles para una propiedad. Por ejemplo: edad(range:18..64) • scale – fija el número de decimales para números de coma flotante. • size – permite restringir el tamaño mínimo y máximo a la vez (IMPORTANTE: no se puede utilizar junto con blank o nullable): password(size:6..12) • unique – garantiza que los valores de esta propiedad sean únicos. Ten encuenta que se hará una consulta a la base de datos en cada validación. • url – obliga a que el valor de la propiedad sea un String que represente una URL válida. • validator – permite definir validaciones especiales para casos no cubiertos por todas las anteriores: numeroPar( validator:{ return(it %2) == 0 }) También podemos incluir, fichero Config.groovy una restricción que será heredada por todas las entidades. grails.gorm.default.constraints = { '*'(nullable:true,blank:false) } 9.1.2 Mensajes de error Se utilizan los ficheros contenidos en grails-app/i18n. Podemos modificar desde aquí para personalizarlos modificando el contenido de los ficheros. 9.2 Creación de relaciones Para informar a Grails de las relaciones existentes en nuestra BD Página 12 de 68 Apuntes Grails. 201507. Jose A. Duran 9.2.1 Uno-A-Uno Basta con indicar a Grails en ambas tablas la tabla relacionada. class Coche { Conductor conductor } class Conductor { Coche coche } Con esto se generará la relación pero no se efectuaran actualizaciones en cascada, al eliminar el coche no se elimina el conductor. Para conseguirlo incluimos una propiedad estática de 'pertenece a', belongsTo de tal forma si eliminamos el coche se elimina el conductor. class Coche { Conductor conductor } class Conductor { static belongsTo = [coche:Coche] } Página 13 de 68 Apuntes Grails. 201507. Jose A. Duran 9.2.2 Tiene-un O cada registro tiene un ... o hasOne. Tan solo indicamos la relación en la tabla afectada. Cada Persona tiene una Direccion. Pero una Direccion no tiene porque tener Persona. class Persona { String nombre Direccion direccion } class Direccion { String calle String codPostal } Así en Dirección se creará una FK (clave foránea), una columna con el valor ID de la persona. Si vemos por dbconsola Página 14 de 68 Apuntes Grails. 201507. Jose A. Duran 9.2.3 Uno-A-Muchos Relación que un registro esta informado en múltiples registros de otra tabla. hasMany Una Persona puede tener múltiples Direcciones. class Persona { String nombre static hasMany = [direccion: Direccion] } class Direccion { String calle String codPostal } La propiedad hasMany genera un Map def persona = new Persona(nombre:'David A. Vise') .addToDireccion(new Direccion(calle:'Percebe Street', codPostal:'08080')) .save() Un objeto Persona y otro Direccion, cuando se guarde la persona, método save() de persona, se guardaran ambos Persona y Direccion . Nota. Hibernate no carga en memoria los objetos Dirección hasta que naveguemos en la esa colección. Así evita cargar las direcciones si estamos navegando por las personas. Si queremos que se carguen podemos activarlo al definir la clase Persona con la propiedad mapping. class Persona { static hasMany = [direccion:Direccion] static mapping = { direccion fetch:"join" } } Para poder funcionar bidireccionalmente, búsquedas, etc basta incluir referencias en ambas entidades. class Persona { String nombre static hasMany = [direccion:Direccion] } class Direccion { String calle String codPostal Persona persona } Recordemos que no funcionaria la actualización en cascada hasta que se modifique la referencia añadiendo belongsTo class Direccion { String calle String codPostal static belongsTo = [persona:Persona] } Página 15 de 68 Apuntes Grails. 201507. Jose A. Duran 9.2.4 Muchos-A-Muchos Relación que 'n' registros de la tabla A pueden estar relacionados con 'n' de la tabla B. Usamos hasMany en ambas tablas, además en el ejemplo incluimos la actualización en cascada de una de ellas. class Persona String static static } { nombre hasMany = [asociacion:Asociacion] belongsTo = Asociacion class Asociacion { String nombre static hasMany = [miembros:Persona] } GORM genera una tabla adicional donde guardar los pares de asociaciones entre ambas tablas Esta asociación está gobernada por la clase Persona pues la que dispone del belongsTo. Nota. Podemos observar que el nombre que ha creado Grails para la tabla intermedia corresponde a los nombre informados en la definición de las entidades. Página 16 de 68 Apuntes Grails. 201507. Jose A. Duran 9.3 Mapeo composiciones Para representar una característica que no es una entidad por si sola. Los campos de dirección extienden de Persona. Esto se llama composición. class Persona{ String nombre Direccion direccion } class Direccion extends Persona{ String calle String numero String portal String piso String letra String codigoPostal String municipio String provincia String pais } Como vemos GORM soporta herencia entre entidades. GORM crea un campo CLASS que identifica la entidad, Persona o Direccion, a la que pertenece el registro. Para obligar a GORM a crear tablas diferentes por cada clase debemos indicarlo con la propiedad mapping class Direccion extends Persona { String calle String numero String portal String piso String letra String codigoPostal String municipio String provincia String pais static mapping = { table = 'casa' } } Podemos usar mapping cuando usamos/asimilamos una tabla ya creada y queremos renombrar la tabla y/o los campos. class Direccion { String calle String String String String String String String String static numero portal piso letra codigoPostal municipio provincia pais mapping = { table 'casa' numero column:'PLAZA' piso column: 'PLANTA' } } Página 17 de 68 Apuntes Grails. 201507. Jose A. Duran 9.4 Operaciones GORM 9.4.1 Actualizar Todas las entidades cuentan con un método save. Sirve tanto para insertar un nuevo registro como para actualizar uno ya existente. def p = new Persona(nombre:'Paco Pérez') p.save() O p = Persona.findByName('Paco Pérez') p.nombre = 'Paco Sánchez' p.save() Recordemos que con Hibernate no se actualiza al instante la BD, si queremos que se actualice al instante usaremos el argumento flush. def p = new Persona(nombre:'Juan González') p.save(flush:true) 9.4.2 Eliminar Tenemos el método delete. Ídem uso con flush si queremos se actualice al momento. def p = Persona.get(1) p.delete() 9.4.3 Bloqueos de datos GORM permite varios métodos de bloqueos de datos, para evitar su manipulación mientras estos se hallan en memoria de la sesión de Hibernate. - Bloqueo Optimista, por defecto, cada instancia o registro, tiene un campo versión que se incrementa cada vez que hacemos save() sobre el registro. Antes de actualizar Hibernate comprueba si la versión coincide con la existente en memoria, sino coincide asume que alguien ha modificado la BD y realiza el rollback o regresión al momento previo, de la transacción y lanza una StaleObjectException. No se bloquea la tabla y nosotros somos los encargados de capturar el error y tratarlo. - Bloqueo Pesimista. Al cargar el registro Hibernate bloquea el registro, incluso de lectura, deben esperar la aplicación actual. Es un sistema seguro pero penaliza el rendimiento. Se invoca el método .lock de la clase. def p = Persona.lock(34) p.nombre = 'Paco Sánchez' p.save() 9.5 Consultas Para obtener instancias (consultas) de nuestras entidades las definimos y asignamos contenido. def gente //Obtener la lista completa de instancias: gente = Persona.list() //Con paginación: gente = Persona.list(offset:10,max:20) //Con ordenación: gente = Persona.list(sort:'nombre',order:'asc') //Cargar por ID: def p = Persona.get(32) gente = Persona.getAll(2,8,34) Página 18 de 68 Apuntes Grails. 201507. Jose A. Duran 9.5.1 Dynamic Finders Localizadores dinámicos aunque parecen métodos estáticos pero no existen en realidad, se generan a la primera invocación. Si tenemos: class Departamento { String nombre String codigo Date fechaCreacion Boolean esTecnico } Podemos invocar los métodos findBy (devuelve el primero) o findAllBy (devuelve todo) y combinaciones de operadores y propiedades. Ejemplos def d def l //Buscar un departamento por su nombre: d = Departamento.findByNombre('Sistemas') //Buscar todos los departamentos según un patrón empiezan por 01: l = Departamento.findAllByCodigoLike('01%') //Buscar departamentos antiguos l = Departamento.findAllByFechaCreacionLessThan(fech) /* === Métodos introducidos en Grails 1.2 ===*/ // Departamentos con esTecnico a true, por código. l = Departamento.findAllEsTecnicoByCodigoLike('01%') // Departamentos con esTecnico a falso, por fecha. l = Departamento.findAllNotEsTecnicoByFechaCreacion(..) La estructura del nombre de los métodos dinámicos es: [Clase].findBy[Propiedad][Comparador]([Valor]) Y pueden ser encadenados con las comparaciones AND y OR //Todos los departamentos cuyo código empiece por 01 //y su nombre empiece por S def lista = Departamento .findByCodigoLikeAndNombreLike('01%','S%') Los comparadores que existen son: • InList – el valor debe estar entre los de la lista que proporcionamos. • LessThan – el valor debe ser menor que el que proporcionamos. • LessThanEquals – el valor debe ser menor o igual. • GreaterThan – el valor debe ser mayor • GreaterThanEquals – el valor debe ser mayor o igual. • Like – Equivalente a un LIKE de SQL. • ILike – LIKE sin distinguir mayúsculas y minúsculas. • NotEqual – El valor debe ser distinto al que proporcionamos. • Between – El valor debe estar entre los dos que proporcionamos. • IsNotNull – El valor no debe ser nulo. • IsNull – El valor debe ser nulo. También pueden paginarse y ordenarse con un Map y los valores correspondientes de max, offset, sort y order. def deps = Departamento.findAllByNombre('Sistemas',[max:10, offset:5, sort:id, order:'desc']) Página 19 de 68 Apuntes Grails. 201507. Jose A. Duran 9.5.2 Criteria Para comparaciones complejas tenemos Criteria. def c = Departamento.createCriteria() def resulado = c.list{ between("fechaCreacion",now - 20,now - 1) and { like("código","01%") } maxResults(15) order("nombre","asc") } Si queremos paginación incluimos max y offset def c = Departamento.createCriteria() def resulado = c.list{max:10, offset:20 between("fechaCreacion",now - 20,now - 1) and { like("código","01%") } maxResults(15) order("nombre","asc") } Nota. Al realizar la paginación se ejecutan realmente 2 consultas, una que nos devuelve la ventana de resultados pedida y una segunda de la que se obtiene el getTotalcount() con el total de registros de la consulta sin filtrar por la paginación. Página 20 de 68 Apuntes Grails. 201507. Jose A. Duran 9.5.2.1 Métodos Criteria Los métodos aplicables en Criteria son: Method list Description Método por defecto. Devuelve todas las filas coincidentes. Devuelve un único resultado, p. e. un registro.Returns a unique result, i.e. just one get row. Ha sido formado para que solo devuelva una sola fila. No confundir con findBy, que devuelve el primero que coincide. scroll Devuelve un conjunto de resultados, scrollable Para agrupar por registros únicos en el caso que la consulta devuelva varias veces listDistinct el mismo registro. Los métodos se aplican al crear el resultado def resulado = c.list{max:10, offset:20 ....... es igual que def resulado = c{max:10, offset:20 ....... 9.5.2.2 Nodos Tenemos diferentes tipos de nodo (filtro). Node between eq Description Where the property value is between two distinct values Where a property equals a particular value. Example between("balance", 500, 1000) eq("branch", "London") A version of eq that supports an optional 3rd eq (case-insensitive) Map parameter to specify that the query be case-insensitive. eq("branch", "london", [ignoreCase: true]) eqProperty eqProperty("lastTx", "firstTx") gt gtProperty ge geProperty idEq Where one property must equal another Where a property is greater than a particular value Where a one property must be greater than another Where a property is greater than or equal to a particular value Where a one property must be greater than or equal to another Where an objects id equals the specified value gt("balance",1000) gtProperty("balance", "overdraft") ge("balance", 1000) geProperty("balance", "overdraft") idEq(1) ilike A case-insensitive 'like' expression ilike("holderFirstName", "Steph%") in Where a property is contained within the specified list of values. Can also be chained with the not method where a property is not contained within the specified list of values. Note: 'in' is a Groovy reserve word, so it must be escaped by quotes. 'in'("age",[18..65]) or not {'in'("age",[18..65])} isEmpty Where a collection property is empty isEmpty("transactions") isNotEmpty Where a collection property is not empty isNotEmpty("transactions") isNull Where a property is null isNull("holderGender") isNotNull Where a property is not null isNotNull("holderGender") lt Where a property is less than a particular value lt("balance", 1000) Página 21 de 68 Apuntes Grails. 201507. Jose A. Duran ltProperty le leProperty Where a one property must be less than another Where a property is less than or equal to a particular value Where a one property must be less than or equal to another ltProperty("balance", "overdraft") le("balance", 1000) leProperty("balance", "overdraft") like Equivalent to SQL like expression like("holderFirstName", "Steph%") ne Where a property does not equal a particular value ne("branch", "London") neProperty Where one property does not equal another neProperty("lastTx", "firstTx") order Order the results by a particular property order("holderLastName", "desc") rlike sizeEq sizeGt sizeGe sizeLt sizeLe sizeNe sqlRestriction Similar to like, but uses a regex. Only supported on Oracle and MySQL. Where a collection property's size equals a particular value Where a collection property's size is greater than a particular value Where a collection property's size is greater than or equal to a particular value Where a collection property's size is less than a particular value Where a collection property's size is less than or equal to a particular value Where a collection property's size is not equal to a particular value Use arbitrary SQL to modify the resultset rlike("holderFirstName", /Steph.+/) sizeEq("transactions", 10) sizeGt("transactions", 10) sizeGe("transactions", 10) sizeLt("transactions", 10) sizeLe("transactions", 10) sizeNe("transactions", 10) sqlRestriction "char_length(first_name) = 4" 9.5.2.3 Ordenar y otros Ademas tenemos acceso a diferentes opciones como máximo u orden, están disponibles con estas ordenes. Name order(String, String) firstResult(int) Description Example Specifies both the sort column (the first argument) and the sort order "age", "desc" order (either 'asc' or 'desc'). Specifies the offset for the results. A value of 0 will return all firstResult 20 records up to the maximum specified. maxResults(int) Specifies the maximum number of records to return. maxResults 10 cache(boolean) Indicates if the query should be cached (if the query cache is enabled). cache true Página 22 de 68 Apuntes Grails. 201507. Jose A. Duran 9.5.2.4 Proyecciones Criteria soporta proyecciones, una proyección se utiliza para cambiar el resultado, realizar una operación sobre el. Operaciones sobre el total. Este ejemplo def c = Departamento.createCriteria() def branchCount = c.get { projections { countDistinct "tipo_departamento" } } Name property distinct Description Returns the given property in the returned results Returns results using a single or collection of distinct property names Example property("firstName") distinct("fn") or distinct(['fn', 'ln']) avg Returns the average value of the given property avg("age") count Returns the count of the given property name count("branch") countDistinct Returns the count of the given property name for distinct rows countDistinct("branch") groupProperty Groups the results by the given property groupProperty("lastName") max Returns the maximum value of the given property max("age") min Returns the minimum value of the given property min("age") sum Returns the sum of the given property sum("balance") rowCount Returns count of the number of rows returned rowCount() 9.5.3 Consultas con nombre Podemos crear consultas y guardarlas, namedQueries, en la definición de la clase class Departamento { String nombre String código Date fechaCreacion Boolean esTecnico static namedQueries = { tecnicos { eq ('esTecnico', true) } } } Después podemos llamar a las consultas donde las necesitemos. def departamentosEsp = Departamento.tecnicos.list() def num = Departamento.tecnicos.count() def dEspPaginado = Departamento.tecnicos.list( max:100, offset:101 ) Página 23 de 68 Apuntes Grails. 201507. Jose A. Duran 9.5.4 Consultas Hibernate o HQL Por último podemos usar el lenguaje de consultas de Hibernate, HQL. def deps = Departamento.findAll("from Departamento as d where d.nombre like 'S%'") //Con parámetros: deps = Departamento.findAll("from Departamento as d where d.id=?",[7]) //Multilínea: deps = Departamento.findAll( "\ from Departamento as d \ where d.nombre like ? Or d.codigo = ?", ['S%','012']) //Con paginación: deps = Departamento.findAll( "from Departamento where id > ?", [7], [max:10,offset:25]) 9.5.5 Eventos de Persistencia Existe una serie de eventos disponibles. • beforeInsert – justo antes de insertar un objeto en nuestra base de datos. • beforeUpdate – justo antes de actualizar un registro. • beforeDelete – justo antes de eliminar un registro. • afterInsert – justo después de insertar un registro. • afterUpdate – justo después de actualizar un registro • afterDelete – justo después de eliminar el registro • onLoad – ejecutado cuando cargamos el objeto de la base de datos. Ejemplo agregando este código a Persona sabríamos la fecha de creación y de última modificación de los registros class Persona { String nombre Date fechaAlta Date fechaUltimaModificacion def beforeInsert = { fechaAlta = new Date() } def beforeUpdate = { fechaUltimaModificacion = new Date() } } Página 24 de 68 Apuntes Grails. 201507. Jose A. Duran 9.5.6 Cache GORM/Hibernate Hibernate, gestiona la traducción entre filas en tablas y objetos en memoria, para lo que se deben realizar los siguientes pasos: • Traducir la consulta del lenguaje Hibernate (HQL) al de la base de datos (SQL). • Lanzar la consulta contra la base de datos mediante JDBC. • Si la consulta devuelve resultados, para cada fila del ResultSet tiene que: – Crear una instancia de la clase correspondiente. – Poblar sus propiedades con los campos de la fila actual, haciendo las traducciones de tipo que sean necesarias. – Si existen relaciones con otras clases, cargar los objetos de la base de datos repitiendo el proceso actual para cada uno, y poblar la colección, o propiedad correspondiente. Para este trabajo se utilizan 3 tipos de cache. • Caché de nivel 1 – Consiste en que los objetos que se crean en lecturas desde la base de datos se recuerdan y comparten para la sesión actual. Si necesitamos realizar dos operaciones con el mismo objeto a lo largo de una sesión, la lectura desde la base de datos sólo se realizará la primera, y en la segunda se volverá a utilizar el mismo objeto. Este es un comportamiento automático de Hibernate y no es necesario activarlo. • Caché de nivel 2 – Consiste en recordar todos los objetos que se crean para las distintas sesiones activas. Esto quiere decir que si un usuario carga una entidad particular, ésta se mantendrá en memoria durante un tiempo para que lecturas posteriores (incluso desde otras sesiones) no necesiten ir de nuevo contra la base de datos. El beneficio que se obtiene en rendimiento es grande, pero conlleva un precio alto, pues hay que coordinar el acceso de las distintas sesiones a los objetos, en especial si desplegamos nuestra aplicación en un clúster de servidores. • Caché de consultas – Consiste en recordar los datos crudos devueltos en las consultas a la base de datos. Este nivel es poco útil en la mayoría de los casos, pero resulta muy beneficioso si realizamos consultas complejas, por ejemplo para informes con datos calculados o que impliquen a varias tablas. Estas caches se configuran en en el archivo grailsapp/conf/DataSource.groovy hibernate { cache.use_second_level_cache=true cache.use_query_cache=true cache.provider_class='org.hibernate.cache.EhCacheProvider' } Podemos definir la cache usada por una entidad en su definición. class Persona { static mapping = { //obligamos a leer cada vez de la B.D. cache: false } Página 25 de 68 Apuntes Grails. 201507. Jose A. Duran 10 Controladores Responsables de recibir ordenes de usuario y gestionar la lógica de negocio. Como aplicación web, deben interceptar las peticiones HTTP del navegador y generar la respuesta, que puede ser html, xml, json, o cualquier otro formato. Pueden generar la respuesta en el propio controlador, en una vista GSP. La convención utilizada en Grails es que un controlador es cualquier clase que se encuentre en la carpeta grails-app/controllers de nuestro proyecto y cuyo nombre termine por Controller. Para cada petición HTTP, Grails determina el controlador que debe invocar en función de las reglas fijadas en grails-app/conf/UrlMappings.groovy, crea una instancia de la clase elegida e invocará la acción correspondiente. La configuración por defecto consiste en definir URIs con este formato: /[controlador]/[acción]/[id] • [controlador] es la primera parte del nombre de nuestro controlador (eliminando el sufijo Controller) • [acción] es la action (closure) a ejecutar • [id] – el tercer fragmento de la uri estará disponible como params.id import grails.converters.* class PersonaController { //def scaffold=true //Tapado -- Para entrar datos def index = { redirect(action:"lista") } def lista = { def l = Persona.list() render l } def listaXML = { def l = Persona.list() render l as XML } def listaJSON = { def l = Persona.list() render l as JSON } } Página 26 de 68 Apuntes Grails. 201507. Jose A. Duran 10.1 Ámbitos en el controlador Objetos implícitos que pueden usarse para almacenar datos. • servletContext – contiene los datos del ámbito de aplicación. Disponible globalmente, desde cualquier controlador o acción. • session – permite asociar un estado a cada usuario. Los datos que guardemos en este ámbito serán visibles únicamente para el usuario, mientras su sesión esté activa. • request – Los valores son visibles durante la ejecución de la solicitud actual. • params – Parámetros de la petición actual, tanto los de la url como los del formulario si lo hubiera. Podemos añadir nuevos valores si fuera necesario, o modificar los existentes. • flash – Almacén temporal para atributos que necesitaremos durante la ejecución de la petición actual y la siguiente, son borrados después. La ventaja de este ámbito es que podemos guardar datos que serán visibles si devolvemos al usuario un código de redirección http (con lo que se produce otra petición HTTP, de manera que no nos sirve el objeto request), y luego se borrarán. Por ejemplo: import grails.converters.* class PersonaController { def verXml = { var p = Persona.findByNombre(params.nom) if(p){ render p as XML } else { flash.message = "no encontrado" redirect(action:index) } } } Al llamar desde la URI a la persona "Jose" (.../persona/verXml?nom=Jose) se nos muestra el XML de ese registro. Página 27 de 68 Apuntes Grails. 201507. Jose A. Duran 10.2 Método Render Si este método no se encuentra, Grails busca la vista predeterminada, grails-app/views/[controlador]/ [acción] Si devuelve un Map de modelos a mostrar los campos del Map están accesibles en la vista GSP como variables locales. Si no hay nada la GSP tiene acceso a las variables locales del controlador. El método render nos proporciona mas control a lo que enviamos al navegador. Acepta estos parámetros. • text – la respuesta que queremos enviar, en texto plano. • builder – un builder para generar la respuesta. • view – La vista que queremos procesar para generar la respuesta. • template – La plantilla que queremos procesar. • plugin – el plugin en el que buscar la plantilla, si no pertenece a nuestra aplicación. • bean – un objeto con los datos para generar la respuesta. • var – el nombre de la variable con la que accederemos al bean. Si no se proporciona el nombre por defecto será "it". • model – Un Map con el modelo para usar en la vista. • collection – Colección para procesar una plantilla con cada elemento. • contentType – el tipo mime de la respuesta. • encoding – El juego de caracteres para la respuesta. • converter – Un objeto grails.converters.* para generar la respuesta. En función de lo que se proporcione el funcionamiento será diferente. def ej1 = { //Enviar texto: render "respuesta simple" //especificar el tipo mime y codificación: } def ej2 = { //especificar el tipo mime y codificación: render( text:"<error>Hubo un error</error>", contentType:"text/xml", encoding:"UTF-8" ) } def ej3 = { //procesar una plantilla con un modelo: render(template:'listado', model:[lista:Persona.list()] ) } def ej4 = { //o con una colección: def p1=" a " def p2=" b " def p3=" c " render(template:'listado',collection:[p1,p2,p3] ) } def ej5 = { //o con un objeto: render(template:'persona',bean:Persona.get(3),var:'p') } def ej6 = { //procesar una vista con un modelo: render(view:'listado', model:[lista:Persona.list()] ) } def ej7 = { //o con el Controlador como modelo: render(view:'persona') } def ej8 = { //generar marcado usando un Builder (ojo con las llaves): Página 28 de 68 Apuntes Grails. 201507. Jose A. Duran render { div(id:'idDiv','Contenido del DIV') } render(contentType:'text/xml') { listado { Persona.list().each { persona( nombre:it.nombre, apellidos:it.apellidos ) } } } } def ej9 = { //generar JSON: render(contentType:'text/json') { persona(nombre:p.nombre,apellido:p.apellido) } } def ej10 = { //generar XML y JSON automáticamente: render Persona.list(params) as XML render Persona.get(params.id) as JSON } 10.3 Encadenar acciones Existen dos métodos para ejecutar una acción dentro de otra. • redirect – termina la ejecución de la solicitud actual enviando al cliente un código de redirección HTTP junto con una nueva URL que invocar. Atención en la segunda petición no tendremos acceso al ámbito request de la primera, de manera que los datos que queramos conservar deberán guardarse en el ámbito flash. //Redirigir a otra acción de este controlador: redirect(action:'otraAccion') //Redirigir a otra acción en otro controlador: redirect(controller:'access',action:'login') //Paso de parámetros: redirect(action:'otraAccion',params:[p1:'v1',...] //Pasar los parámetros de esta petición: redirect(action:'otra',params:params) //Redirigir a una URL: redirect(url:'http://www.imaginaworks.com') //Redirigir a una URI: redirect(uri:'/condiciones.uso.html') • chain – Si necesitamos que el modelo se mantenga entre la primera acción y la segunda podemos utilizar el método chain: def accion1 = { def m = ['uno':1] chain(action:accion2,model:m) } def accion2 = { ['dos':2] } //El modelo resultante será ['uno':1,'dos':2] Página 29 de 68 Apuntes Grails. 201507. Jose A. Duran 10.4 Interceptors Closures con nombre específico. Se ejecutan antes y/o después de llamar la acción solicitada por el navegador. class PersonaController { def beforeInterceptor = { println "procesando: ${actionUri}" } def afterInterceptor = {model -> println "completado: ${actionUri}" println "Modelo generado: ${model}" } Si beforeInterceptor = false entonces no se ejecuta la impresión de "procesando", en el caso contrario, espera por carga de trabajo, nos avisa que esta "procesando". Otro ejemplo class PersonaController { def beforeInterceptor = [ action:this.&auth, except:['login','register'] ] private auth(){ if(!session.user){ redirect(action:'login') return false; } } } Definimos un beforeInterceptor que captura todas las acciones, excepto login y register, e invoca el método auth (privado, para que no sea expuesto como una acción por el controlador) que comprobará si el usuario está identificado antes de continuar procesando la petición. 10.5 Procesar datos de entrada Existen múltiples funciones para procesar los datos de entrada. 10.5.1 Data Binding Todas las entidades tienen un constructor implícito y podemos usarlo. def save = { def p = new Persona(params) p.save() } Grails buscara en params los campos con el mismo nombre que en la entidad y si es necesario convierte entre ellos. Podemos usar este método para instancias. def save = { def p = Persona.get(params.id) p.properties = params p.save() } Y se invocaría /persona/save/2?nombre=Paco&apellidos=Gómez Si además controlamos errores el código sería def save = { def p = Persona.get(params.id) p.properties = params Página 30 de 68 Apuntes Grails. 201507. Jose A. Duran if(p.hasErrors()){ //procesar la lista de errores. }else{ //la operación tuvo éxito. } } 10.5.2 Recibir ficheros Grails utiliza org.springframework.web.multipart.MultipartHttpServletRequest. Una interfaz de Spring. Debemos incluir en nuestra GSP. <g:form action="save" method="post" enctype="multipart/form-data"> <input type="file" name="foto" /> <input type="submit" value="enviar" /> </g:form> Y con org.springframework.web.multipart.MultipartFile estará disponible en el controlador con la interfaz. Nos proporciona los métodos. • getBytes() - devuelve un array de bytes con el contenido del archivo. • getcontentType() - devuelve el tipo mime del archivo. • getInputStream() - devuelve un InputStream para leer el contenido del archivo. • getName() - Devuelve el nombre del campo file del formulario. • getOriginalFileName() - Devuelve el nombre original del archivo, tal como se llamaba en el ordenador del cliente. • getSize() - devuelve el tamaño del archivo en bytes. • isEmpty() - devuelve true si el archivo está vacío o no se recibió ninguno. • transferTo(File dest) – copia el contenido del archivo al archivo proporcionado. Los datos se almacenan en un archivo temporal que es borrado al procesar la petición. Si queremos guardar el fichero hay que usar transferTo para copiarlo. Podemos recibir la petición de dos formas, usando el Data Binding en la entidad. // La entidad class Persona_1 { String nombre String apellidos byte[] foto /* Observa que la propiedad foto es de tipo byte[], y que el nombre coincide con el nombre del campo file del formulario.*/ } --------------------------------------------------//El controlador class Persona_1Controller { def save = { def p = new Persona_1(params) } } //Podemos acceder directamente al archivo def save = { def archivo = request.getFile('foto') if(!archivo.empty){ archivo.transferTo(new File('....')) render "Archivo guardado" }else{ flash.message = "no se recibió archivo" Página 31 de 68 Apuntes Grails. 201507. Jose A. Duran redirect(action:'edit') } } 10.5.3 Evitar doble post Para evitar este problema. Forma 1, deshabilitar por Javascript el botón. <input type="submit" name="s" value="Enviar" onclick="this.disabled=true" /> Uso de un token, al enviar un formulario se almacena el valor del token asociado a la sesión de usuario y no se permite el procesamiento del mismo token hasta que termine la primera ejecución. Se habilita con useToken en la GSP. <g:form useToken="true" action="save" ...> La GSP genera el código y en el controlador podemos: a - Tratar el token explícitamente. def save = { withForm { //Formulario enviado correctamente. }.invalidRequest { //Formulario enviado por duplicado. }} b - Si no lo hacemos por defecto detecta la 2a invocación y la redirige a la vista de formulario. El token repetido es guardado en el ambito flash para poder tratarlo en la GSP <g:if test="${flash.invalidToken}"> Por favor, no pulse el botón enviar hasta que haya terminado. </g:if> 10.5.4 Objetos Command Los 'objetos comando' se 'pueblan' automáticamente con los datos que llegan en la solicitud. Se suelen definir en el controlador. def login = {LoginCommand cmd -> if(cmd.hasErrors()){ redirect(action:'loginForm') }else{ session.user = Persona.findByUserName(cmd.userName) redirect(controller:'home') }} class LoginCommand { String userName String userPass static constraints = { userName(blank:false,minSize:6) userPass(blank:false,minSize:6) }} Podemos definir restricciones (constraints) como si se tratase de una entidad. En la acción del controlador, tenemos que declarar el parámetro con el tipo del objeto command, y Grails se encargará de poblar las propiedades con los parámetros de la solicitud de forma automática. Igual que las entidades, los objetos command tienen una propiedad errors y un método hasErrors() que podemos utilizar para validar la entrada. Página 32 de 68 Apuntes Grails. 201507. Jose A. Duran 11 Servicios Debe implementar la lógica de negocio de la aplicación. • La solicitud llega a la acción correspondiente del controlador. • El controlador invoca al servicio encargado de la implementación del caso de uso. • En base al resultado de la invocación, decide cuál es la próxima vista a mostrar, y solicita a la capa de presentación que se la muestre al usuario. Los servicios están ubicados en grails-app/services Grails hará que todas las clases que declaren una variable con el mismo nombre que el servicio tengan una instancia disponible. Ejemplo, control de entrada de datos iniciales al entrar un usuario nuevo. En el Service creamos un método altaUsuario que recibe el Map con los parámetros de la petición y devuelve la instancia Persona. class UsuarioService { Persona altaUsuario(params){ def p = new Persona(params) //1. validar datos de entrada if(p.validate()){ //aplicar reglas a p p.save() } return p } } Como params trae los campos del formulario de alta desde la vista, crear la entidad Persona para esos parámetros, e invocar al método validate sobre ella para que se apliquen todas las reglas de validación (constraints). Si validate devuelve falso, habrá errores en la propiedad errors de p, y como no entramos en el bloque if, el servicio devuelve la entidad sin guardar en la base de datos y con todos los mensajes de error para que el controlador decida qué hacer. Si validate es verdadero, aplicaremos las reglas de negocio pertinentes y guardaremos Página 33 de 68 Apuntes Grails. 201507. Jose A. Duran En el controlador tendremos. class UsuarioController { def usuarioService // . . . otra acciones def save = { def p = usuarioService.altaUsuario(params) if(p.hasErrors()){ //tratar errores }else{ flash.message = 'Usuario registrado.' redirect(action:'show',id:p.id) } } } En el controlador declaramos una variable con el mismo nombre que la clase UsuarioService, salvo por la primera letra minúscula. Ésta es la convención a seguir para que Grails inyecte automáticamente una instancia del servicio en nuestro controlador, con lo que no debemos preocuparnos por su ciclo de vida. 11.1 Creación de instancias Por defecto todos los servicios son singleton (una instancia de la clase que se inyecta en todos los artefactos que declaren la variable correspondiente) esto tiene el inconveniente de la privacidad de la información, todos los controladores ven la misma instancia y el mismo valor. Para modificar esto basta con modificar la variable scope. Los valores posibles son: • prototype – cada vez que se inyecta el servicio en otro artefacto se crea una nueva instancia. • request – se crea una nueva instancia para cada solicitud HTTP. • flash – cada instancia estará accesible para la solicitud HTTP actual y para la siguiente. • flow – cuando declaramos el servicio en un web flow, se creará una instancia en cada flujo. • conversation – cuando declaramos el servicio en un web flow, la instancia será visible para el flujo actual y todos sus sub-flujos (conversación) • session – Se creará una instancia nueva del servicio para cada sesión de usuario. • singleton (valor por defecto) – sólo existe una instancia del servicio. class UnoPorUsuarioService { static scope = 'session' //. . . } 11.2 Métodos transaccionales Se pueden usar anotaciones en los métodos para marcarlos como transaccionales y personalizar el nivel de protección del método. import grails.transaction.Transactional import org.springframework.transaction.annotation.* class UsuariosService { @Transactional def registraUsuario(){ // Se ejecuta en una transacción. } @Transactional( readOnly =true) def buscarUsuarios() { // Se ejecuta en una transacción de sólo // lectura. } @Transactional(timeout=10000) def registraEnServicioRemoto(){ // Si no ha terminado en 10'' la transacción // habrá fallado. Página 34 de 68 Apuntes Grails. 201507. Jose A. Duran } @Transactional(isolation=Isolation.SERIALIZABLE) def modificaUsuario(){ // Se evitará la visibilidad de cualquier // dato generado hasta que la transacción // esté completa. } } - Por defecto la anotación @Transactional tiene los siguientes parámetros: • propagation - por defecto es PROPAGATION_REQUIRED. • isolation - por defecto es ISOLATION_DEFAULT. • readOnly - por defecto es false, es decir read/write. • timeout – tiempo en ms. Página 35 de 68 Apuntes Grails. 201507. Jose A. Duran 12 Vistas. GSP o Groovy Server Pages Son las responsables de mostrar al usuario el estado actual del modelo de datos y las acciones a escoger. Son similares a las páginas JSP. Las vistas tienen la extensión .gsp y se encuentran en grails-app/views podemos decidir la vista a mostrar con el método render o la vista por defecto. Por defecto se escoge la vista que coincide con el nombre de la action actual y está situada la carpeta con el mismo nombre que el controlador que contiene la action. Las vistas acceden a los datos de su 'modelo' y el controlador debe definir estos datos para mostrar. • Introduciendo código Groovy entre las marcas <% y %>. Igual que con las JSP, desaconsejado. • Utilizando expresiones Groovy con el formato ${expresion}, que serán reemplazadas por su valor antes de enviar la respuesta al cliente. Además de los valores definidos en el controlador, existe una serie de objetos implícitos a los que tenemos acceso desde cualquier página GSP: – application – Una referencia al contexto JavaEE (javax.servlet.ServletContext). – applicationContext – Una referencia al contexto Spring (org.springframework.context.ApplicationContext). – flash – El ámbito flash (ver capítulo sobre Controladores) – grailsApplication – Una referencia al objeto GrailsApplication, acceso a otros artefactos, entre otras cosas. – out – Una referencia al objeto Writer sobre el que se volcará el resultado de procesar la GSP. – params – El Map con los parámetros de la solicitud (ver capítulo sobre Controladores) – request – Una referencia al objeto HttpServletRequest. – response – Una referencia al objeto HttpServlet Response – session – Una referencia al objeto HttpSession. – webRequest – Una referencia al objeto GrailsWebRequest, que podemos utilizar para obtener datos sobre la petición actual no disponibles en el objeto request, como el nombre del controlador o la acción actual. • Utilizando librerías de etiquetas. Como veremos a continuación, podemos extender la funcionalidad de las GSP definiendo nuestras propias etiquetas con un sistema mucho más simple e intuitivo que las librerías de etiquetas JSP. 12.1 Etiquetas GSP Podemos definir nuestras etiquetas o utilizar las que incorpora Grails. Sintaxis: <[ns]:[tag] atrib1="val1" atrib2="atrib2" ...> <!-- cuerpo --> </[ns]:[tag]> Donde • [ns] es el espacio de nombres (Namespace) de la etiqueta. Las etiquetas estándar de Grails, así como las nuestras si no indicamos lo contrario, estan en el espacio de nombres "g", p. e.: <g:link action="login">Iniciar sesión</g:/link> • [tag] es el nombre de la etiqueta. 12.1.1 Etiquetas para manejo variables • set – Para definir variables en la página GSP, y el ámbito en el que deben almacenarse. – var – el nombre de la variable a crear o modificar. – value – el valor a almacenar en la variable. – scope – el ámbito de la variable (page, request, flash, session o application). <g:set var="hoy" value="${new Date()}" /> Página 36 de 68 Apuntes Grails. 201507. Jose A. Duran 12.1.2 Etiquetas logicas e iteración • if, else y elseif: Permiten la inclusión opcional de código en las vistas: <g:set var="roles" value="editor" /> <g:if test="${roles.contains('admin')}"> Hola, administrador. </g:if> <g:elseif test="${roles.contains('editor')}"> Hola, editor. </g:elseif> <g:else> Hola, usuario. </g:else> • each – Permite iterar sobre los elementos de una colección: <g:each in="${['Paco','Juan','Pepe']}" var="nom"> <p>Hola, ${nom}</p> </g:each> • while – Permite iterar mientras se cumpla la condición indicada: <g:set var="i" value="${1}" /> <g:while test="${i < 10}"> <p>Número actual: ${i++}</p> </g:while> 12.1.3 Etiquetas para filtrar colecciones • findAll – Busca entre los elementos de una colección aquellos que cumplen una condición, expresada en Groovy: Números pares de 1 a 8: <g:set var="arrai" value="${[1,2,3,4,5,6,7,8]}" /> <p> numeros: ${arrai}</p> <g:findAll in="${arrai}" expr="it % 2 == 0"> <p>Nu: ${it}</p> </g:findAll> • grep – Busca entre los elementos de una colección de dos formas distintas: a - Buscar objetos de una clase particular: <g:grep in="${personas}" filter="Conductor.class"> <p>${it.nombre} ${it.apellidos}</p> </g:grep> b - Buscar cadenas que cumplan una expresión regular: <g:grep in="${personas.nombre}" filter="~/Jose.*?/" > <p>${it}</p> </g:grep> 12.1.4 Etiquetas para enlazar páginas y recursos • link – Permite crear enlaces (etiquetas 'a' de HTML) de forma lógica, que funcionarán incluso si cambiamos la configuración de URLs de nuestra aplicación. <g:link action="show" id="1">Persona 1</g:link> <g:link controller="persona" action="list"> Listado de personas </g:link> <g:link controller="persona" action="list" params="[sort:'nombre',order:'asc']"> Listado alfabético de Personas </g:link> • createLink – Genera una ruta para utilizar directamente en el atributo href de un Página 37 de 68 Apuntes Grails. 201507. Jose A. Duran enlace html, o en javascript, etc: <g:createLink controller="persona" action="show" id="5" /> <!-- Genera: /persona/show/5 --> • resource – Crea enlaces a recursos estáticos como hojas de estilo o scripts javascript: <g:resource dir="css" file="main.css" /> <!-- Genera: /css/main.css --> • include – Permite empotrar la respuesta de otra acción o vista en la respuesta actual: <g:include action="status" id="${user.id}" /> 12.1.5 Etiquetas para formularios Existen múltiples etiquetas Grails para la creación de formularios y sus campos. • form y uploadForm – Ambas crean una etiqueta form. Diferencia, la segunda añade enctype="multipart/form-data" para permitir el envío de archivos al servidor: <g:form method=”POST” name="f" url="[controller:'persona',action:'save']"> <!-- Formulario --> </g:form> <!-En Grails 1.2+ podemos utilizar métodos distintos a GET y POST en la etiqueta form. Por ejemplo, PUT o DELETE permiten invocar servicios REST desde formularios HTML. --> • textField – Para crear campos de texto. • checkBox – Para crear campos check. • radio – para crear radio buttons. • radioGroup – para agrupar varios botones radio. • hiddenField – para crear campos ocultos. • select – para crear listas desplegables. • actionSubmit – Genera un botón submit para el formulario. Podemos usar varios botones en el mismo formulario que envíen los datos a diferentes urls: <g:actionSubmit value="Enviar" action="save" /> <g:actionSubmit value="Cancelar" action="index" /> En la mayoría de los casos, los atributos que pongamos en una etiqueta y que no sean reconocidos por ella se incluirán sin modificar en el HTML generado, así podemos hacer: <g:actionSubmit value="Eliminar" action="delete" onclick="return confirm('Estás seguro??')" /> y la etiqueta actionSubmit incluirá el atributo onclick en el html generado tal cual lo hemos definido nosotros. 12.2 Etiquetas AJAX Facilitan la generación de código Javascript. a - Debemos elegir la librería a utilizar, Jquery, Dojo, Prototype ... b - Indicarlo en una etiqueta en la cabecera de la página <head> <g:javascript library="prototype" /> </head> c - Disponemos de distintas etiquetas para las situaciones más habituales: • remoteLink – Genera un enlace que realiza una invocación AJAX al servidor y muestra el contenido en el Página 38 de 68 Apuntes Grails. 201507. Jose A. Duran contenedor con el id proporcionado en el atributo "update": <div id="listado"></div> <g:remoteLink action="list" controller="persona" update="listado"> Ver Listado Completo </g:remoteLink> • formRemote – Crea una etiqueta form de HTML con la lógica necesaria para hacer un submit AJAX de los datos, en lugar de un POST normal: <g:formRemote name="f" url="[action:actualizar]" update="resultado"> . . . </g:formRemote> <div id="resultado"></div> • submitToRemote – crea un botón que envía el formulario actual mediante AJAX. Especialmente útil en casos en que no podemos utilizar formRemote. <g:form action="actualizar"> ... <g:submitToRemote update="resultado" /> </g:form> <div id="resultado"></div> • remoteField – Crea un campo de texto que envía su valor al servidor cada vez que se cambia. • remoteFunction – Genera una función javascript que podemos asociar al evento que queramos de cualquier objeto HTML: <input type="button" name="b" value="Ver Usuario" onclick="$ {remoteFunction(action:'show')}" /> Página 39 de 68 Apuntes Grails. 201507. Jose A. Duran 12.3 Eventos Javascript Asociar código a ciertos eventos. Los eventos que podemos capturar son: • onSuccess – la llamada se realizó con éxito. • onFailure – la llamada al servidor falló. • on_[CODIGO-ERROR-HTTP] – la llamada al servidor devolvió un error • onUninitialized – la librería AJAX falló al inicializarse. • onLoading – la llamada se ha realizado y se está recibiendo la respuesta. • onLoaded – la respuesta se ha recibido completamente. • onComplete – la respuesta se ha recibido y se ha completado su procesamiento. <g:javascript> function trabajando(){ alert("vamos a conectar con el servidor..."); } function completado(){ alert("Proceso completado"); } function noEncontrado(){ alert("La ruta estaba mal...")} function error(){ alert("Ha ocurrido un error en el servidor"); } </g:javascript> <g:remoteLink action="show" id="3" update="resultado" onLoading="trabajando()" onComplete="completado()" on404="noEncontrado()" on500="error()"> Mostrar la persona con id 3 </g:remoteLink> En cada petición AJAX hay un objeto XmlHttpRequest que almacena los datos del servidor y el código HTTP y otra información del proceso. Si necesitamos acceder al objeto XmlHttpRequest podemos utilizar el parámetro implícito e en las funciones Javascript: g:javascript> function evento(e){ alert(e); } </g:javascript> <g:remoteLink action="test" onSuccess="evento(e)"> Probar </g:remoteLink> Página 40 de 68 Apuntes Grails. 201507. Jose A. Duran 12.4 Generar XML o JSON en el servidor Pese a que con los converters de Grails es fácil generar XML o JSON. def listaJSON = { def l = Persona.list() render l as JSON } Para invocar esta acción desde AJAX deberíamos escribir el Javascript para procesar el código <g:javascript> function actualizar(e){ //evaluamos el JSON: var p = eval("(" + e.responseText + ")"); $(personaActiva).innerHTML = p.nombre } </g:javascript> <div id="personaActiva"></div> <g:remoteLink action="show" id="3" onSuccess="actualizar(e)"> Mostrar persona con ID 3 </g:remoteLink> 12.4 Usar etiquetas como métodos Las etiquetas GSP pueden invocarse como funciones. Nos permite usarlas desde • atributos en otras etiquetas: <img src="${resource(dir:'images',file:'logo.png')}" /> • Artefactos no GSP, como Controladores: def contenido = g.include(action:'status') 12.5 Crear TagLibs Es sencillo crear una librería de etiquetas. En Grails una librería es una clase Groovy cuyo nombre acaba en TagLib y se aloja en grails-app/taglib/xxxTagLib.groovy Ejemplo una libreria que envuelve lo que le pongamos en un borde de color. class IwTagLib { static namespace = "iw" def conBorde = { atr,body -> out << "<div style='background:${atr.color}'>" out << "${atr.color}" out << body() out << "</div>" } Y al llamarlo en una GSP <iw:conBorde color="#555">Hola a todos</iw:conBorde> Cuando se procese el navegador recibirá <div style='background:#555'>#f555Hola a todos</div> Si no se define la variable namespace en la librería Grails adjudica "g" por defecto. Página 41 de 68 Apuntes Grails. 201507. Jose A. Duran 12.6 Uso librerías de etiquetas JSP Grails soporta el uso de librerías de etiquetas JSP mediante la directiva taglib <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> Con la ventaja adicional de que podemos usarlas al estilo tradicional: <fmt:formatNumber value="${libro.precio}" pattern="0.00" /> y también como llamadas a métodos: <input type="text" name="precio" value="${fmt.formatNumber(value:10,pattern:'.0')}"/> Página 42 de 68 Apuntes Grails. 201507. Jose A. Duran 13 Layouts: Sitemesh Sitemesh es un framework para la creación de interfaces web con un formato unificado. Utiliza el patrón Decorator, partiendo de un layout inicial de la página incorporando modificaciones a partir de cada vista particular. Con este sistema toda estructura genérica se define en un único archivo independiente de nuestras GSP por lo que simplifica la gestión de la aplicación. El archivo en el que se define la estructura de página debe ubicarse en grails-app/views/layouts y puede contener los mismos elementos de una página GSP. Un ejemplo <html> <head> <title> <g:layoutTitle default="Sin Título"/> </title> <g:layoutHead /> </head> <body onload="${pageProperty(name:'body.onload')}"> <div id="top"><!--contenido fijo--></div> <div id="main"> <g:layoutBody /> </div> <div id="bottom"> <!-- contenido fijo --> </div> </body> </html> Página 43 de 68 Apuntes Grails. 201507. Jose A. Duran Interesante, las etiquetas. • layoutTitle – inserta el título de la GSP procesada. • layoutHead – inserta el contenido de la cabecera de la GSP procesada. • layoutBody – inserta el body de la GSP procesada. • pageProperty – permite obtener elementos individuales de la GSP procesada, como por ejemplo atributos de una etiqueta concreta. 13.1 Selección del Layout El layout a aplicar para una acción será el que se encuentre en grails-app/views/layouts/[controlador]/[accion].gsp Si no se encuentra, se buscará grails-app/views/layouts/[controlador].gsp Para modificar este comportamiento podemos especificar el layout a aplicar en el controlador: class PersonaController { //Se buscará en //grails-app/views/layouts/custom/general.gsp static layout = 'custom/general' } o bien en la GSP: <html> <head> <title> … </title> <meta name="layout" content="main"></meta> … La ventaja del sistema de layouts es que podemos elegir la estructura de nuestra página en tiempo de ejecución, creando por ejemplo una versión simplificada para navegadores sin soporte de Javascript, o para mostrar a usuarios que navegan desde un teléfono móvil sin necesidad de modificar nuestras vistas GSP. Página 44 de 68 Apuntes Grails. 201507. Jose A. Duran 14 Definiendo estructura URL Grails utiliza por defecto un convenio para las URL's /controlador/acción/id pero podemos adaptarlo o a las necesidades de nuestro proyecto. Basta modificar el archivo. grails-app/conf/UrlMappings.groovy. Ejemplo 1 class UrlMappings { static mappings = { "/articulos" { controller="articulo" action="list" } } } Estamos indicando que cuando el usuario invocase la URL /articulos, debe ejecutarse la acción list del controlador articulo. Ejemplo 2 class UrlMappings { static mappings = { "/articulos/$y?/$m?/$d?/$id?" { controller="articulo" action="show" constraints { y(matches:/d{4}/) m(matches:/d{2}/) d(matches:/d{2}/) } } } } El símbolo ? Es opcional e indica que se resolverá el mapeo aunque no se le asigne valor. En el constraints nos aseguramos que los valores sean correctos, forma esperada. En el controlador accederíamos: class ArticuloController = { def show = { def año = params.y def mes = params.m def dia = params.d def id = params.id … } } Podemos usar las variables reservadas controller, action e id para personalizar el formato inicial de URLs: Ejemplo 3 class UrlMappings { static mappings = { "/$controller/$id/$action"() } } Página 45 de 68 Apuntes Grails. 201507. Jose A. Duran Usar otras variable accesibles desde el controlador Map params. Ejemplo 4 class UrlMappings { static mappings = { "/articulos/$y/$m" { order = "fecha" sort = "asc" controller = "articulo" action = "list" } } } 14.1 URL Maping y etiqueta Link La etiqueta link genera los enlaces correctamente aunque cambiemos la configuración en UrlMapping.groovy. <g:link controller:'articulo' action:'show' params:[y:'2015',m:'07']> Ver artículos </g:link> antes <g:link controller='persona' action='show' id='1'> Ver persona 1 </g:link> El enlace generado será: <a href="/articulos/2009/12">Ver artículos</a> 14.2 Códigos de error Podemos mapear los códigos de error para que apunten a una página personalizada. Así modificamos UrlMapping.groovy indicando la nueva dirección. class UrlMappings { static mappings = { "500"(view:"/error/serverError") "404"(view:"/error/notFound") } } 14.3 Capturar métodos HTTP Utilizar los métodos HTTP para realizar distintas acciones. class UrlMappings { static mappings = { "/persona/$id" { action = [ GET:'show', PUT:'update', DELETE:'delete', POST:'save'] } } } Página 46 de 68 Apuntes Grails. 201507. Jose A. Duran 14.4 Patrones URL Posibilidad de asociar un nombre a cada regla URL y creación reglas personalizadas. Ejemplo: name postsByTag: "/blog/$seccion/$tag?" { controller = "blogPost" action = "show" } Y lo llamamos desde las GSP con la forma <link:postsByTag seccion='software' tag='groovy'> Posts sobre Groovy </link:postsByTag> Así le indicamos a Grails que debe crear la URL siguiendo el formato con el mismo nombre postsByTag que hemos establecido en UrlMapping.groovy Página 47 de 68 Apuntes Grails. 201507. Jose A. Duran 15 Web Flows O Flujos WEB, conversación o flujo de comunicación entre el navegador y el servidor/aplicación. Se extiende a lo largo de varias peticiones manteniendo un estado entre ellas. Podemos establecer un estado inicial y final, gestionando la transacción entre estados. Para hacer uso de los Web Flows debemos usar el plugin webflow. Por definición cualquier acción que acabe en Flow define un flujo web. class ProfileController { def index = {//Fíjate: no usamos el 'Flow' al final. redirect(action:'define') } def defineFlow = { personal { on('submit'){PersonalCmd cmd -> flow.personal = cmd if(flow.personal.validate()) return success() else return error() }.to 'formacion' on('cancel').to 'cancel' } formacion { on('submit'){FormacionCmd cmd -> flow.formacion = cmd if(flow.formacion.validate()) return success() else return error() }.to 'experiencia' on('back').to 'formacion' on('cancel').to 'cancel' } experiencia { on('submit'){ExperienciaCmd cmd -> flow.experiencia = cmd if(flow.experiencia.validate()) return success() else return error() }.to 'objetivos' on('back').to 'experiencia' on('cancel').to 'cancel' } objetivos { on('submit'){ObjetivosCmd cmd -> flow.objetivos = cmd if(flow.objetivos.validate()) return success() else return error() }.to 'saveProfile' on('back').to 'experiencia' on('cancel').to 'cancel' } saveProfile { /* Generar el perfil con los objetos cmd. */ } cancel { redirect(controller:'home') } } } • La acción por defecto, index, redirige al navegador hacia el flujo. OJO el “nombre” oficial de la acción no incluye el sufijo “Flow”. Página 48 de 68 Apuntes Grails. 201507. Jose A. Duran • La acción que define el flujo se llama defineFlow, y en ella se establecen todos los pasos posibles y la forma de pasar de uno a otro. • Las vistas asociadas a cada paso se buscarán en grailsapp/views/profile/define/. Lo primero que veremos será el estado inicial, definido en la vista grailsapp/views/profile/define/personal.gsp. Esta página deberá contener el primer formulario, con los botones necesarios para lanzar los eventos “submit” y “cancel”: <g:form action='define'> . . . <g:submitButton name='submit' value='Siguiente'></g:submitButton> <g:submitButton name='cancel' value='Cancelar'></g:submitButton> </g:form> En cada paso del flujo podemos utilizar Command Objects (13.5.4) para la validación de los datos de entrada. • Además de los ámbitos que ya conocemos (session, request, flash, etc), disponemos del ámbito flow, en el que podemos guardar los datos relacionados con este proceso y serán destruidos cuando termine la ejecución del último paso. • Cuando realizamos la validación de los datos de entrada, utilizamos los métodos estándar success() y error(), que permiten seguir adelante o volver a mostrar el formulario para que el usuario corrija los errores, respectivamente. Página 49 de 68 Apuntes Grails. 201507. Jose A. Duran 16 Filtros Es posible interceptar todas, o las que deseemos, las peticiones URL de nuestra aplicación. La convención sobre filtros indica que es cualquier clase que se encuentre en la carpeta grails-app/conf cuyo nombre termine en Filters, y que contenga un bloque de código filters podrá interceptar las URLs de la aplicación. class MisFiltrosFilters { def filters = { all(controller:'*', action:'*') { before = { if(!session.usr && !actionName.equals('login')){ redirect(action:'login') return false; } } after = { Map model -> } afterView = { Exception e -> }}}} En el ejemplo, revisamos que exista sesión de usuario, si no es asi enviamos a 'login'. Los filtros tan solo permiten dos acciones. • Redirigir al usuario a otra URL mediante el método redirect. • Generar una respuesta mediante el método render. Si podemos restringir la aplicación del filtro. Además tenemos varios puntos de ejecución del filtro. • before – El código se ejecutará antes de invocar a la acción. Si devuelve false, la acción no se llegará a ejecutar nunca. • after – Se ejecuta tras la acción. Podemos recibir el modelo generado mediante un parámetro en la closure. • afterView – Se ejecuta después de renderizar la vista. También podemos acceder a todos los entornos disponibles. Request, response, session, servletContext, flash, params, actionName, controllerName, grailsApplication y applicationContext. 16.1 Filtro XSS Por razones de seguridad si queremos eliminar el marcado XSS o Cross Site Scripting. Creamos un método MarkupUtils que elimina el marcado XML, en la carpeta src/groovy class MarkupUtils { static String removeMarkup(String original) { def regex = "</?\\w+((\\s+\\w+(\\s*=\\s*(?:\".*?\"|'.*?'|[^\'\">\\s]+))?)+\\s*|\\s*)/?>" def matcher = original =~ regex; def result = matcher.replaceAll(""); return result; } } Y ahora creamos un nuevo filtro que llama al método MarkupUtils class IWFilters{ def filters = { xss(controller:'*', action:'*') { before = { def debug = false if(debug) println request.method if(true || request.method == 'GET'){ params.each { entry -> if(entry.value instanceof String){ params[entry.key] = MarkupUtils.removeMarkup(entry.value) Página 50 de 68 Apuntes Grails. 201507. Jose A. Duran }}}}}}} En cada petición este filtro elimina de los parámetros cualquier código de marcado. Llegarán limpios a nuestro constructor y no tendremos que mezclar tareas de seguridad con nuestra lógica de negocio. Map params es mutable y podemos alterar los parámetros de entrada, ya sea por motivos de seguridad como en este caso, o simplemente para añadir valores por defecto. Página 51 de 68 Apuntes Grails. 201507. Jose A. Duran 17 Pruebas (NO USADO) Durante el proceso de desarrollo, podemos probar el estado de nuestra aplicación mediante el comando: grails test-app. Realizará las pruebas unitarias y de integración. Devolviendo un informe al finalizar el proceso. Ejemplo, contamos con la clase Persona class Persona { static hasMany = [amigos:Persona] String nombre String apellidos String email Date fechaNacimiento static constraints = { nombre(size:3..50,blank:false) apellidos(size:3..100,blank:true) fechaNacimiento() email(email:true) } } Se define una relación uno a muchos de la clase Persona consigo misma, una persona puede tener varios amigos, y ser amigo de varias otras. Crearemos un servicio con un método que garantice que cuando existe una relación de amistad, ésta sea mutua: import grails.transaction.Transactional @Transactional class GenteService { boolean transactional = false // Se asegura de que ambas personas se tengan como amigos. def addToFriends(Persona p1, Persona p2){ if(!p1.amigos.contains(p2)){ p1.amigos.add(p2) } if(!p2.amigos.contains(p1)){ p2.amigos.add(p1) } } } 17.1 Test unitarios. Para probar que un método se comporta como debe sin tener en cuenta el entorno. Creamos un nuevo test unitario que se ubicará en /test/unit/paquete/.. class GenteUnitarioSpec extends Specification { def setup() { } def cleanup() { } void testAddToFriends() { Persona p1 = new Persona( nombre:'Nacho', apellidos:'brito', email:'[email protected]', amigos:[]) Persona p2 = new Persona( nombre:'Jacinto', apellidos:'Benavente', email:'[email protected]', amigos:[]) Página 132 Página 52 de 68 Apuntes Grails. 201507. Jose A. Duran def testInstances = [p1,p2] mockDomain(Persona,testInstances) def srv = new GenteService() srv.addToFriends(p1,p2) assertTrue p1.amigos.contains(p2) assertTrue p2.amigos.contains(p1) } } 17.2 El método mock Gracias a la clase GrailsUnitTestCase nuestros objetos se comporten en pruebas como lo harían en ejecución: • mockDomain(clase,testInstances=) - Toma la clase proporcionada como primer parámetro y la altera para añadir toda la funcionalidad de una entidad en GORM. Añade tanto los métodos estáticos (get(), count(), etc) como los de instancia (save(), delete(), etc). El segundo parámetro es una lista en la que se irán almacenando las instancias de la clase cada vez que se invoque el método save(). • mockForConstraintsTests(clase, testInstances=) - Permite comprobar el funcionamiento de las reglas de validación en clases de entidad y objetos comando. Por ejemplo: void testValidaciones() { mockForConstraintsTests(Persona,[]) Persona p = new Persona() //1. probar que no puede haber nulos assertFalse p.validate() assertEquals 'nullable',p.errors['nombre'] assertEquals 'nullable',p.errors['apellidos'] assertEquals 'nullable',p.errors['email'] //2. probar la validación del email p.email = 'Email incorrecto' assertFalse p.validate() assertEquals 'email',p.errors['email'] } • mockLogging(clase, enableDebug = false) – añade la propiedad log a la clase proporcionada. Todos los mensajes de log serán trazados por consola. • mockController(clase) – añade todas las propiedades y métodos dinámicos a la clase indicada para que se comporte como un controlador. • mockTagLib(clase) – añade todas las propiedades y métodos dinámicos a la clase proporcionada para que se comporte como una librería de etiquetas GSP. 17.3 Test integración Para revisar funcionamiento de los métodos teniendo en cuenta el contexto. 17.4 Test funcional No incluidos en Grails. Existe un plugin webtest que da soporte para Canoo Webtest Página 53 de 68 Apuntes Grails. 201507. Jose A. Duran 18 Internacionalización o i18n (solo cat) Usado para que la interfaz gráfica de Grails de soporte a múltiples idiomas. Usamos ficheros properties en la carpeta grails-app/i18n. En ellos guardamos las distintas versiones de cada mensaje soportados por nuestra aplicación. Cada fichero define un idioma y acaba en el 'Locale' del idioma, por ejemplo: • messages_es_AR.properties para los mensajes en español de Argentina • messages_es_ES.properties para los mensajes en español de España • messages_en.properties para los mensajes en inglés Para decidir qué idioma mostrar a un usuario, Grails lee el valor de la cabecera Accept-Language que el navegador envían en cada petición. Para cambiar el idioma e ignorar la cabecera podemos añadir el parámetro lang en cualquier URL para forzar a Grails a usar un idioma particular: /persona/create?lang=es Desde ese momento nuestra elección quedará registrada en un Cookie y se usará en las siguientes peticiones. 18.1 Funcionamiento Si queremos que nuestra aplicación muestre los mensajes a cada usuario en el idioma que haya seleccionado, tenemos que escribir nuestras vistas y controladores teniendo en cuenta los archivos de recursos. En lugar de definir los mensajes de texto directamente en las GSP, tendremos que utilizar la etiqueta message: <g:message code="mi.mensaje.01" /> De esta manera, Grails buscará en el archivo messages del idioma correspondiente, fichero messages_XX.properties una fila como esta: mi.mensaje.01=Hola mensaje localizado 01_ES Atención si en el fichero de idioma no encuentra el mensaje, lo buscará en el genérico, messages.properties. Si es necesario se pueden incluir parámetros en los mensajes: mi.mensaje.02=Hola mensaje localizado 02_ES {0} y proporcionar valores mediante el parámetro args en la etiqueta message: <g:message code="mi.mensaje.02" args="${['Jose']}" /> El patrón {0} se sustituirá por el primer valor de la lista, el {1} por el segundo, y así sucesivamente. Página 54 de 68 Apuntes Grails. 201507. Jose A. Duran 19 Seguridad Mecanismos incluidos en Grails cuenta con varios mecanismos de seguridad para evitar ataques de inyección SQL y HTML: • Todo el SQL generado en GORM se escapa para evitar inyección de SQL. • Cuando generamos HTML mediante scaffolding, los datos se escapan antes de mostrarse. • Las etiquetas que generan URLs (link, form, createLink, resource, etc) utilizan los mecanismos de escapado apropiados. La propia Máquina Virtual Java hace las labores de cajón de arena para evitar problemas de desbordamiento de memoria y ejecución de código malicioso. La mayoría de los ataques que se reciben en aplicaciones web tienen que ver con técnicas incorrectas o mal aplicadas en alguno de estos contextos: • Generación de consultas a la base de datos. Si construimos consultas SQL, o incluso HQL de Hibernate, introduciendo parámetros de la petición HTTP sin procesar, estamos expuestos a ataques de inyección de SQL. • Generación de html. Si en nuestras GSP mostramos valores recibidos como parámetros en la petición, y no los escapamos, estamos expuestos a ataques de inyección de HTML (y Javascript). 19.1 Tipos de ataques Los ataques más comunes incluyen. - Inyección SQL, inclusión de código SQL en campos o dirección URL, para evitarlo debemos usar Hibernate para procesar parámetros y sustituir de los caracteres especiales. - Denegación de servicio, realizan peticiones masivas para provocar la caída de una aplicación. - Inyección Javascript y HTML, por ejemplo aprovechar un campo comentario para incluir código y que se ejecute. Utilizamos codecs que permiten valores para usarlos en HTML, URL etc .. <g:each in="${comentarios}"> <div class="comentario"> ${it.texto.encodeAsHTML} </div> </g:each> El método encodeAsHTML sustituye todos los caracteres de nuestra cadena que tienen algún significado en HTML, como < y > por secuencias equivalentes como &lt; y &gt; evitando la inclusión de posible código. Existen diversas utilidades para evitar la inclusión de código. • HTMLCodec – permite escapar y des-escapar los caracteres especiales en un texto para que no incluya etiquetas HTML. • URLCodec – permite incluir el texto en una URL, escapando todos los caracteres ilegales. • Base64Codec – codifica y descodifica Base64 • JavaScriptCodec – codifica strings escapando caracteres ilegales en javascript. • HexCodec – codifica un array de bytes o una lista de enteros en un string de valores hexa. • MD5Codec – codifica un array de bytes, una lista de enteros o una cadena de texto, como un hash MD5 en un string. • MD5BytesCodec – igual que el anterior, pero devolviendo el array de bytes en lugar del string. • SHA1Codec – codifica un array de bytes, una lista de enteros o una cadena de texto, como un hash SHA1 en un string. • SHA1BytesCodec – igual que el anterior, pero devolviendo el array de bytes en lugar del string. • SHA256Codec – codifica un array de bytes, una lista de enteros, o una cadena de texto, como un hash SHA256 en un string. • SHA256BytesCodec – igual que el anterior, pero devolviendo el array de bytes en lugar del string. Página 55 de 68 Apuntes Grails. 201507. Jose A. Duran 20. Desarrollo de Plugins Para el desarrollo de plugins con Grails vamos a crear dos extensiones diferentes. En la primera veremos cómo hacer que el plugin añada artefactos a la aplicación en la que lo instalemos. El segundo te mostrará como añadir métodos dinámicos a las clases de la aplicación. Posibilidades de los plugins. a – Incorporar nuevos artefactos, controladores, librerías de tags, servicios, vistas o plantillas. b – Alterar el funcionamiento de la aplicación. • Respondiendo a eventos Grails (cambio de versión, instalación del plugin, etc). • Alterando la configuración de Spring (nuevos beans disponibles en el contenedor). • Modificando la generación del archivo web.xml. • Añadiendo métodos dinámicos a las clases de la aplicación. • Monitorizando los cambios de ciertos componentes. Ejemplo. Añadir artefactos a la aplicación. En Grails un plugin es similar a una nueva aplicación Grails, en nuestro caso iwEmail, se creará una estructura similar a un proyecto. Incluye un fichero en la raíz del proyecto con el nombre iwEmailGrailsPlugin.groovy aquí entraremos la información del plugin. En este ejemplo se crea una etiqueta nueva que permite enviar por email grails create-plugin iwMail. Una vez creado el proyecto, lo primero que necesitamos es un servicio para enviar correo electrónico, así que ejecutamos: grails create-service email y rellenamos el archivo grails-app/services/EmailService.groovy, suponiendo que tenemos una clase com.imaginaworks.srvc.Email que hace el trabajo (es fácil desarrollar esa clase utilizando, por ejemplo, commons Email de Apache): class EmailServiceSpec extends Specification { boolean transactional = false def sendHTML( String smtp, String from, String toaddress, String content, String msgSubject) { log.info( "Envia correo de ${from} a ${toaddress}. Asunto: ${msgSubject}, Cuerpo:\n$ {content}") try{ Email.sendHTMLEmail( smtp,usr,pwd,from,toaddress, msgSubject,wrapWithHtml(content)); } catch(Exception x){ log.error("Error al enviar el correo: ${x}") x.printStackTrace(); } } def wrapWithHtml(msg) { //Continua el código siguiente página def wrapped = """ Página 56 de 68 Apuntes Grails. 201507. Jose A. Duran <html> <head> <style> body { color: #333; font-size: 0.8em; line-height: 1.8em; text-align: left; background-off: #E6E6E6; } </style> </head> <body> ${msg} </body> </html> """ return wrapped } } Tenemos por tanto dos métodos, uno público sendHTML que usa la clase Email para enviar el correo y un método que encuadra el texto del mensaje en una plantilla fija. Pasamos a crear una librería de etiquetas grails create-tag-lib email class EmailTagLib { static namespace = 'iw' EmailService mailService def email = {attrs, body -> def smtp = 'servidor.correo.smtp' def from = '[email protected]' try{ mailService.sendHTML( smtp, from, attrs.to, body(), attrs.sbj) } catch(Exception x){ log.error("ERROR: ${x}") } } } El nombre de la etiqueta que usaremos será <iw:email to='[email protected]' sbj='prueba'> <p> Cuerpo del mensaje en <strong>html</strong> </p> </iw:email> Podemos ejecutar el plugin con $>grails run-app, para distribuirlo debemos utilizar $>grails package-plugin Esto genera un fichero Zip con el contenido del proyecto ha excepción de: - grails-app/conf/DataSource.groovy - grails-app/conf/UrlMappings.groovy - build.xml - web-app/WEB-INF/** Para instalarlo en la aplicación deseada basta con usar. $>grails install-plugin /ruta/a/grails-iw-mail-0.1.zip Página 57 de 68 Apuntes Grails. 201507. Jose A. Duran 20.1 Instalar el Plugin Una forma es ejecutar el comando install-plugin. Al Hacerlo, Grails extraerá el contenido del archivo ZIP en un directorio central dentro de tu carpeta de usuario ($HOME/.grails/ [version]/projects/[aplicación]/plugins/) y pondrá a disposición de tu aplicación los artefactos definidos en el plugin. El código fuente del plugin no interfiere con tu proceso de desarrollo. Cuando generes el archivo war de tu aplicación, Grails compilará todas las clases Groovy y Java del plugin y colocará los archivos .class en WEB-INF/classes junto con los de tuyos. Es muy importante que tengas en cuenta que si el plugin incluye contenido estático (imágenes, archivos javascript, hojas de estilo, etc), Grails los copiará en la carpeta web-app/plugins/[tu plugin] del proyecto, lo cual afectará a la hora de crear enlaces a dichos recursos. Para evitar problemas puedes utilizar la variable pluginContextPath en tus enlaces y rutas: <g:resource dir='${pluginContextPath}/js' file='utils.js' /> Ejemplo 2. añadir un método para la representación de cada instancia en formato CSV, csvHeader generar encabezado con nombres de columnas y toCSVRow para la salida de cada instancia. Así en el fichero CsvPluginGrailsPlugin.groovy def doWithDynamicMethods = { ctx -> // TODO Implement registering dynamic methods to classes (optional) //Método estático para escribir la cabecera del CSV gdc.metaClass.'static'.csvHeader = {Object[] args -> def csv = '' gdc.persistentProperties.each{ csv += "${it.name}," } csv = csv[0..csv.size() - 2] + '\n' return csv } //Método de instancia para convertir un objeto en una fila CSV gdc.metaClass.toCSVRow = { Object[] args -> def csv = '' def value gdc.persistentProperties.each{ value = delegate."${it.name}" csv += "${value}," } csv = csv[0..csv.size() - 2] + '\n' return csv }} De esta forma, al inicializar el plugin recorremos todas las clases de entidad definidas en la aplicación y añadiendo dos métodos, uno estático y otro de instancia. Esta técnica es posible gracias a que cada plugin tiene acceso a una implementación de la interfaz GrailsApplication que, entre otras cosas, nos da acceso a los distintos artefactos mediante una serie de propiedades dinámicas (al estilo de los dynamic Finders de GORM): • *Classes – obtiene todas las clases para un tipo particular de artefacto. • get*Class – localiza el artefacto por su nombre (ej getDomainClass(“Persona”)) • is*Class – comprueba si una clase es un tipo particular de artefacto. • add*Class – añade un artefacto a la aplicación, devolviendo una referencia a él para poderlo manipular. En cada caso el asterisco se podrá sustituir por todos los tipos de artefactos: • controller • domain • taglib • service Página 58 de 68 Apuntes Grails. 201507. Jose A. Duran 21 Servicios WEB SOAP-REST • SOAP (Simple Object Access Protocol) – es un estándar del W3C que define cómo objetos remotos pueden comunicarse mediante el intercambio de XML. En la comunicación hay dos partes (cliente y servidor), una de las cuales (el servidor) presta una serie de servicios que son consumidos por la otra (cliente). Es habitual que el servidor haga pública la especificación de sus servicios mediante un documento WSDL (Web Service Description Language) que podemos utilizar construir un cliente que invoque tales servicios. Los servicios web SOAP están orientados a funcionalidad. El servidor implementa una serie de funcionalidades y le dice al mundo cómo pueden invocarse. • REST (Representational State Transfer) – es un conjunto de técnicas orientadas a crear servicios web en los que se renuncia a la posibilidad de especificar la interfaz de los servicios de forma abstracta a cambio de contar con una convención que permite manejar la información mediante una serie de operaciones estándar. La convención utilizada no es otra que el protocolo HTTP. La idea detrás de REST es el desarrollo de servicios orientados a la manipulación de recursos. En un servicio REST típico, tenemos una URL por cada recurso (documento, entidad, etc) que gestionamos, y que realiza una tarea diferente sobre dicho recurso en función del método HTTP que utilicemos. 21.1 SOAP servir 1 dato. Ejemplo. Desarrollar servicio CRUD: Creación, Lectura, Actualización y Borrado. Grails no soporta SOAP por defecto pero existen plugins que permiten implementarlo. El plugin CXF permite que demos servicio SOAP. a – Instalación plugin cxf. En el fichero BuildConfig.groovy añadimos referencia al plugin plugins { //JAD_20150728.01 Plugin X dar servicio SOAP compile ":cxf:2.1.1" } b - Y en eclipse, actualizamos, Refresh Dependencies, añadiendo eclipse el plugin al proyecto. c - Creación servicio TestService import grails.transaction.Transactional import javax.jws.* @Transactional class TestService { static expose= ['cxfjax'] @WebMethod def TestList (){ return Persona.load(1).nombre } @WebMethod def serviceMethod() { String a = "ENVIANDO" String aa = "SALIDA SOAP" println a return aa } } Donde con @WebMethod indicamos los métodos publicados o disponibles. Debemos recordar que si queremos que un dominio este disponible debemos incluir en su cabecera import javax.xml.bind.annotation.* @XmlAccessorType(XmlAccessType.FIELD) class Persona { String nombre String numero } Página 59 de 68 Apuntes Grails. 201507. Jose A. Duran Este código nos proporciona en http://.../services un formulario se listan los servicios disponibles. Y en services/test?wsdl tenemos publicados los diferentes servicios ofrecidos. Para ver este servicio podemos utilizar, SoapUI o la extensión de crome Wizdler Y al ejecutarlo con GO obtenemos la respuesta. Aquí vemos como tan solo devolvemos un dato, nombre de persona id 1. Página 60 de 68 Apuntes Grails. 201507. Jose A. Duran 21.2 SOAP devolver objeto. Ejemplo. Devolver un objeto mediante SOAP, igual que el anterior caso empezamos con la instalación del plugin cxf. a – Instalación plugin cxf. En BuildConfig.groovy y refrescamos dependencias. plugins { //JAD_20150730 Plugin X dar servicio SOAP compile ":cxf:2.1.1" } b – Informamos en la entidad del tipo @XmlAccesorType, NONE y de los campos que queremos sean visibles/accesibles en el servicio, marcandolos con @XmlElement. import javax.xml.bind.annotation.* @XmlAccessorType(XmlAccessType.NONE) class Persona{ @XmlElement String nombre @XmlElement String dni //El campo codigo no se mostrará //No tiene la constante @XmlElement String codigo } c – Pasamos a crear el servicio, Test, desde donde publicaremos los datos SOAP. import grails.transaction.Transactional import javax.jws.* @Transactional class TestService { static expose = ['cxfjax'] @WebMethod Persona devolverObjeto() { println "Ejecutando Advanced Soap Service" Persona persona = new Persona() persona.nombre="Jose A" persona.dni="454L" persona.codigo="222" return persona } } Nota. Para ver publicados el/los servicios los 'exponemos' con expose = ['cxfjax'] Al acceder a localhost:/../services tenemos la página con los servicios publicados. Página 61 de 68 Apuntes Grails. 201507. Jose A. Duran Al acceder al servicio publicado obtenemos el fichero wsdl Y al entrar en Ejecutamos y obtenemos el resultado esperado. Nota. Observar que el campo codigo no se devuelve al no estar indicado en la entidad. Página 62 de 68 Apuntes Grails. 201507. Jose A. Duran 21.3 REST El servicio REST no necesita plugins para su funcionamiento. Podemos implementarlo directamente en un controller. import grails.converters.* class PersonaController { def rest = { switch(request.method){ case 'GET': doGet(params) break; case 'POST': doPost(params) break; case 'PUT': doPut(params) break; case 'DELETE': doDelete(params) break; } } private void doPost(params){ def p = new Persona() def d = request.XML p.nombre = d.nombre p.numero = d.numero if(p.save()){ render p as XML }else{ response.status = 500 render p.errors as XML } } private void doGet(params){ def p = Persona.get(params.id) render(contentType:'text/xml'){ personas { persona{ codigo(p.numero) nombreCompleto( "${p.nombre}") } } } } private void doPut(params){ println params.id def p = Persona.get(params.id) def d = request.XML p.nombre = d.nombre p.numero = d.numero if(p.save()){ render p as XML } else{ response.status = 500 render p.errors as XML } } private void doDelete(params){ def p = Persona.get(params.id) p.delete() render(contentType:'text/xml'){ resultado("Persona con id ${params.id} borrada correctamente.") } } } Todos los módulos funcionan de forma parecida, capturan por request.XML la información de entrada, id, Página 63 de 68 Apuntes Grails. 201507. Jose A. Duran campos y muestran la salida. Pantallas: POST, nuevo registro GET, muestra registro PUT modifica DELETE, borra Página 64 de 68 Apuntes Grails. 201507. Jose A. Duran 22 Consumir servicios SOAP Previo. Debemos conocer los datos de el/los servicios a consumir. Algunos servidores de ejemplo. http://www.webservicex.net/ - donde tenemos múltiples servicios. http://www.imn.ac.cr/MeteorologicoWS/MeteorologicoWS.asmx?WSDL Servicio metereologico. a - Utilizaremos cxf-client. En el fichero BuildConfig.groovy añadimos referencia al plugin plugins { ///JAD_20150728.01 Plugin X recibir servicio SOAP compile ":cxf-client:2.1.1" } Y en eclipse, actualizamos, Refresh Dependencies, añadiéndose el plugin al proyecto. b – Procedemos añadiendo al fichero /grails-app/conf/Config.groovy la información de los webservices a consultar. cxf { client { tiempoClient { wsdl = "http://www.imn.ac.cr/MeteorologicoWS/MeteorologicoWS.asmx?WSDL" clientInterface = org.tempuri.WSMeteorologicoSoap serviceEndpointAddress = "http://www.imn.ac.cr/MeteorologicoWS/MeteorologicoWS.asmx" } datosBolsaClient { wsdl = "http://www.webservicex.net/stockquote.asmx?WSDL" clientInterface = net.webservicex.StockQuoteSoap serviceEndpointAddress = "http://www.webservicex.net/stockquote.asmx" } cambioMonedaClient{ wsdl = "http://www.webservicex.net/CurrencyConvertor.asmx?WSDL" clientInterface = net.webservicex.CurrencyConvertorSoap serviceEndpointAddress = "http://www.webservicex.net/CurrencyConvertor.asmx" } } } Definimos en wsdl dirección fichero DSL donde está la información sobre el servicio SOAP, clientInterface la clase que implementa la interface para la petición SOAP, serviceEndpointAddress la dirección donde se encuentra el asmx. c – Ahora desde consola debemos ejecutar la orden grails wsdl12java en el directorio del proyecto. Esto genera/carga las clases ubicadas en el servidor wsdl a nuestro proyecto en /src/java. Para este paso solo es necesario el parámetro wsdl, podríamos intercalarlo en el punto b sino tenemos suficiente información sobre el servicio. Página 65 de 68 Apuntes Grails. 201507. Jose A. Duran d – Implementar las clases del servicio SOAP cargadas en nuestro proyecto, controlador, servicio o layout. import org.tempuri.* import net.webservicex.* class DemoController { def index() { } WSMeteorologicoSoap tiempoClient def meteoCxfFecha (){ String fecha try{ fecha = tiempoClient.fecha() println fecha render fecha } catch(Exception e){ } } StockQuoteSoap datosBolsaClient def bolsaABB (){ String valor try{ valor = datosBolsaClient.getQuote("ABB") println valor render valor } catch(Exception e){ } } CurrencyConvertorSoap cambioMonedaClient def cambio_DoEu (){ double cambio try{ cambio = cambioMonedaClient.conversionRate(Currency.USD, Currency.EUR) println cambio render cambio } catch(Exception e){ } } } Página 66 de 68 Apuntes Grails. 201507. Jose A. Duran Importamos las clases SOAP. En los actions llamamos a la clase que realiza la tarea siguiendo las instrucciones/código que podemos encontrar en las clases SOAP importadas. public interface CurrencyConvertorSoap { */.........../* @WebResult(name = "ConversionRateResult", targetNamespace = "http://www.webserviceX.NET/") @RequestWrapper(localName = "ConversionRate", targetNamespace = "http://www.webserviceX.NET/", className = "net.webservicex.ConversionRate") @WebMethod(operationName = "ConversionRate", action = "http://www.webserviceX.NET/ConversionRate") @ResponseWrapper(localName = "ConversionRateResponse", targetNamespace = "http://www.webserviceX.NET/", className = "net.webservicex.ConversionRateResponse") public double conversionRate( @WebParam(name = "FromCurrency", targetNamespace = "http://www.webserviceX.NET/") net.webservicex.Currency fromCurrency, @WebParam(name = "ToCurrency", targetNamespace = "http://www.webserviceX.NET/") net.webservicex.Currency toCurrency ); } Aquí podemos observar el resultado las distintas actions. Página 67 de 68 Apuntes Grails. 201507. Jose A. Duran 23 GROOVY Basado en Java. Lo amplia y simplifica, y es igual que Java en: • Separación del código en paquetes. • Definición de clases (excepto clases internas) y métodos. • Estructuras de control, excepto el bucle for(;;). • Operadores, expresiones y asignaciones. • Gestión de excepciones. • Literales (con algunas variaciones) • Instalación, referencia y de-referencia de objetos, llamadas a métodos; Y nos mejora o expande: • La facilidad de manejo de objetos, mediante nuevas expresiones y sintaxis. • Distintas formas de declaración de literales. • Control de flujo avanzado mediante nuevas estructuras. • Nuevos tipos de datos, con operaciones y expresiones específicas. • En Groovy todo es un objeto. • Brevedad: El código Groovy es mucho más breve que el de Java. Mas: • No hay ';' al final de las líneas. En Groovy es opcional. • La palabra reservada def permite declarar una variable de tipo dinámico. También es posible declarar variables con tipo estático (ej: Integer numero) • Los comentarios se crean con // o /* */, como en Java. • print y println son equivalentes a System.out.println() y System.out.print(), respectivamente. • El parámetro a la función println no va entre paréntesis. En Groovy también es opcional Ejemplo class Persona { private String nombre private String apellido Persona(String n, String a){ nombre = n apellido = a } String toString() { "Persona: $nombre $apellido" } } a - Groovy genera los getters y setters necesarios. b - No necesitamos ';' final linea. c - Nos genera los constructores por defecto. d - Sin especificar un return por defecto nos muestra el último método. e - Podemos insertar variables $nombre dentro de una cadena. Página 68 de 68