Experto en desarrollo de aplicaciones web con JavaEE y Javascript

Framework Grails

Sesión 8: Configuración de aplicaciones. Plugins interesantes

Índice

  • Configuración de aplicaciones
  • Empaquetamiento de aplicaciones
  • Otros comandos interesantes
  • Plugins

Configuración de aplicaciones

  • El archivo Config.groovy
  • Sistema de logs
  • El archivo BuildConfig.groovy
  • El archivo DataSource.groovy
  • El archivo Bootstrap.groovy
  • El archivo UrlMappings.groovy

El archivo Config.groovy

  • grails.config.locations
  • grails.project.groupId
  • grails.views.default.codec
  • grails.controllers.defaultScope
  • grails.views.gsp
  • grails.converters.encoding
  • grails.scaffolding.templates.domainSuffix
  • grails.mime.types

Sistema de logs

				
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'
}
				
			

Niveles de log

  • off
  • fatal
  • error
  • warn
  • info
  • debug
  • trace
  • all

Artefactos de Grails

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

Tipos de artefactos

  • conf
  • filters
  • taglib
  • services
  • controllers
  • domain

Ejemplo

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

Ejemplo

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

Ejemplo

				
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()]
}
				
			

Ejemplo

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

El archivo BuildConfig.groovy

  • grails.servlet.version
  • grails.project.class.dir
  • grails.project.test.class.dir
  • grails.project.test.reports.dir
  • grails.project.target.level
  • grails.project.war.file
  • grails.project.fork
  • grails.project.dependency.resolution
  • grails.project.dependency.resolution.plugins

El archivo DataSource.groovy

  • 3 entornos: desarrollo, test y producción
  • Configuración diferentes para cada entorno

DataSource.groovy

				
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
            }
        }
    }
}
				
			

Arranque por entorno

  • Desarrollo: grails dev run-app o grails run-app
  • Test: grails test run-app
  • Producción: grails prod run-app

Mysql

				
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
            }
        }
    }
				
			

Driver Mysql

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

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

Opciones dbCreate

  • create-drop
  • create
  • update
  • validate
  • otro valor

El archivo Bootstrap.groovy

  • Ahorra tiempo
  • init()
  • destroy()
  • Diferentes entornos

Driver Mysql

				
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
    }
  }
}
				
			

El archivo UrlMappings.groovy

				
class UrlMappings {

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

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

Tareas por usuario

				
"/todos/$username"(controller:"todo",action:"showTodosByUser")
				
			
				
"/$username"{
      controller = "todo"
      action = "showtodosbyuser"
}
				
			

Internacionalización

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

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

Restricciones

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

Códigos de error

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

Reescritura de URLs

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

Reescritura de URLs

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

Reescritura de URLs

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

Empaquetamiento de aplicaciones

  • Actualizar repositorio
  • Tests
  • Incrementar version app.version
  • grails clean
  • Generar war grails prod war

Servidores de aplicaciones

  • JBoss
  • GlassFish
  • Apache Geronimo
  • BEA WebLogic
  • IBM WebSphere
  • Apache Tomcat
  • Jetty

Otros comandos interesantes

  • grails bug-report
  • grails clean
  • grails console
  • grails doc
  • grails help
  • grails list-plugins
  • grails plugin-info
  • grails run-app -https
  • grails schema-export
  • grails set-version
  • grails stats
  • grails uninstall-plugin

Plugins

  • Plugin console
  • Plugin Searchable
  • Plugin export

Plugin console

  • Ejecución en línea de comandos
  • Acceso a contextos: aplicación o clases de dominio

Instalación plugin console

				
grails install-plugin console
				
			
				
grails.project.dependency.resolution {
  ...
  plugins {
    ...
    compile ":console:1.5.4"
  }
}
				
			

A tener en cuenta

  • http://localhost:8080/todo/console
  • Aplicación asegurada

Acceso al plugin console

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

Interfaz plugin console

Variables en el plugin console

  • ctx
  • grailsApplication
  • config
  • request
  • session

Ejemplos de uso del plugin console

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

ctx.todoService.deleteTodo(todo)
				
			

Plugin Searchable

  • Tareas de búsqueda
  • Basado en Compass
  • Utiliza Apache Lucene

Instalación plugin searchable

				
grails.project.dependency.resolution = {
  ...
  
  repositories {
    ...
    mavenRepo "http://repo.grails.org/grails/core"
  }

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

Buscador generado

  • http://localhost:8080/todo/searchable

Buscador de tareas

				
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

    ...
}
				
			

Restringir la búsqueda

				
static searchable = [only: ['title', 'description']]
				
			
				
static searchable = [except: 'date', 'reminderDate']
				
			

Problemas con Hibernate 4

				
runtime ":hibernate4:4.3.5.5" // or ":hibernate:3.6.10.17"
				
			
				
//cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3
cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4
				
			

SearchableService

  • search, https://grails.org/Searchable+Plugin+-\+Methods+-+search
  • countHits, https://grails.org/Searchable+Plugin+-\+Methods+-+countHits
  • moreLikeThis, https://grails.org/Searchable+Plugin+-\+Methods+-+moreLikeThis
  • suggestQuery, https://grails.org/Searchable+Plugin+-\+Methods+-+suggestQuery
  • termsFreqs, https://grails.org/Searchable+Plugin+-\+Methods+-+termFreqs

Ejemplo con SearchableService

				
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]})"
}
				
			

Ejemplo con SearchableService

				
//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 }
				
			

Filtrar por parámetro

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

Plugin export

  • Exportar a otros formatos: excel, pdf, csv, etc.

Instalación plugin export

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

Configuración plugin export

				
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']
]
				
			

Uso del plugin export

  • Incluir etiqueta <export:resource/>
  • Incluir etiqueta <export:formats />
  • Especificar que formatos queremos <export:formats formats="['csv','excel','ods','pdf','xml']"/>

Ejemplo del plugin export

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

Interfaz de usuario del plugin export

Cambios en el controlador

				
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)]
    }
  ...
}
				
			

Mejoras en la forma de exportar

				
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)]
}
				
			

Internacionalizar el plugin export

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

¿Preguntas...?

Ejercicios