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