GRAILS – Apuntes

Anuncio
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 < y > 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
Descargar