- 7-2 Controladores Vistas Librería etiquetas Servicios Consola Plantillas

Apuntes Grails. 201507. Jose A. Duran GRAILS – Apuntes - 3333445- 1 Creación proyecto – VISTAZO RAPIDO 1.2 Crear 'domain controllers' 1.3 Completar

0 downloads 113 Views 905KB Size

Recommend Stories


Bases de Datos I. Cursada Clase 6: Vistas. Vistas. Vistas
Bases de Datos I Cursada 2008 Clase 6: Vistas Facultad de Ciencias Exactas Universidad Nac. Centro de la Pcia. de Bs. As. BASES DE DATOS I Vistas 

Nimbus, servicios en la nube. Etiquetas y filtros
Nimbus, servicios en la nube Etiquetas y filtros SERVICIOS USAL EN NUBE Etiquetas y filtros 1| 2| 3| 4| 5| Etiquetas Crear etiquetar Etiquetar men

Story Transcript

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. Prueba/accion Hoy es: ${new Date()}

Lo que tengo que decir es: ${miVariable}

Página 7 de 68



Apuntes Grails. 201507. Jose A. Duran

Y obtendremos el HTML

Como observamos se ha generado la variable miVariable, en la etiqueta out -> -> -> -> -> ->

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:"Hubo un 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.

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.

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. 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 Por favor, no pulse el botón enviar hasta que haya terminado.

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 . 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: 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.: Iniciar sesión • [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).

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: Hola, administrador. Hola, editor. Hola, usuario.

• each – Permite iterar sobre los elementos de una colección:

Hola, ${nom}



• while – Permite iterar mientras se cumpla la condición indicada:

Número actual: ${i++}



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:

numeros: ${arrai}

Nu: ${it}



• grep – Busca entre los elementos de una colección de dos formas distintas: a - Buscar objetos de una clase particular:

${it.nombre} ${it.apellidos}



b - Buscar cadenas que cumplan una expresión regular:

${it}



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. Persona 1 Listado de personas Listado alfabético de Personas

• 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:



• resource – Crea enlaces a recursos estáticos como hojas de estilo o scripts javascript:

• include – Permite empotrar la respuesta de otra acción o vista en la respuesta actual:

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:

• 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:

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:

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

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": Ver Listado Completo • 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: . . . • submitToRemote – crea un botón que envía el formulario actual mediante AJAX. Especialmente útil en casos en que no podemos utilizar formRemote. ... • 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:

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. 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"); } Mostrar la persona con id 3

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); } Probar

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 function actualizar(e){ //evaluamos el JSON: var p = eval("(" + e.responseText + ")"); $(personaActiva).innerHTML = p.nombre } Mostrar persona con ID 3

12.4 Usar etiquetas como métodos Las etiquetas GSP pueden invocarse como funciones. Nos permite usarlas desde • atributos en otras etiquetas: • 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 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: … … 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. Ver artículos

antes

Ver persona 1

El enlace generado será: Ver artículos 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

Posts sobre Groovy

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”: . . .

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 = "

Get in touch

Social

© Copyright 2013 - 2024 MYDOKUMENT.COM - All rights reserved.