1 /* 2 * Copyright 2003-2007 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.codehaus.groovy.grails.orm.hibernate.support; 17 18 import grails.validation.ValidationException; 19 import groovy.lang.Closure; 20 import groovy.lang.GroovySystem; 21 import groovy.lang.MetaClass; 22 import groovy.lang.MetaMethod; 23 import groovy.lang.MetaProperty; 24 25 import java.lang.reflect.Field; 26 import java.lang.reflect.Method; 27 import java.lang.reflect.Modifier; 28 import java.util.HashMap; 29 import java.util.List; 30 import java.util.Map; 31 32 import org.apache.commons.lang.ArrayUtils; 33 import org.apache.commons.logging.Log; 34 import org.apache.commons.logging.LogFactory; 35 import org.codehaus.groovy.grails.commons.GrailsClassUtils; 36 import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty; 37 import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinder; 38 import org.codehaus.groovy.grails.orm.hibernate.cfg.Mapping; 39 import org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod; 40 import org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod; 41 import org.codehaus.groovy.grails.orm.hibernate.metaclass.ValidatePersistentMethod; 42 import org.codehaus.groovy.runtime.DefaultGroovyMethods; 43 import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; 44 import org.hibernate.EntityMode; 45 import org.hibernate.HibernateException; 46 import org.hibernate.engine.EntityEntry; 47 import org.hibernate.event.PostDeleteEvent; 48 import org.hibernate.event.PostDeleteEventListener; 49 import org.hibernate.event.PostInsertEvent; 50 import org.hibernate.event.PostInsertEventListener; 51 import org.hibernate.event.PostLoadEvent; 52 import org.hibernate.event.PostLoadEventListener; 53 import org.hibernate.event.PostUpdateEvent; 54 import org.hibernate.event.PostUpdateEventListener; 55 import org.hibernate.event.PreDeleteEvent; 56 import org.hibernate.event.PreDeleteEventListener; 57 import org.hibernate.event.PreLoadEvent; 58 import org.hibernate.event.PreLoadEventListener; 59 import org.hibernate.event.PreUpdateEvent; 60 import org.hibernate.event.PreUpdateEventListener; 61 import org.hibernate.event.SaveOrUpdateEvent; 62 import org.hibernate.event.SaveOrUpdateEventListener; 63 import org.hibernate.persister.entity.EntityPersister; 64 import org.springframework.util.ReflectionUtils; 65 import org.springframework.validation.Errors; 66 67 /** 68 * <p>Invokes closure events on domain entities such as beforeInsert, beforeUpdate and beforeDelete. 69 * 70 * <p>Also deals with auto time stamping of domain classes that have properties named 'lastUpdated' and/or 'dateCreated'. 71 * 72 * @author Lari Hotari 73 * @since 1.3.5 74 */ 75 public class ClosureEventListener implements SaveOrUpdateEventListener, PreLoadEventListener, PostLoadEventListener, 76 PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener, PreDeleteEventListener, 77 PreUpdateEventListener { 78 private static final long serialVersionUID = 1L; 79 private static final Log log = LogFactory.getLog(ClosureEventListener.class); 80 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[] {}; 81 82 EventTriggerCaller saveOrUpdateCaller; 83 EventTriggerCaller beforeInsertCaller; 84 EventTriggerCaller preLoadEventCaller; 85 EventTriggerCaller postLoadEventListener; 86 EventTriggerCaller postInsertEventListener; 87 EventTriggerCaller postUpdateEventListener; 88 EventTriggerCaller postDeleteEventListener; 89 EventTriggerCaller preDeleteEventListener; 90 EventTriggerCaller preUpdateEventListener; 91 boolean shouldTimestamp = false; 92 MetaProperty dateCreatedProperty; 93 MetaProperty lastUpdatedProperty; 94 MetaClass domainMetaClass; 95 boolean failOnErrorEnabled = false; 96 MetaProperty errorsProperty; 97 @SuppressWarnings("unchecked") 98 Map validateParams; 99 MetaMethod validateMethod; 100 101 @SuppressWarnings("unchecked") ClosureEventListener(Class<?> domainClazz, boolean failOnError, List failOnErrorPackages)102 public ClosureEventListener(Class<?> domainClazz, boolean failOnError, List failOnErrorPackages) { 103 initialize(domainClazz, failOnError, failOnErrorPackages); 104 } 105 106 @SuppressWarnings("unchecked") initialize(Class<?> domainClazz, boolean failOnError, List failOnErrorPackages)107 private void initialize(Class<?> domainClazz, boolean failOnError, List failOnErrorPackages) { 108 domainMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(domainClazz); 109 dateCreatedProperty = domainMetaClass.getMetaProperty(GrailsDomainClassProperty.DATE_CREATED); 110 lastUpdatedProperty = domainMetaClass.getMetaProperty(GrailsDomainClassProperty.LAST_UPDATED); 111 if (dateCreatedProperty != null || lastUpdatedProperty != null) { 112 Mapping m = GrailsDomainBinder.getMapping(domainClazz); 113 shouldTimestamp = (m != null && !m.isAutoTimestamp()) ? false : true; 114 } 115 116 saveOrUpdateCaller = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.ONLOAD_SAVE); 117 beforeInsertCaller = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.BEFORE_INSERT_EVENT); 118 preLoadEventCaller = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.ONLOAD_EVENT); 119 if (preLoadEventCaller == null) { 120 preLoadEventCaller = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.BEFORE_LOAD_EVENT); 121 } 122 postLoadEventListener = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.AFTER_LOAD_EVENT); 123 postInsertEventListener = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.AFTER_INSERT_EVENT); 124 postUpdateEventListener = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.AFTER_UPDATE_EVENT); 125 postDeleteEventListener = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.AFTER_DELETE_EVENT); 126 preDeleteEventListener = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.BEFORE_DELETE_EVENT); 127 preUpdateEventListener = buildCaller(domainClazz, ClosureEventTriggeringInterceptor.BEFORE_UPDATE_EVENT); 128 129 if(failOnErrorPackages.size() > 0) { 130 failOnErrorEnabled = GrailsClassUtils.isClassBelowPackage(domainClazz, failOnErrorPackages); 131 } else { 132 failOnErrorEnabled = failOnError; 133 } 134 135 validateParams = new HashMap(); 136 validateParams.put(ValidatePersistentMethod.ARGUMENT_DEEP_VALIDATE, Boolean.FALSE); 137 138 errorsProperty = domainMetaClass.getMetaProperty(AbstractDynamicPersistentMethod.ERRORS_PROPERTY); 139 140 validateMethod = domainMetaClass.getMetaMethod(ValidatePersistentMethod.METHOD_SIGNATURE, 141 new Object[] { Map.class }); 142 } 143 buildCaller(Class<?> domainClazz, String event)144 private EventTriggerCaller buildCaller(Class<?> domainClazz, String event) { 145 Method method = ReflectionUtils.findMethod(domainClazz, event); 146 if (method != null) { 147 ReflectionUtils.makeAccessible(method); 148 return new MethodCaller(method); 149 } 150 151 Field field = ReflectionUtils.findField(domainClazz, event); 152 if (field != null) { 153 ReflectionUtils.makeAccessible(field); 154 return new FieldClosureCaller(field); 155 } 156 157 MetaMethod metaMethod = domainMetaClass.getMetaMethod(event, EMPTY_OBJECT_ARRAY); 158 if (metaMethod != null) { 159 return new MetaMethodCaller(metaMethod); 160 } 161 162 MetaProperty metaProperty = domainMetaClass.getMetaProperty(event); 163 if (metaProperty != null) { 164 return new MetaPropertyClosureCaller(metaProperty); 165 } 166 167 return null; 168 } 169 170 @SuppressWarnings("unchecked") onSaveOrUpdate(SaveOrUpdateEvent event)171 public void onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException { 172 Object entity = event.getObject(); 173 boolean newEntity = !event.getSession().contains(entity); 174 if (newEntity) { 175 if (beforeInsertCaller != null) { 176 beforeInsertCaller.call(entity); 177 if (event.getSession().contains(entity)) { 178 EntityEntry entry = event.getEntry(); 179 if (entry != null) { 180 Object[] state = entry.getLoadedState(); 181 synchronizePersisterState(entity, entry.getPersister(), state); 182 } 183 } 184 } 185 if (shouldTimestamp) { 186 long time = System.currentTimeMillis(); 187 if (dateCreatedProperty != null && newEntity) { 188 Object now = DefaultGroovyMethods.newInstance(dateCreatedProperty.getType(), new Object[] { time }); 189 dateCreatedProperty.setProperty(entity, now); 190 } 191 if (lastUpdatedProperty != null) { 192 Object now = DefaultGroovyMethods.newInstance(lastUpdatedProperty.getType(), new Object[] { time }); 193 lastUpdatedProperty.setProperty(entity, now); 194 } 195 } 196 } 197 } 198 synchronizePersisterState(Object entity, EntityPersister persister, Object[] state)199 private void synchronizePersisterState(Object entity, EntityPersister persister, Object[] state) { 200 String[] propertyNames = persister.getPropertyNames(); 201 for (int i = 0; i < propertyNames.length; i++) { 202 String p = propertyNames[i]; 203 MetaProperty metaProperty = domainMetaClass.getMetaProperty(p); 204 if (ClosureEventTriggeringInterceptor.IGNORED.contains(p) || metaProperty == null) { 205 continue; 206 } 207 Object value = metaProperty.getProperty(entity); 208 state[i] = value; 209 persister.setPropertyValue(entity, i, value, EntityMode.POJO); 210 } 211 } 212 onPreLoad(PreLoadEvent event)213 public void onPreLoad(PreLoadEvent event) { 214 if(preLoadEventCaller != null) { 215 preLoadEventCaller.call(event.getEntity()); 216 } 217 } 218 onPostLoad(PostLoadEvent event)219 public void onPostLoad(PostLoadEvent event) { 220 if (postLoadEventListener != null) { 221 postLoadEventListener.call(event.getEntity()); 222 } 223 } 224 onPostInsert(PostInsertEvent event)225 public void onPostInsert(PostInsertEvent event) { 226 if (postInsertEventListener != null) { 227 postInsertEventListener.call(event.getEntity()); 228 } 229 } 230 onPostUpdate(PostUpdateEvent event)231 public void onPostUpdate(PostUpdateEvent event) { 232 if (postUpdateEventListener != null) { 233 postUpdateEventListener.call(event.getEntity()); 234 } 235 } 236 onPostDelete(PostDeleteEvent event)237 public void onPostDelete(PostDeleteEvent event) { 238 if (postDeleteEventListener != null) { 239 postDeleteEventListener.call(event.getEntity()); 240 } 241 } 242 onPreDelete(PreDeleteEvent event)243 public boolean onPreDelete(PreDeleteEvent event) { 244 if (preDeleteEventListener != null) { 245 return preDeleteEventListener.call(event.getEntity()); 246 } 247 return false; 248 } 249 250 @SuppressWarnings("unchecked") onPreUpdate(PreUpdateEvent event)251 public boolean onPreUpdate(PreUpdateEvent event) { 252 Object entity = event.getEntity(); 253 boolean evict = false; 254 if (preUpdateEventListener != null) { 255 evict = preUpdateEventListener.call(entity); 256 synchronizePersisterState(entity, event.getPersister(), event.getState()); 257 } 258 if (lastUpdatedProperty != null && shouldTimestamp) { 259 Object now = DefaultGroovyMethods.newInstance(lastUpdatedProperty.getType(), new Object[] { System 260 .currentTimeMillis() }); 261 event.getState()[ArrayUtils.indexOf(event.getPersister().getPropertyNames(), GrailsDomainClassProperty.LAST_UPDATED)] = now; 262 lastUpdatedProperty.setProperty(entity, now); 263 } 264 if (!AbstractSavePersistentMethod.isAutoValidationDisabled(entity) 265 && !DefaultTypeTransformation.castToBoolean(validateMethod.invoke(entity, 266 new Object[] { validateParams }))) { 267 evict = true; 268 if (failOnErrorEnabled) { 269 Errors errors = (Errors) errorsProperty.getProperty(entity); 270 throw new ValidationException("Validation error whilst flushing entity [" + entity.getClass().getName() 271 + "]", errors); 272 } 273 274 } 275 return evict; 276 } 277 278 private static abstract class EventTriggerCaller { EventTriggerCaller()279 EventTriggerCaller() { 280 281 } 282 call(Object entity)283 public abstract boolean call(Object entity); 284 resolveReturnValue(Object retval)285 boolean resolveReturnValue(Object retval) { 286 if (retval instanceof Boolean) { 287 return !((Boolean) retval).booleanValue(); 288 } else { 289 return false; 290 } 291 } 292 } 293 294 private static class MethodCaller extends EventTriggerCaller { 295 Method method; 296 MethodCaller(Method method)297 MethodCaller(Method method) { 298 this.method = method; 299 } 300 call(Object entity)301 public boolean call(Object entity) { 302 Object retval = ReflectionUtils.invokeMethod(method, entity); 303 return resolveReturnValue(retval); 304 } 305 } 306 307 private static class MetaMethodCaller extends EventTriggerCaller { 308 MetaMethod method; 309 MetaMethodCaller(MetaMethod method)310 MetaMethodCaller(MetaMethod method) { 311 this.method = method; 312 } 313 call(Object entity)314 public boolean call(Object entity) { 315 Object retval = method.invoke(entity, EMPTY_OBJECT_ARRAY); 316 return resolveReturnValue(retval); 317 } 318 } 319 320 private static abstract class ClosureCaller extends EventTriggerCaller { 321 boolean cloneFirst=false; 322 callClosure(Object entity, Closure callable)323 Object callClosure(Object entity, Closure callable) { 324 if(cloneFirst) { 325 callable=(Closure)callable.clone(); 326 } 327 callable.setResolveStrategy(Closure.DELEGATE_FIRST); 328 callable.setDelegate(entity); 329 Object retval = callable.call(); 330 return retval; 331 } 332 } 333 334 private static class FieldClosureCaller extends ClosureCaller { 335 Field field; 336 FieldClosureCaller(Field field)337 FieldClosureCaller(Field field) { 338 this.field = field; 339 if(Modifier.isStatic(field.getModifiers())) { 340 cloneFirst=true; 341 } 342 } 343 344 @Override call(Object entity)345 public boolean call(Object entity) { 346 Object fieldval = ReflectionUtils.getField(field, entity); 347 if (fieldval instanceof Closure) { 348 return resolveReturnValue(callClosure(entity, (Closure) fieldval)); 349 } else { 350 log.error("Field " + field + " is not Closure or method."); 351 return false; 352 } 353 } 354 } 355 356 private static class MetaPropertyClosureCaller extends ClosureCaller { 357 MetaProperty metaProperty; 358 MetaPropertyClosureCaller(MetaProperty metaProperty)359 MetaPropertyClosureCaller(MetaProperty metaProperty) { 360 this.metaProperty = metaProperty; 361 if(Modifier.isStatic(metaProperty.getModifiers())) { 362 cloneFirst=true; 363 } 364 } 365 366 @Override call(Object entity)367 public boolean call(Object entity) { 368 Object fieldval = metaProperty.getProperty(entity); 369 if (fieldval instanceof Closure) { 370 return resolveReturnValue(callClosure(entity, (Closure) fieldval)); 371 } else { 372 log.error("Field " + metaProperty + " is not Closure."); 373 return false; 374 } 375 } 376 377 } 378 } 379