About Me

Mein Bild
Freelancing software developer / architect, mobile geek. Topics: Software technologies (java, groovy, agile), politics, web, science, green, mobile, android.
Homepage: www.tutaona.com

"The absence of limitations is the enemy of art." - Orson Welles
Posts mit dem Label grails werden angezeigt. Alle Posts anzeigen
Posts mit dem Label grails werden angezeigt. Alle Posts anzeigen

Mittwoch, 27. Juni 2012

Logging Config for Grails 2.0

Finally I developed a good logging config. It debug logs your classes to development.log, grails stuff info logging to grails.log. Test logging goes into test.log and for production it logs nothing to the console (= catalina.out) and info logging to a daily rolling logfile.
import org.apache.log4j.*
environments {
  development {
    log4j = {
      appenders {
        file name: 'grailsfile', file: 'target/grails.log', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
        file name: 'rootlog', file: 'target/root.log', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
        file name: 'devfile', file: 'target/development.log', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
      }
      root { error 'stdout', 'rootlog' }
      info additivity: false, grailsfile: 'org.codehaus.groovy.grails, com.orientechnologies'//.commons'
      all additivity: false, devfile: [
        'grails.app.controllers.my.package',
        'grails.app.domain.my.package',
        'grails.app.services.my.package',
        'grails.app.taglib.my.package',
        'grails.app.conf.my.package',
        'grails.app.filters.my.package',
        'my.package'
      ]
    }
  }

  test {
    log4j = {
      appenders {
        file name: 'grailsfile', file: 'target/grails.log', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
        file name: 'rootlog', file: 'target/root.log', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
        file name: 'testfile', file: 'target/test.log', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
      }
      root { error 'stdout', 'rootlog' }
      info additivity: false, grailsfile: 'org.codehaus.groovy.grails'
      all additivity: false, testfile: [
        'grails.app.controllers.my.package',
        'grails.app.domain.my.package',
        'grails.app.services.my.package',
        'grails.app.taglib.my.package',
        'grails.app.conf.my.package',
        'grails.app.filters.my.package',
        'my.package'
      ]
    }
  }

  production {
    grails.logging.jul.usebridge = false
    log4j = {
      appenders {
        console name: 'console', layout: pattern(conversionPattern: "[%d{ISO8601}]\t%p\t%c{2}\t%m%n")
        appender new org.apache.log4j.DailyRollingFileAppender(
          name:"logfile", fileName:"./my.application.log",
          layout:pattern(conversionPattern: '[%d{ISO8601}]\t%p\t%c{2}\t%m%n')
        )
      }
      // set this to debug if debugging on a dev server
      info 'grails.app.controllers.my.package',
        'grails.app.domain.my.package',
        'grails.app.services.my.package',
        'grails.app.taglib.my.package',
        'grails.app.conf.my.package',
        'grails.app.filters.my.package',
        'my.package'
     
      info 'org.codehaus.groovy.grails.commons', 'com.orientechnologies'

      root {
        error 'logfile'
        additivity = true
      }
    }
  }
}

Montag, 25. Juni 2012

Access Grails Config in Non-DI Classes

ApplicationContext context = servletContextHolder.servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT) as ApplicationContext
ConfigObject grailsConfig = context.getBean(GrailsApplication).config

Freitag, 22. Juni 2012

Using SQL Views With Grails

The Problem
In a recent grails backend project for a conference management system I had a complex synchronization where the initial sync could be quite large. As the clients were iPads and could have connection and bandwidth constraints so the huge initial sync must happen in chunks. So if a client due to connectivity problems fails to receive a chunk it can request it again and not too much of data has to be transferred again. As the iPad knows about it's connection quality the app could even adjust the chunk size to its needs. Large chunks (causing less overhead) when the connection is good and smaller chunks (less data to resend) when the connection is poor. The idea was to use SQL views to create a snapshot of the database and use HQL queries against the views with all it's paging convenience for simple chunk handling code.

The problem was that GORM has no support for views. If you query a view GORM cannot map it to anything as there is no mapping defined. Moreover as the views must have different names for each sync and GORM's mapping of domain classes to tables is defined static but I needed the mapping to be dynamic. That was a hard one to solve.

Creating SQL Views in Grails
That was rather something simple as groovy has support for SQL. So I just take a client ID to make the view's name unique.
The business logic (controller/service)
def dataSource // the Spring-Bean "dataSource" is auto-injected

@Secured([Role.ROLE_RECEPTIONIST])
def syncUsers = {
    def clientId = params.client
    long syncpoint = params.syncpoint ? params.syncpoint : 0) as Long)

    def roleAttendee = Role.findByAuthority(Role.ROLE_ATTENDEE)

    def db = new Sql(dataSource)
    def query = "CREATE VIEW syncview_user_"+clientId+" \
        AS SELECT u.* FROM user AS u, user_role AS ur \
        WHERE ur.user_id = u.id AND ur.role_id = $roleAttendee.id"
    db.execute(query)
}   
The hibernate interceptor (src/java)
import org.hibernate.EmptyInterceptor
import org.springframework.web.context.request.RequestContextHolder

public class DynamicTableNameInterceptor extends EmptyInterceptor {
 
 public String onPrepareStatement(String sql) {
  if(RequestContextHolder.getRequestAttributes()) {
   def params = RequestContextHolder.currentRequestAttributes().params
   println ">>>> $sql"
   println params
   //params.each { println it}
   sql = sql.replaceAll("from user ", "from syncview_user_"+params.client+" ")
   println sql
   println "------"
  }
  return sql //.replaceAll('_SCHEMA_', schema)
 }
}
resources.groovy:
beans = {
 entityInterceptor(DynamicTableNameInterceptor)
}

List Test-App Execution Order

Neat little bash magic


function grailsTestOrder() {
    grep testsuite target/test-reports/TESTS-TestSuites.xml | grep -v testsuites | cut -d\  -f8-9 | sed -E 's/name="(.*)" package="(.*)"/\2.\1/' | grep .
}
 
alias testorder=grailsTestOrder

Donnerstag, 10. Mai 2012

Simple Webservices Client

import grails.converters.*;
def url = new URL("http://www.your-needed-webservice-with-a-json-response.com")
def response = JSON.parse(url.newReader())

Executing a SQL Script on Startup

import groovy.sql.Sql
import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH

String sqlFilePath = ApplicationHolder.application.parentContext.servletContext.getRealPath("/data/table_dump.sql")
String sqlString = new File(sqlFilePath).text
Sql sql = Sql.newInstance(CH.config.dataSource.url, CH.config.dataSource.username, CH.config.dataSource.password, CH.config.dataSource.driverClassName)
sql.execute(sqlString)

Montag, 14. November 2011

Grails logging how I like it

I was fiddling with grails logging for a long time and I was doing it again and again and again... not very DRY. So here is how it's good for me for most projects. To the console are errors only, in dev mode a simple log file at debug level and in production a daily rolling log file at info level.

// log4j configuration
log4j = {
appenders {
console name:'console', threshold:org.apache.log4j.Level.ERROR
file  name:'file', file: './myapp.log', threshold: org.apache.log4j.Level.DEBUG, layout: pattern(conversionPattern: "%d{ISO8601}\t%p\t%c:%L\t%m%n")
}
environments {
production {
appenders {
console name:'console', threshold: org.apache.log4j.Level.ERROR
appender new org.apache.log4j.DailyRollingFileAppender(name:"file", fileName:"/var/log/myapp.log",
datePattern: '\'_\'yyyy-MM-dd', layout:pattern(conversionPattern: '%d{ISO8601}\t%p\t%c:%L\t%m%n'),
threshold:org.apache.log4j.Level.INFO)
}
}
}

// trace "org.hibernate.SQL",
// "org.hibernate.type"

debug 'grails.app.service',
'grails.app.controller'
'grails.app.tagLib'

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',
'org.apache.commons.digester',
'net.sf.ehcache.hibernate',
'org.apache.tomcat',
'org.apache.catalina',
'net.sf.ehcache',
'org.apache.coyote',
'org.apache.commons.beanutils'

warn   'org.mortbay.log'

root {
debug 'console','file'
additivity = false
}
}

Montag, 22. August 2011

Grails jquery autocompleter dialog

<script>
function show_scenarioOpenDialog() {
    $('#openScenarioDialog').dialog('open');
    return false;
}
$(function() {
    $('#openScenarioDialog').dialog({
        autoOpen: false,
        modal: true,
        width: 600,
        buttons: {
            <g:if test="${!isReadOnly}">
            '${message(code:"button.form.submit")}': function() {
                $('#openScenarioForm').submit();
            },
            </g:if>
        '${message(code:"button.form.cancel")}': function() {
$( this ).dialog( "close" ); // der bleibt tatsaechlich erhalten
}
        }
    });
$("#scenarioName").autocomplete({
        source: function(request, response) {
            $.ajax({
            url: "${createLink(base: '.', action: 'findByNameLike', controller: 'scenario')}",
                dataType: "json",
                data: {
                    name: request.term
                },
                success: function(data) {
                    response($.map(data, function(item) {
                        return {
                            label: item.name,
                            value: item.name,
                            title: item.uid
                        }
                    }))
                }
            })
        },
        minLength: 1,
        select: function(event, ui) {
$("#scenarioUid").val(ui.item.title);
        }
});
});
</script>
<div id="openScenarioDialog" class="popupForm" title="Open Scenario">
<g:form target="_top" base="." action="openScenarioSubmit" controller="workspace" name="openScenarioForm">
<div class="search form-row ui-widget">
    <label for="scenarioName">Scenario Name: </label>
        <input type="text" id="scenarioName" name="scenarioName" />
        <input type="hidden" id="scenarioUid" name="scenarioUid"/>
    </div>
</g:form>
</div>