{"id":35670,"date":"2016-07-23T18:06:41","date_gmt":"2016-07-23T12:36:41","guid":{"rendered":"http:\/\/www.tothenew.com\/blog\/?p=35670"},"modified":"2016-07-25T12:05:04","modified_gmt":"2016-07-25T06:35:04","slug":"using-hibernate-events-with-persistenceeventlistener","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/using-hibernate-events-with-persistenceeventlistener\/","title":{"rendered":"Using Hibernate Events with PersistenceEventListener"},"content":{"rendered":"<p>In my last <a title=\"blog\" href=\"http:\/\/www.tothenew.com\/blog\/hooking-into-the-instance-methods-of-the-gorm-api\/\" target=\"_blank\">blog<\/a>, we discussed how to hook into GORM API to add some common custom functionality. We will refer the same problem that we discussed in my last blog. Here is the problem statement:<\/p>\n<blockquote><p>In my grails plugin I was needed to add some fields that were common to a set of domains. For eg: for some domains we wanted to store fields like createdBy and lastUpdatedBy to keep track of users who created and last updated each record in that domain.<\/p><\/blockquote>\n<p>We also discussed that a plugin named <code>Audit Logging Plugin<\/code> already exist for the same purpose. The reason behind not using the audit plugin is that the updates made by this plugin to update createdBy and lastUpdatedBy fields is done via a separate query. So how does this affects us? If we have auto versioning enabled in our <a title=\"grails development\" href=\"http:\/\/www.tothenew.com\/grails-application-development\">Grails application<\/a>, then this will cause increment in version of the respective domain object. And if you are doing some operation in a separate thread upon insertion\/updation of the domain objects then you might encounter <code>StaleObjectStateException<\/code>. In our case, on each insert\/update we were sending a message to Rabbit MQ queue which was being consumed by a separate application.<\/p>\n<p><strong>So how to handle this:<\/strong><br \/>\nSo instead of using the default grails events we used hibernate events to update the stamping fields. Here is the code to do this:<\/p>\n<p>[code lang=&#8221;groovy&#8221;]<br \/>\npackage com.verecloud.nimbus4.listener<\/p>\n<p>import groovy.util.logging.Commons<br \/>\nimport org.apache.commons.lang.ArrayUtils<br \/>\nimport org.grails.datastore.mapping.core.Datastore<br \/>\nimport org.grails.datastore.mapping.engine.event.*<br \/>\nimport org.hibernate.event.PreInsertEvent as HibernatePreInsertEvent<br \/>\nimport org.hibernate.event.PreUpdateEvent as HibernatePreUpdateEvent<br \/>\nimport org.hibernate.persister.entity.EntityPersister<br \/>\nimport org.springframework.context.ApplicationEvent<\/p>\n<p>@Commons<br \/>\nclass CustomPersistenceEventListenerImpl extends AbstractPersistenceEventListener {<\/p>\n<p>    \/\/Name of Stamp fields<br \/>\n    String stampCreatedBy = &quot;createdBy&quot;<br \/>\n    String stampLastUpdatedBy = &quot;lastUpdatedBy&quot;<\/p>\n<p>    CustomPersistenceEventListenerImpl(Datastore datastore) {<br \/>\n        super(datastore)<br \/>\n    }<\/p>\n<p>    @Override<br \/>\n    protected void onPersistenceEvent(AbstractPersistenceEvent event) {<br \/>\n        if (event.source != this.datastore) {<br \/>\n            log.trace(&quot;Event received for other datastore. Ignoring event&quot;)<br \/>\n            return<br \/>\n        }<br \/>\n        \/\/Do stamping<br \/>\n        \/\/event.nativeEvent will give us the actual HibernateEvent<br \/>\n        stamp(event.nativeEvent, event.eventType)<br \/>\n    }<\/p>\n<p>    @Override<br \/>\n    \/\/This listener will listen PreInsert and PreUpdate events only.<br \/>\n    boolean supportsEventType(Class&lt;? extends ApplicationEvent&gt; eventType) {<br \/>\n        return eventType.isAssignableFrom(PreInsertEvent) ||<br \/>\n                eventType.isAssignableFrom(PreUpdateEvent)<br \/>\n    }<\/p>\n<p>    private stamp(Serializable event, EventType eventType) {<br \/>\n        if (EventType.PreInsert == eventType) {<br \/>\n            HibernatePreInsertEvent preInsertEvent = event as HibernatePreInsertEvent<\/p>\n<p>            stampCreatedBy(preInsertEvent.entity, preInsertEvent.persister, preInsertEvent.state)<br \/>\n            stampLastUpdatedBy(preInsertEvent.entity, preInsertEvent.persister, preInsertEvent.state)<\/p>\n<p>        } else if (EventType.PreUpdate == eventType) {<br \/>\n            \/\/On update we need to update lastUpdatedBy field only<br \/>\n            HibernatePreUpdateEvent preUpdateEvent = event as HibernatePreUpdateEvent<br \/>\n            stampLastUpdatedBy(preUpdateEvent.entity, preUpdateEvent.persister, preUpdateEvent.state)<br \/>\n        }<br \/>\n    }<\/p>\n<p>    private void stampCreatedBy(entity, EntityPersister persister, Object[] state) {<br \/>\n        if (entity.getProperty(stampCreatedBy) == null) {<br \/>\n            String currentUser = getActor()<\/p>\n<p>            String[] propertyNames = persister.entityMetamodel.propertyNames<\/p>\n<p>            \/\/ inserts<br \/>\n            entity.setProperty(stampCreatedBy, currentUser)<br \/>\n            setValue(state, propertyNames, stampCreatedBy, currentUser, entity)<br \/>\n        }<br \/>\n    }<\/p>\n<p>    private void stampLastUpdatedBy(entity, EntityPersister persister, Object[] state) {<br \/>\n        String currentUser = getActor()<br \/>\n        String[] propertyNames = persister.entityMetamodel.propertyNames<\/p>\n<p>        \/\/ inserts<br \/>\n        setValue(state, propertyNames, stampCreatedBy, entity.getProperty(stampCreatedBy), entity)<\/p>\n<p>        \/\/ updates<br \/>\n        entity.setProperty(stampLastUpdatedBy, currentUser)<br \/>\n        setValue(state, propertyNames, stampLastUpdatedBy, currentUser, entity)<br \/>\n    }<\/p>\n<p>    \/\/Using this method we can directly set a value for a property which will update in the same query<br \/>\n    private void setValue(Object[] state, String[] properties, String propertyToSet, Object value, Object entity) {<br \/>\n        int index = ArrayUtils.indexOf(properties, propertyToSet)<br \/>\n        if (index &gt;= 0) {<br \/>\n            state[index] = value<br \/>\n        } else {<br \/>\n            log.error(&quot;Field &#8216;&quot; + propertyToSet + &quot;&#8217; not found on entity &#8216;&quot; + entity.getClass().getName() + &quot;&#8217;.&quot;)<br \/>\n        }<br \/>\n    }<\/p>\n<p>    private String getActor() {<br \/>\n        String actor = &quot;Sandeep Poonia&quot; \/\/fetch the user who is currently logged in<br \/>\n        return actor<br \/>\n    }<br \/>\n}<br \/>\n[\/code]<\/p>\n<p>And to register the listener from the plugin do:<\/p>\n<p>[code lang=&#8221;groovy&#8221;]<br \/>\ndef doWithApplicationContext = { ctx -&gt;<br \/>\n\tregisterAuditLogListener(ctx, application)<br \/>\n}<\/p>\n<p>private void registerAuditLogListener(AbstractApplicationContext applicationContext, GrailsApplication application) {<br \/>\n    application.mainContext.eventTriggeringInterceptor.datastores.each { key, datastore -&gt;<br \/>\n        CustomPersistenceEventListenerImpl listener = new CustomPersistenceEventListenerImpl(datastore)<br \/>\n        applicationContext.addApplicationListener(listener)<br \/>\n    }<br \/>\n}<br \/>\n[\/code]<\/p>\n<p>Another limitation with <code>Audit Logging Plugin<\/code> is that it works on PreUpdate and not on PostUpdate which may be required in some cases.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my last blog, we discussed how to hook into GORM API to add some common custom functionality. We will refer the same problem that we discussed in my last blog. Here is the problem statement: In my grails plugin I was needed to add some fields that were common to a set of domains. [&hellip;]<\/p>\n","protected":false},"author":182,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":11},"categories":[7,1],"tags":[1711,3481,29,4840,3480],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/35670"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/182"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=35670"}],"version-history":[{"count":0,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/35670\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=35670"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=35670"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=35670"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}