Ankur Tripathi « Intelligrape Groovy & Grails Blogs
Subscribe via E-Mail:

Ankur

Posts by Ankur:

  • Extending Audit Logging Plugin to track changes to Persistent Collections

    23 Jan 2012 in Grails& Plugin

    In one of our project we needed to maintain history of domain objects when they are updated. We saw Grails Audit Logging Plugin as a good candidate. But later, found that it doesn’t take care of persistent collections. So with help of my colleague Vivek and this Stack Overflow thread, we extended this plugin without making it inline, to handle this limitation.

    Audit Logging plugin provides a bean named auditLogListener to handle Hibernate events and provide handlers in Grails Domain classes with old values map and new values map. So what we have to do is create a class named CustomAuditLogListener which extends AuditLogListener from the plugin and overrides the onPostUpdate() method. Implemetation for this class is:

    import org.codehaus.groovy.grails.plugins.orm.auditable.AuditLogListener
    import org.hibernate.collection.PersistentCollection
    import org.hibernate.engine.CollectionEntry
    import org.hibernate.engine.PersistenceContext
    import org.hibernate.event.PostUpdateEvent
    
    class CustomAuditLogListener extends AuditLogListener {
    
        @Override
        void onPostUpdate(final PostUpdateEvent event) {
            if (isAuditableEntity(event)) {
                log.trace "${event.getClass()} onChange handler has been called"
                onChange(event)
            }
        }
    
        private void onChange(final PostUpdateEvent event) {
            def entity = event.getEntity()
            String entityName = entity.getClass().getName()
            def entityId = event.getId()
    
            // object arrays representing the old and new state
            def oldState = event.getOldState()
            def newState = event.getState()
    
            List<String> propertyNames = event.getPersister().getPropertyNames()
            Map oldMap = [:]
            Map newMap = [:]
    
            if (propertyNames) {
                for (int index = 0; index < newState.length; index++) {
                    if (propertyNames[index]) {
                        if (oldState) {
                            populateOldStateMap(oldState, oldMap, propertyNames[index], index)
                        }
                        if (newState) {
                            newMap[propertyNames[index]] = newState[index]
                        }
                    }
                }
            }
    
            if (!significantChange(entity, oldMap, newMap)) {
                return
            }
    
            // allow user's to over-ride whether you do auditing for them.
            if (!callHandlersOnly(event.getEntity())) {
                logChanges(newMap, oldMap, event, entityId, 'UPDATE', entityName)
            }
             executeHandler(event, 'onChange', oldMap, newMap)
            return
        }
    
        private populateOldStateMap(def oldState, Map oldMap, String keyName, index) {
            def oldPropertyState = oldState[index]
            if (oldPropertyState instanceof PersistentCollection) {
                PersistentCollection pc = (PersistentCollection) oldPropertyState;
                PersistenceContext context = sessionFactory.getCurrentSession().getPersistenceContext();
                CollectionEntry entry = context.getCollectionEntry(pc);
                Object snapshot = entry.getSnapshot();
                if (pc instanceof List) {
                    oldMap[keyName] = Collections.unmodifiableList((List) snapshot);
                }
                else if (pc instanceof Map) {
                    oldMap[keyName] = Collections.unmodifiableMap((Map) snapshot);
                }
                else if (pc instanceof Set) {
                    //Set snapshot is actually stored as a Map
                    Map snapshotMap = (Map) snapshot;
                    oldMap[keyName] = Collections.unmodifiableSet(new HashSet(snapshotMap.values()));
                }
                else {
                    oldMap[keyName] = pc;
                }
            } else {
                oldMap[keyName] = oldPropertyState
            }
        }
    }
    

    Now we need to register CustomAuditLogListener class as implementation for auditLogListener which will be done in resources.groovy. The bean has to be defined in resources.groovy as:

     auditLogListener(CustomAuditLogListener) {
            sessionFactory   = ref('sessionFactory')
            verbose          = application.config?.auditLog?.verbose?:false
            transactional    = application.config?.auditLog?.transactional?:false
            sessionAttribute = application.config?.auditLog?.sessionAttribute?:""
            actorKey         = application.config?.auditLog?.actorKey?:""
        }
    

    Now we will be able to fetch older values for persistent collections in onChange handler as documented in plugin documentation.

    Hope you find this helpful.

    Ankur Tripathi
    ankur@intelligrape.com

    • Share/Bookmark
  • Specifying default package for all Grails artefacts

    20 Jul 2011 in Grails

    Hi All,

    Today i found how to specify default package for all Grails artefacts, would like to share with you.
    By default when we create any domain, controller or any other artefact, It takes application name as default package. If you want to change that just define grails.project.groupId property in your Config.groovy.

     grails.project.groupId = "your.package.name"

    Looked into grails documentation but didn’t find it there, after some search found this jira issue. Seems this was some how missed in the documentation.

    Cheers,
    Ankur
    ankur@intelligrape.com

    • Share/Bookmark
  • New way To Configure a RuleSet for Static Groovy Code analysis using CodeNarc plugin

    08 Mar 2011 in Grails

    The CodeNarc plugin provides static code analysis using CodeNarc library. CodeNarc analyzes Groovy code for defects, bad practices, inconsistencies, style issues etc. CodeNarc provides a Groovy DSL for defining RuleSets.

    Install CodeNarc plugin into you grails project. Now create one groovy file say CustomRules.groovy and add following code into it.

    
    ruleset {
    	Println
    	SystemOutPrint //these are the rules from CodeNarc library
    }
    

    The above configuration will look for Println and SystemOutPrint (Logging Rules) rule violation in your project. see all rules.

    Now add location of this file in Config.properties

    codenarc.ruleSetFiles="file:grails-app/conf/CustomRules.groovy"
    

    and you are done with configuring custom rule, Now you can run codenarc for configured rule.


    Hope this helps
    Ankur
    ankur@intelligrape.com

    • Share/Bookmark
  • Multiple Variable Assignment in Groovy

    14 Dec 2010 in Groovy

    In one of my project, I was in need to return multiple variables from a method. I searched and found very good Groovy way for ‘Multiple Assignment’, This allows us to assign multiple variables at once.

    def (str1,str2,str3) = ['Groovy','and','Grails']
    assert str1 == 'Groovy' && str2 == 'and' && str3 == 'Grails'

    We can have types as part of the declaration

    def (int a,String b) = [10,'someString']
    

    With method calls

    def someMethod(){
    [5,'Hello']
    }
    def (int a,String b) = someMethod()
    

    For more information see Multiple Assignments.

    Ankur
    ankur@intelligrape.com

    • Share/Bookmark
  • Creating custom codec in Grails

    13 Nov 2010 in Grails

    Grails applications can define their own codecs and Grails will load them along with the standard codecs. A custom codec class must be defined in the grails-app/utils/ directory and the class name must end with ‘Codec’. For more information see Dynamic Encoding Methods.

    
    import java.security.MessageDigest
    import sun.misc.BASE64Encoder
    
    class PasswordCodec {
    
    static encode = { str ->
    MessageDigest md = MessageDigest.getInstance('SHA')
    md.update(str.getBytes('UTF-8'))
    return (new BASE64Encoder()).encode(md.digest())
    }
    
    }
    

    Using this custom codec

    
    String pwd = "12345"
    println pwd.encodeAsPassword()  // Output : jLIjfQZ5yojbZGTqxg2pY0VROWQ=
    

    Hope this helps
    Ankur
    ankur@intelligrape.com

    • Share/Bookmark