8. Configuración de aplicaciones. Plugins interesantes

En esta última sesión de Grails analizaremos los diferentes aspectos de configuración de nuestra aplicación y posteriormente instalaremos algunos plugins interesantes desarrollados por la comunidad de usuarios de Grails.

8.1. Configuración de aplicaciones

8.1.1. El archivo Config.groovy

El archivo grails-app/conf/Config.groovy contiene los parámetros de configuración general de nuestra aplicación. En este archivo se pueden declarar variables que estarán disponibles en cualquier artefacto de nuestra aplicación a través del objeto global grailsApplication.config. Por ejemplo si definimos la siguiente variable es.ua.expertojava.todo.miParametro = "dato", ésta va a ser accesible desde cualquier controlador, vista, servicio, etc. mediante la expresión grailsApplication.config.es.ua.expertojava.todo.miParametro.

Además de poder declarar nuevas variables globales, el archivo Config.groovy utiliza una serie de variables definidas que tienen el siguiente significado:

  • grails.config.locations: ubicaciones donde encontrar otros archivos de configuración que se fundirán con el principal Config.groovy. Esta variable está comentada por defecto con lo que sólo se tendrá en cuenta el archivo Config.groovy.

  • grails.project.groupId: nombre por defecto del paquete en el que se crean todos los artefactos de la aplicación. Por defecto tenemos que este paquete coincidirá con el nombre de la aplicación. Lo podríamos modificar por ejemplo a "es.ua.expertojava.todo" para no ahorrarnos tener que especificar el paquete cada vez que creamos un nuevo artefacto.

  • grails.views.default.codec: especifica el formato por defecto de nuestras páginas GSPs. Puede tomar el valor 'none', 'html', o 'base64'. Por defecto se utiliza el valor html.

  • grails.controllers.defaultScope: el scope por defecto de los controladores, en principio singleton.

  • grails.views.gsp: un mapa de configuración de como se van a renderizar nuestras páginas GSP.

  • grails.converters.encoding: codificación de los convertidores

  • grails.scaffolding.templates.domainSuffix: el sufijo añadido a las instancias de nuestras clases de dominio en el plugin scaffolding.

  • grails.mime.types: indica un mapa con los posibles tipos mime soportados en nuestra aplicación

8.1.2. Sistema de logs

Grails utiliza la librería log4j para implementar todo el sistema de logs. Podemos configurar estos logs de nuestras aplicaciones en el propio archivo Config.groovy. Aquí encontraremos una variable llamada log4j que ya viene con algunas reglas.

log4j.main = {
    error  'org.codehaus.groovy.grails.web.servlet',  //  controllers
           'org.codehaus.groovy.grails.web.pages', //  GSP
           'org.codehaus.groovy.grails.web.sitemesh', //  layouts
           'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
           'org.codehaus.groovy.grails.web.mapping', // URL mapping
           'org.codehaus.groovy.grails.commons', // core / classloading
           'org.codehaus.groovy.grails.plugins', // plugins
           'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
           'org.springframework',
           'org.hibernate',
           'net.sf.ehcache.hibernate'
}

En esta configuración se especifica que aquellos problemas surgidos a nivel error serán añadidos al archivo de logs. Habitualmente tenemos ocho niveles estándar de logs.

  • off

  • fatal

  • error

  • warn

  • info

  • debug

  • trace

  • all

Esto significa que si por ejemplo especificamos warn 'org.example.domain', todos los problemas surgidos de tipo warn, error o fatal serán impresos en el archivo. Los otros niveles serán ignorados.

Lo habitual es que necesitemos generar logs en controladores, servicios y otros artefactos. Estos artefactos se encuentran en el paquete grails.app.<tipo_de_artefacto>.<nombre_de_clase>.

log4j.main = {
  // Establecemos el nivel info para todos los artefactos de la aplicación
  info "grails.app"

  // Especificamos el nivel debug para el controlador de la clase Tag
  debug "grails.app.controllers.es.ua.expertojava.todo.TagController"

  // Especificamos el nivel error para el servicio de la clase Todo
  error "grails.app.services.es.ua.expertojava.todo.TodoService"

  // Especificamos el nivel para todas las librerías de etiquetas
  info "grails.app.taglib"
}

Los tipos de artefactos pueden ser los siguientes:

  • conf: para todo lo que esté en el directorio de configuración grails-app/con

  • filters: para los filtros

  • taglib: para las librerías de etiquetas

  • services: para los servicios

  • controllers: para los controladores

  • domain: para las clases de dominio

Vamos a implementar un pequeño ejemplo en nuestra aplicación. En primer lugar, vamos a crear un appender, que no es más que un objeto en el cual especificamos como y donde queremos escribir los mensajes de log. En nuestro caso lo haremos escribiendo en un archivo de logs, aunque también podríamos haber optado por enviarlo a un correo electrónico o bien almacenarlo en una base de datos.

log4j.main = {
  appenders {
    file name:'file', file:'mylog.log'
  }
}

A continuación, vamos a indicarle a nuestra aplicación que queremos utilizar este appender en el controlador de la clase Category.

trace file: "grails.app.controllers.es.ua.expertojava.todo.CategoryController"

Por último, añadiremos una línea en el método index() del controlador CategoryController para escribir un mensaje de prueba.

def index() {
  log.trace("Método index del controlador CategoryController")
  params.max = Math.min(max ?: 10, 100)
  respond Category.list(params), model:[categoryInstanceCount: Category.count()]
}

Si todo ha ido bien, tendremos un archivo en la raíz de nuestra aplicación llamado mylog.log y que añadirá el mensaje que hemos creado cada vez que se muestre el listado de usuarios.

2015-03-28 12:05:08,549 ["http-bio-8080"-exec-1] TRACE es.ua.expertojava.todo.CategoryController  -
Método index del controlador CategoryController

8.1.3. El archivo BuildConfig.groovy

Además del archivo Config.groovy, todos los aspectos relativos a la configuración de la generación del proyecto se encuentran en el archivo conf/BuildConfig.groovy. En este archivo podemos encontrar las siguientes variables definidas:

  • grails.servlet.version: indicamos la especificación de Servlets. Por defecto utilizaremos la versión 3.0.

  • grails.project.class.dir: especificamos el directorio donde se alojarán todos los archivos compilados del proyecto. Por defecto se alojarán en el directorio target/classes

  • grails.project.test.class.dir: especificamos el directorio donde se compilarán los tests de nuestros proyectos. Por defecto se alojarán en el directorio target/test-classes

  • grails.project.test.reports.dir: especificamos el directorio donde se alojarán los informes de las ejecuciones de los tests. Por defecto los tendremos en target/test-reports

  • grails.project.target.level: especificamos la versión de la máquina virtual de java. Por defecto será la 1.6

  • grails.project.war.file: especificamos el nombre y la ubicación del archivo war que se generará. Por defecto, se alojarán en el directorio target y se llamará con el nombre de la aplicación seguido de la versión, empezando por la 0.1

  • grails.project.fork: parámetros de configuración de la máquina virtual de Java utilizada en cada entorno de ejecución.

  • grails.project.dependency.resolution: especificamos como se resuelven las dependencias de los archivos JAR de nuestros proyectos.

  • grails.project.dependency.resolution.plugins: un listado de los plugins utilizados en nuestra aplicación.

8.1.4. El archivo DataSource.groovy

Muchas empresas disponen de varios entornos que las aplicaciones deben superar para finalmente pasar a disposición de los usuarios finales. Los entornos más habituales son el entorno de desarrollo que se refiere al entorno propio del desarrollador, con su propio servidor local instalado en su ordenador, el entorno de tests en el que otras personas se encargan de comprobar que la aplicación funciona tal y como se espera de ella y por último, el entorno de producción, que es donde aparecen en escena los usuarios finales, que son quienes realmente "probarán" el buen funcionamiento de la aplicación.

En cada uno de estos entornos, lo habitual es tener una configuración de base de datos diferente para cada uno, puesto que los requerimientos serán distintos. Por ejemplo, en un entorno de desarrollo posiblemente nos sea suficiente utilizar una base de datos en memoria como H2, pero este tipo de base de datos será insuficiente para los entornos de test y producción con lo que deberemos utilizar un servidor de base de datos como por ejemplo MySQL.

Como no podía ser menos, Grails lo tiene todo preparado para realizar esta diferenciación sin problemas. El archivo grails-app/conf/DataSource.groovy se encarga de todo mediante la creación por defecto de tres entornos de desarrollo: desarrollo, test y producción, tal y como puedes comprobar en el siguiente ejemplo.

dataSource {
    pooled = true
    jmxExport = true
    driverClassName = "org.h2.Driver"
    username = "sa"
    password = ""
}
hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = false
//    cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3
    cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4
    singleSession = true // configure OSIV singleSession mode
    flush.mode = 'manual' // OSIV session flush mode outside of transactional context
}

// environment specific settings
environments {
    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"
        }
    }
    test {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
        }
    }
    production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
            properties {
               // See http://grails.org/doc/latest/guide/conf.html#dataSource for documentation
               jmxEnabled = true
               initialSize = 5
               maxActive = 50
               minIdle = 5
               maxIdle = 25
               maxWait = 10000
               maxAge = 10 * 60000
               timeBetweenEvictionRunsMillis = 5000
               minEvictableIdleTimeMillis = 60000
               validationQuery = "SELECT 1"
               validationQueryTimeout = 3
               validationInterval = 15000
               testOnBorrow = true
               testWhileIdle = true
               testOnReturn = false
               jdbcInterceptors = "ConnectionState"
               defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED
            }
        }
    }
}

En el primer bloque dataSource se definen una serie de valores genéricos, que posteriormente podremos sobreescribir en cada uno de los entornos. El bloque hibernate se refiere a parámetros de configuración del propio hibernate, mientras que el último bloque de environments es el que nos va a permitir diferenciar cada uno de los entornos.

Por defecto Grails crea los tres entornos de los que hablábamos anteriormente. Con el comando grails run-app que hemos utilizado hasta ahora, la aplicación se ejecuta en el entorno de desarrollo, que es el utilizado por defecto, pero si quisiéramos utilizar otro entorno podríamos ejecutar los comandos de la siguiente tabla:

Entorno Comando

Desarrollo

grails dev run-app o grails run-app

Test

grails test run-app

Producción

grails prod run-app

En el fichero DataSource.groovy se puede comprobar las tres configuraciones para cada uno de los entornos. Para los entornos de desarrollo y test se va a utilizar una base de datos en memoria como H2, eso si, diferente para cada uno de ellos (devDB y testDb).

Para el entorno de producción deberíamos utilizar una base de datos más robusta y propia de entornos de producción como es MySQL. Para ello le especificamos la URI de la base de datos, así como el controlador, el nombre de usuario y la contraseña de acceso a la misma.

production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:mysql://localhost:3306/todo?useUnicode=true&characterEncoding=UTF-8"
            username = "userprod"
            password = "pwdprod"
            properties {
               // See http://grails.org/doc/latest/guide/conf.html#dataSource for documentation
               jmxEnabled = true
               initialSize = 5
               maxActive = 50
               minIdle = 5
               maxIdle = 25
               maxWait = 10000
               maxAge = 10 * 60000
               timeBetweenEvictionRunsMillis = 5000
               minEvictableIdleTimeMillis = 60000
               validationQuery = "SELECT 1"
               validationQueryTimeout = 3
               validationInterval = 15000
               testOnBorrow = true
               testWhileIdle = true
               testOnReturn = false
               jdbcInterceptors = "ConnectionState"
               defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED
            }
        }
    }

Además, deberíamos disponer del driver correspondiente de MySQL (http://www.mysql.com/downloads/connector/j/) y copiarlo en el directorio lib de la aplicación o bien especificarlo en las dependencias en el archivo BuildConfig.groovy.

grails.project.dependency.resolution = {
  ...

  dependencies {
    runtime 'mysql:mysql-connector-java:5.1.34'
  }
  ....
}

Además, mediante el parámetro dbCreate podemos especificar la forma en la que Grails debe generar el esquema de la base de datos a partir de la definición de las clases de dominio. Este parámetro puede tomar tres valores diferentes:

  • create-drop. El esquema de la base de datos se creará cada vez que arranquemos la aplicación en el entorno dado, y será destruido al interrumpir su ejecución.

  • create. Crea el esquema de la base de datos, en caso de que el éste no exista, pero no modifica el esquema existente, aunque si borrará todos los datos almacenados en la misma.

  • update. Crea la base de datos si no existe, y actualiza las tablas si detecta que se han añadido entidades nuevas o campos a los ya existentes. Estas modificaciones no incluirán la eliminación o modificación de nada en la base de datos, con lo que hay que tener cuidado con este tipo de cambios en las clases de dominio y que luego no se ven reflejados automáticamente en la base de datos.

  • validate. Simplemente compara nuestro esquema actual con la base de datos especificada para enviarnos algún que otro aviso de diferencia.

  • cualquier otro valor. Si especificamos cualquier otro valor, Grails no hará nada por nosotros. No debemos especificar ningún valor a la variable dbCreate si nos encargaremos de la base de datos nosotros mismos mediante una herramienta externa.

8.1.5. El archivo BootStrap.groovy

A lo largo de las sesiones relativas a Grails, hemos utilizado el archivo de configuración BootStrap.groovy para insertar ciertos datos de ejemplo en nuestra aplicación que nos han servido para comprobar su funcionamiento.

El archivo define dos closures, init y destroy. El primero de ellos se ejecutará cada vez que ejecutemos la aplicación mediante el comando grails run-app en cualquiera de los entornos de ejecución recientemente comentados. Mientras que el closure destroy se ejecutará cuando paremos la ejecución de la aplicación.

Hasta el momento, cuando hemos insertado los datos en la base de datos, lo hacíamos independientemente del entorno de ejecución en el que estuviéramos, algo que no será lo habitual, puesto que para los entornos de test y producción, es más que probable que los datos ya estén almacenados en la base de datos o bien se inserten mediante una batería de pruebas en el caso del entorno de tests.

Teniendo en cuesta esto, necesitaremos diferenciar en cada momento el entorno de ejecución de nuestra aplicación para insertar datos o no. Para ello, Grails dispone de la clase grails.util.Environment y la variable current que nos indica cual es el entorno de ejecución actual. El siguiente código de ejemplo, muestra como realizar esta distinción a la hora de insertar datos en diferentes entornos.

import grails.util.Environment

class BootStrap {
  def init = { servletContext ->
    switch (Environment.current) {
      case Environment.DEVELOPMENT:
        configuracionDesarrollo()
        break
      case Environment.PRODUCTION:
        configuracionProduccion()
        break
      case Environment.TEST:
        configuracionTest()
        break
    }
  }
  def destroy = {
    switch (Environment.current) {
      case Environment.DEVELOPMENT:
        salirDesarrollo()
        break
      case Environment.PRODUCTION:
        salirProduccion()
        break
      case Environment.TEST:
        salirTest()
        break
    }
  }
}

8.1.6. El archivo UrlMappings.groovy

Otro de los archivos interesantes en la configuración de una aplicación Grails es UrlMappings.groovy. Gracias a este archivo vamos a poder definir nuevas relaciones entre las URLs y los controladores.

Por defecto, este archivo indica que el primer parámetro que sigue al nombre de la aplicación se refiere al controlador, el segundo a la acción y el tercero al identificador de la instancia de la clase de dominio que estamos tratando. Además, en reciente versiones de Grails se ha añadido un nuevo parámetro que indica el formato de la petición, por ejemplo, json o xml.

class UrlMappings {

  static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(view:"/index")
        "500"(view:'/error')
  }
}

Un ejemplo típico del uso del mapeo de URLs es modificar este comportamiento para permitir otras URLs más limpias. Por ejemplo, como administradores de la aplicación estaría bien poder ver las tareas de los usuarios a partir de su nombre de usuario, algo como http://localhost:8080/todo/usuario1.

Para ello, podemos introducir una nueva regla en el archivo UrlMappings.groovy para que la aplicación sepa como actuar cuando le llegue una petición con una URL del estilo de la comentada. La siguiente regla se encargaría de este redireccionamiento encubierto.

"/todos/$username"(controller:"todo",action:"showTodosByUser")

O bien podemos optar por una sintaxis diferente.

"/$username"{
      controller = "todo"
      action = "showtodosbyuser"
}

En primer lugar le especificamos la parte de la URL a partir del nombre de la aplicación que necesitamos que concuerde y después definimos que controlador y que acción se deben encargar de procesar esta petición. En ambas opciones tendremos acceso al valor del nombre de usuario a través de la variable params.username.

Posteriormente, deberíamos implementar un método en el controlador TodoController llamado showTodosByUser() que nos devolviera aquellos tareas que pertenezcan al usuario cuyo nombre de usuario coincida con el pasado en la url.

Otra posible utilidad del mapeo de URLs es la internacionalización de las URLs. Por ejemplo, en nuestra aplicación hemos definido las clases de dominio en inglés y por lo tanto las URLs se muestran también en inglés. Si deseamos que estas URLs se muestren también en castellano, podemos crear una serie de reglas en el archivo UrlMappings.groovy.

"/tarea/$action?/$id?(.$format)?"{
      controller = "tarea"
}

"/usuario/$action?/$id?(.$format)?"{
      controller = "user"
}

Las reglas de mapeo también permiten la introducción de restricciones que deben cumplir las partes de la URL. Por ejemplo, si quisiéramos obtener un listado con todos las tareas creadas un determinado día podríamos tener la siguiente url http://localhost:8080/todo/2015/03/28. Las restricciones que debe cumplir la URL son que el año debe ser una cifra de cuatro dígitos mientras que el mes debe estar compuesta por dos números y el día por otras dos. Para que la aplicación supiera que hacer con este tipo de direcciones debemos introducir la siguiente regla de mapeo.

"/$year/$month/$day" {
  controller = "todo"
  action = "showtodosbyday"
  constraints {
    year(matches:/\d{4}/)
    month(matches:/\d{2}/)
    day(matches:/\d{2}/)
  }
}

Otro aspecto interesante del mapeo de URLs puede ser la captura de los códigos de error que se producen en el acceso a una aplicación web, como por ejemplo el típico error 404 cuando la página solicitada no existe. En este tipo de casos, estaría bien modificar la típica pantalla de este tipo de errores, por otra en que se mostrará información sobre nuestra aplicación, como por ejemplo un mapa de todas las opciones de la aplicación.

Para controlar la información mostrada al producirse estos errores, podemos añadir lo siguiente en el archivo UrlMapping.groovy

"404"(view:'/error')

para que sea una página GSP quien se encargue de esta gestión o bien

"404"(controller:'error', action:'notFound')

para que sea un controlador quien haga este trabajo.

Por último, también es muy interesante ver como Grails es capaz de reescribir los enlaces que generamos en nuestra aplicación. Imagina por ejemplo que tenemos la siguiente regla en nuestro archivo UrlMappings.groovy

static mappings = {
   "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
}

Y que en una página GSP tenemos el siguiente código

<g:link controller="blog" action="show" params="[blog:'fred', year:2014]">
    My Blog
</g:link>

<g:link controller="blog" action="show" params="[blog:'fred', year:2014, month:10]">
My Blog - October 2014 Posts
</g:link>

Lo que Grails convertiría en lo siguiente

<a href="/fred/2014">My Blog</a>
<a href="/fred/2014/10">My Blog - October 2014 Posts</a>

8.2. Empaquetamiento de aplicaciones

Al terminar una nueva funcionalidad de una aplicación o una nueva versión de la misma, necesitamos generar el paquete WAR correspondiente a la nueva versión para desplegarla en el servidor de destino.

La generación del archivo WAR se realiza simplemente ejecutando el comando grails war, el cual nos generará un archivo con extensión .war con el nombre de la aplicación, en nuestro caso todo, seguido de la versión de la aplicación. En nuestro caso, la primera vez que ejecutemos el comando grails war el fichero generado se llamará todo-0.1.war.

Esto sería lo más básico para generar el archivo WAR de la aplicación, sin embargo, lo habitual es hacer alguna cosa más, tal y como se muestra en el siguiente listado.

  • Actualizar el código fuente del repositorio de control de versiones para asegurarse de que todas las partes del proyecto están actualizadas.

  • Ejecutar los tests de integración, unitarios y funcionales que hayamos implementado para comprobar que todo funciona tal y como esperamos.

  • Incrementar la variable app.version del archivo application.properties manualmente o bien mediante el comando grails set-version 0.2.

  • Limpiar el proyecto de archivos temporales mediante el comando grails clean.

  • Generar el archivo WAR indicándole el entorno donde queremos desplegar este WAR. Por ejemplo, el comando grails prod war crearía un archivo WAR para ser desplegado en nuestro entorno de producción.

Una aplicación Grails empaquetada como un archivo WAR puede ser desplegada en servidores de aplicaciones JAVA EE tales como JBoss (http://www.jboss.org/), GlassFish (https://glassfish.dev.java.net/), Apache Geronimo (http://geronimo.apache.org), BEA WebLogic (http://www.bea.com) o IBM WebSphere (http://www.ibm.com/software/websphere) o incluso en un contenedor web como Apache Tomcat (http://tomcat.apache.org) o Jetty (http://www.mortbay.com).

Cada uno de estos servidores o contenedores tendrán su propia especificación y forma de desplegar los archivos WAR generados. Unos mediante unos directorios especiales donde copiar los WAR, otros mediante una consola basada en web, otros por línea de comandos e incluso mediante tareas de tipo Ant. En la sección Deployment (http://www.grails.org/Deployment) de la web oficial de Grails puedes encontrar información sobre como desplegar los archivos WAR en varios servidores.

8.3. Otros comandos interesantes de Grails

Durante este módulo dedicado a Grails, han ido apareciendo varios de los comandos más habituales que utilizaremos cuando creemos aplicaciones con Grails. Sin embargo, los comandos vistos hasta ahora no son los únicos y es ahora cuando veremos algunos de ellos. Ten en cuenta también que cuando instalamos plugins, es posible que también se añadan nuevos comandos.

Si ejecutamos el comando grails help veremos un listado con todos los posibles comandos que tenemos disponibles en nuestra instalación de Grails. La siguiente tabla muestra los comandos más interesantes que no hemos visto hasta ahora.

Comando Descripción

grails bug-report

Genera un archivo comprimido en ZIP con los archivos fuente de nuestro proyecto para el caso de que queramos informar de un bug

grails clean

Limpia el directorio tmp de nuestra aplicación. Este comando puede ser combinado con otros comandos como por ejemplo grails clean run-app

grails console

Nos muestra la consola de Groovy que veíamos en la parte del módulo dedicada a este lenguaje de programación.

grails doc

Genera la documentación completa de nuestro proyecto.

grails help

Muestra un listado de comandos disponibles en Grails. Si le pasamos como parámetro uno de esos posibles comandos, nos mostrará información adicional sobre el comando dado. Por ejemplo grails help doc

grails list-plugins

Muestra un listado completo tanto de los plugins disponibles como de los ya instalados en la aplicación.

grails plugin-info

Muestra la información completa del plugin pasado como parámetro al comando.

grails run-app -https

Ejecuta el comando grails run-app utilizando como servidor Tomcat pero sobre un servidor seguro https. El puerto por defecto es 8443 y puede ser modificado añadiendo al comando -Dserver.port.https=<numero_puerto>

grails schema-export

Genera un fichero con las sentencias SQL necesarias para exportar la base de datos.

grails set-version

Establece la versión de la aplicación. Por ejemplo grails set-version 1.0.4

grails stats

Nos muestra una serie de datos referentes a nuestro proyecto, con respecto al número de controladores, clases de dominio, servicios, librerías de etiquetas, tests de integración, etc. y al número de líneas totales en cada apartado.

grails uninstall-plugin

Desinstala el plugin pasado como parámetro de la aplicación.

8.4. Plugins

La comunidad de usuarios de Grails es cada vez mayor y eso hace que, cuando una característica no ha sido desarrollada en el núcleo de Grails, sean los propios usuarios quienes se encarguen de desarrollarla como un plugin externo. En el momento en que se escribieron estos apuntes eran casi 1200 los plugins desarrollados por la comunidad (http://www.grails.org/plugins/).

Estos plugins abarcan aspectos tan diversos como la seguridad como el plugin de Spring Security (http://www.grails.org/plugin/spring-security-core), el desarrollo de interfaces gráficas ricas como Rich UI (http://www.grails.org/plugin/richui) o la exportación de datos a otros formatos con el plugin Export (http://www.grails.org/plugin/export).

Para ver el funcionamiento de los plugins en Grails, nosotros vamos a utilizar un plugin que nos permitirá buscar contenido en las clases de dominio de nuestra aplicación sin prácticamente esfuerzo alguno. También utilizaremos el plugin export para exportar la información de nuestras clases de dominio a otros formatos. Pero para empezar, veamos un plugin que nos permitirá utilizar una consola a través de nuestra aplicación para poder realizar todo tipo de operaciones.

8.4.1. Plugin console

El plugin console no es más un plugin que permite la ejecución en línea de comandos. Desde esta consola tendremos acceso a determinados contextos como pueden ser la propia aplicación o las clases de dominio. Es muy útil cuando queremos realizar operaciones sobre las clases de dominio y no podemos o no queremos tocar la aplicación para esto.

La instalación del plugin se realiza con el comando grails install-plugin console o bien editando el archivo de configuración BuildConfig.groovy para añadir el plugin correspondiente:

grails.project.dependency.resolution {
  ...
  plugins {
    ...
    compile ":console:1.5.6"
  }
}

Una vez instalado el plugin y con la aplicación en funcionamiento, podremos acceder a esta consola desde la dirección http://localhost:8080/todo/console.

Sin embargo, es probable que, debido a la configuración segura de nuestra aplicación, necesites actualizar las reglas de seguridad para que por ejemplo permita el acceso a esta url a los administradores de la misma.

grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    "/console/**":          ['ROLE_ADMIN'],
    "/plugins/console*/**": ['ROLE_ADMIN']
]

Ten en cuenta que este ejemplo considera que estamos utilizando el método de Spring Security Annotation.

Plugin Console

Con el ejemplo mostrado en la imagen podríamos obtener todos las tareas de nuestra aplicación e imprimirlos por pantalla. Pero además podemos acceder a cualquier artefacto de nuestra aplicación incluidos los servicios, tal y como si estuviéramos ejecutando este código dentro de nuestra aplicación.

Las siguientes variables son accesible desde esta consola:

  • ctx - el contexto de la aplicación Spring

  • grailsApplication - la instancia de la aplicación Grails

  • config - la configuración Grails

  • request - la petición HTTP actual

  • session - la sesión HTTP actual

El objeto ctx sin duda es el más interesante puesto que nos va a permitir acceder al contexto de la aplicación y por consiguiente a varios artefactos como pueden ser los servicios de forma muy sencilla y por ejemplo eliminar tareas directamente.

Todo todo = es.ua.expertojava.todo.Todo.get(1)

ctx.todoService.deleteTodo(todo)

Este plugin es una forma rápida de probar determinadas funcionalidades de nuestra aplicación.

8.4.2. Plugin para la búsqueda de contenido: Searchable

Para esta tarea de búsqueda, Grails cuenta con un plugin llamado Searchable que nos va a facilitar muchísimo esta labor. Este plugin además, está basado en el framework OpenSymphony Compass Search Engine (http://www.opensymphony.com/compass/) y que tiene por detrás a Apache Lucene, con lo que el plugin cuenta con mucho respaldo.

Para la instalación de plugin en Grails, debemos modificar el archivo de configuración de configuración BuildConfig.groovy y seguir las instrucciones que vienen en la página del plugin http://grails.org/plugin/searchable.

Estas instrucciones pasan por añadir un nuevo repositorio maven y añadir el plugin en cuestión.

grails.project.dependency.resolution = {
  ...

  repositories {
    ...
    mavenRepo "http://repo.grails.org/grails/core"
  }

  plugins {
    ...
    compile ":searchable:0.6.9"
  }
}

Una vez instalado, podemos comprobar que en la dirección http://localhost:8080/todo/searchable (habrá que añadir también esta dirección en la configuración segura de nuestra aplicación), tenemos un buscador que se ha generado automáticamente. Sin embargo, si probamos a hacer alguna búsqueda, el buscador nos dirá que no ha encontrada nada. Esto es porque todavía no le hemos dicho al plugin donde debe buscar la información.

Si pensamos donde podemos añadir un buscador en nuestra aplicación encontraremos que los usuarios de nuestro aplicación querrán buscar tareas, con lo que debemos indicar en la clases de dominio Todo que necesitamos que nuestro plugin searchable sea capaz de buscar en ella.

Para que el plugin searchable sea capaz de encontrar tareas que contengan un determinado texto, necesitamos modificar la clase de dominio Todo y añadirle una nueva propiedad llamada searchable. Esta propiedad es static y debe contener un valor booleano indicando si permitimos la búsqueda o no, con lo que la clase de dominio Todo quedarían así:

package es.ua.expertojava.todo

class Todo {
    String title
    String description
    Date date
    Date reminderDate
    String url
    Boolean done = false
    Category category

    Date dateCreated
    Date lastUpdated

    Date dateDone

    User user

    static searchable = true

    ...
}

Si ahora realizamos alguna búsqueda de prueba, Searchable nos devolverá los resultados encontrados en todas las propiedades de las clases de dominio Todo. Ahora bien, estamos permitiendo que se busque el término de búsqueda en todas las propiedades de las clases de dominio implicadas, algo que no siempre será lo deseado. Para ello, el plugin permite la utilización de dos variables con las que le indicaremos al sistema donde puede buscar o donde no.

Estas dos variables son only y except y podemos definirlas de la siguiente forma:

static searchable = [only: ['title', 'description']]

para indicarle que debe buscar en las propiedades title y description o bien mediante:

static searchable = [except: 'date', 'reminderDate']

si lo que queremos es indicarle sólo aquellas propiedades en las que no debemos realizar la búsqueda. En este último caso, buscaremos en todas las propiedades de la clase de dominio Todo excepto en date y reminderDate.

En el momento de escribir estos apuntes el plugin tenía un problema para funcionar con Hibernate 4 con lo que vamos a tener que actualizar un par de ficheros para utilizar en su lugar Hibernate 3. Si abrimos el archivo de configuración BuildConfig.groovy veremos como hay una línea indicando

runtime ":hibernate4:4.3.10" // or ":hibernate:3.6.10.18"

que vamos a tener que modificar para utilizar Hibernate 3. Además, en el archivo de configuración DataSource.groovy encontraremos

//cache.region.factory_class = 'org.hibernate.cache.SingletonEhCacheRegionFactory' // Hibernate 3
cache.region.factory_class = 'org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory' // Hibernate 4

y de igual forma, vamos a tener que modificar para utilizar Hibernate 3 en lugar de Hibernate 4.

Con estas modificaciones, si ahora intentamos de nuevo realizar una búsqueda en http://localhost:8080/todo/searchable veremos como ya empieza a buscar en los campos title y description de la clase de dominio Todo.

Pero si esto se nos queda corto (y posiblemente sea así), podemos utilizar el servicio SearchableService. Además, los métodos de este servicio también están disponibles en las clases de dominio "anotadas" con la variable searchable. Estos son los métodos de los que disponemos:

  • search: encuentra objetos que cumplen una determinada consulta.

  • countHits: encuentra el número de objetos que cumplen una determinada consulta.

  • moreLikeThis: encuentra objetos similares a la instancia pasada por parámetro.

  • suggestQuery: sugiere una nueva consulta a partir de la consultada pasada por parámetro.

  • termsFreqs: devuelve la frecuencia para los términos en el índice.

Un ejemplo para buscar en las tareas de nuestra aplicación podría ser el siguiente:

def searchResult = searchableService.search(
    "Tests",
    [offset: 0, max: 20]
)
println "${searchResult.total} hits:"
for (i in 0..<searchResult.results.size()) {
    println "${searchResult.offset + i + 1}: " +
        "${searchResult.results[i].toString()} " +
        "(score ${searchResult.scores[i]})"
}

o bien, también podemos buscar directamente en la clase de dominio en cuestión.

//Devuelve un único objeto de tipo Todo
//que coincidan con la búsqueda realizada
def todo = Todo.search(
    "Tests",
    [result: 'top']
)
assert todo instanceof Todo

//Devuelve todos los objetos de tipo Todo
//que coincidan con la búsqueda realizada
def todos = Todo.search(
    "Tests",
    [reload: true, result: 'every']
)
assert todos.each { it instanceof Todo }

Podemos comprobar estos ejemplos directamente en la consola vista en el plugin anterior.

En ocasiones es probable que necesitemos filtrar los resultados de una búsqueda por un determinado parámetro. Por ejemplo, en nuestra aplicación si quisiéramos buscar aquellas tareas que pertenezcan a la categoría "Hogar", deberíamos tener algo así:

def todos = Todo.search([result:'every']) {
    must(queryString(params.q))
    must(term('$/Todo/category/id',Category.findByName("Hogar")?.id))
}

Debemos también especificar que entre las propiedades a buscar en la clase de dominio Todo se encuentra la propiedad category y la clase Category también tendrá la propiedad estática searchable a cierto para que nos permitan buscar en las instancias de esta clase.

8.4.3. Plugin para exportar a otros formatos: Export

Hoy en día, cualquier aplicación que se precie debe ser capaz de exportar sus datos a otros formatos para que el usuario tenga la comodidad de elegir como quiere utilizar los datos. Los formatos más comunes a exportar la información de nuestra aplicación será pdf, hojas de cálculo excel, csv (datos separados por comas) u ods (la hoja de cálculo de OpenOffice).

Esta tarea que en otros sistemas se vuelve complicada y pesada, en Grails podemos solucionarla utilizando un plugin disponible llamado export. Como siempre, lo primero que vamos a hacer es instalarlo añadiendo la siguiente línea a nuestro archivo BuildConfig.groovy.

grails.project.dependency.resolution = {
  ...

  repositories {
    ...
    mavenRepo "http://repo.grails.org/grails/core"
  }

  dependencies {
    ...
    compile 'commons-beanutils:commons-beanutils:1.8.3'
  }
  plugins {
    ...
    compile ":export:1.7-SNAPSHOT"
  }
}

Una vez instalado el plugin export debemos añadir algunos mime types en la variable grails.mime.types del archivo Config.groovy. Los nuevos mime types serán los relativos a csv, excel, pdf y ods. La variable grails.mime.types quedaría así:

grails.mime.types = [
    all:           '*/*',
    atom:          'application/atom+xml',
    css:           'text/css',
    csv:           'text/csv',
    pdf:           'application/pdf',
    excel:         'application/vnd.ms-excel',
    ods:           'application/vnd.oasis.opendocument.spreadsheet',
    form:          'application/x-www-form-urlencoded',
    html:          ['text/html','application/xhtml+xml'],
    js:            'text/javascript',
    json:          ['application/json', 'text/json'],
    multipartForm: 'multipart/form-data',
    rss:           'application/rss+xml',
    text:          'text/plain',
    hal:           ['application/hal+json','application/hal+xml'],
    xml:           ['text/xml', 'application/xml']
]

Para empezar a utilizar el plugin, debemos incluir la etiqueta <export:resource/> en la cabecera del archivo GSP donde queramos incluir las opciones para exportar. Esto incluirá los archivos CSS necesarios para crear una barra de herramientas con las opciones para exportar la página actual.

El siguiente paso consistirá en incluir la barra de herramientas necesaria para exportar la página HTML generada a algún formato de los ya comentados. Para esto el plugin pone a nuestra disposición una nueva etiqueta <export:formats />, la cual acepta como parámetro un listado de los formatos a los que queremos exportar la página, como por ejemplo <export:formats formats="['csv','excel','ods','pdf','xml']"/>.

Si queremos añadir por ejemplo en la página que contiene el listado de las tareas una barra para exportar dicho listado, podríamos tener algo así en la página grails-app/views/todo/index.gsp.

<html>
  <head>
    ...
    <export:resource/>
  </head>
  <body>
    ...
      <h1><g:message code="default.list.label" args="[entityName]" /></h1>
      <g:if test="${flash.message}">
        <div class="message" role="status">${flash.message}</div>
      </g:if>
      <export:formats formats="['csv', 'excel', 'ods', 'pdf', 'xml']" />
    ...
  </body>
</html>

Si echamos un vistazo al listado de tareas comprobaremos como en la parte superior de los mismos aparecerá una barra con los formatos a los que podamos exportar dicho listado.

Barra para exportar a varios formatos

Sin embargo, si intentamos exportar el listado a cualquier de los formatos utilizados, veremos como no sucede nada nuevo y la aplicación vuelve a mostrarnos el listado tal y como ha aparecido siempre, es decir, en formato HTML. Para que la aplicación pueda exportar a los nuevos formatos, debemos modificar el controlador de la clase de dominio Todo y más en concreto el método index() para que acepte los nuevos formatos.

package es.ua.expertojava.todo



import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional

@Transactional(readOnly = true)
class TodoController {

    def todoService
    def springSecurityService
    def exportService

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        if(params?.f && params.f != "html"){
            response.contentType = grailsApplication.config.grails.mime.types[params.f]
            response.setHeader("Content-disposition", "attachment; filename=todos.${params.extension}")
            exportService.export(params.f, response.outputStream, Todo.findAllByUser(springSecurityService.currentUser, params), [:], [:])
        }
        respond Todo.findAllByUser(springSecurityService.currentUser, params), model:[todoInstanceCount: Todo.countByUser(springSecurityService.currentUser)]
    }
  ...
}

Ahora nuestra aplicación sí va a ser capaz de exportar a otros formatos para que el usuario final elija cual de ellos utilizar en cada ocasión. Sin embargo, si echamos un vistazo por ejemplo al formato en PDF, comprobaremos como el listado que aparece muestra todas las propiedades de la clase Todo, a excepción de la propiedad version, lo cual no va a ser aconsejable.

Para mejorar esto, el plugin permite especificar que propiedades queremos mostrar e incluso la etiqueta que queremos que aparezca en la fila de encabezados de la tabla. Para conseguir esto, necesitamos completar los dos últimos parámetros del método export() que anteriormente dejábamos vacíos. Además, también necesitaremos indicarle el formato de como deseamos las propiedades de los usuarios y un mapa de parámetros para el fichero exportado. Así quedaría el método index().

def index(Integer max) {
    params.max = Math.min(max ?: 10, 100)
    if(params?.f && params.f != "html"){
        response.contentType = grailsApplication.config.grails.mime.types[params.f]
        response.setHeader("Content-disposition", "attachment; filename=todos.${params.extension}")

        List props = ["title", "description", "date", "url", "done"]
        Map tags = ["title":"Título",
                     "description":"Descripción",
                     "date":"Fecha",
                     "url":"URL",
                     "done":"Hecho"]

        // Closure formateador
        def uppercase = { domain, value ->  return value.toUpperCase() }

        Map formatters = [title: uppercase]
        Map parameters = [title: "LISTADO DE USUARIOS"]

        exportService.export(params.f, response.outputStream,
            Todo.findAllByUser(springSecurityService.currentUser, params), props, tags,
            formatters, parameters)
    }
    respond Todo.findAllByUser(springSecurityService.currentUser, params), model:[todoInstanceCount: Todo.countByUser(springSecurityService.currentUser)]
}

Por último, comentar también que podemos cambiar los textos asociados a cada uno de los formatos que aparecen en la barra para exportar. Simplemente debemos crear nuevas entradas en el archivo grails-app/i18n/messages.properties correspondientes, tal y como aparece en el siguiente fragmento de código.

default.csv = CSV
default.excel = EXCEL
default.pdf = PDF
default.xml = XML
default.ods = ODS

8.5. Ejercicios

8.5.1. Log de acceso a controladores (0.25 puntos)

Con lo visto en esta sesión sobre la configuración de los logs y lo visto en la sesión 4 de los filtros que se pueden crear en los controladores, genera logs de tipo trace en todos los métodos de los controladores de nuestra aplicación (sin incluir los controladores login y logout del plugin spring security) para saber el usuario, controlador, método y modelo utilizados en la petición.

2015-03-28 11:13:15,863 [http-bio-8080-exec-9] TRACE todo.LogFilters  - User admin - Controlador category - Accion index - Modelo [categoryInstanceCount:2, categoryInstanceList:[Hogar, Trabajo]]

Estos logs deberán ser almacenados en un archivo llamados logs/filters.log.

8.5.2. URL limpia y pública para las tareas de los usuarios (0.50 puntos)

Crea una url del tipo /todos/$username como la vista en la parte de teoría que muestre todas las tareas del usuario pasado por parámetro en la URL. Este listado de tareas sólo podrá ser accesible por los usuarios de tipo administrador.

Modifica también el listado de usuarios que pueden ver los administradores para mostrar un enlace en cada usuario para que les permita ver este listado de tareas. Además, este enlace sólo debe ser mostrado para los usuarios que puedan tener tareas, no para los administradores.

8.5.3. Buscador de tareas integrado (0.50 puntos)

El listado de tareas pide a gritos un buscador integrado. Impleméntalo utilizando el plugin searchable y muéstralo en la parte superior del listado de las tareas. Date cuenta que las tareas están ahora asignadas a un usuario y que por lo tanto, el buscador sólo deberá buscar aquellas tareas que pertenezcan al usuario autenticado.

Estoy seguro que no hace falta que te lo recuerde, pero antes de entregar el proyecto ejecuta grails test-app unit: para comprobar que no hayas roto tus tests haciendo otras tareas.