1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package net.taylor.audit;
27
28 import java.io.Serializable;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 import javax.jms.JMSException;
39 import javax.jms.Message;
40 import javax.jms.ObjectMessage;
41 import javax.jms.Session;
42 import javax.persistence.Embeddable;
43 import javax.persistence.Entity;
44
45 import net.taylor.audit.entity.Action;
46 import net.taylor.audit.entity.AuditEntry;
47 import net.taylor.audit.entity.AuditField;
48 import net.taylor.jms.JmsSender;
49 import net.taylor.jms.MessageCallback;
50 import net.taylor.jpa.JpaUtil;
51 import net.taylor.lang.ReflectionUtil;
52 import net.taylor.security.JAASUtil;
53
54 import org.apache.commons.collections.CollectionUtils;
55 import org.apache.commons.lang.StringUtils;
56 import org.apache.commons.lang.builder.ToStringBuilder;
57 import org.apache.commons.lang.builder.ToStringStyle;
58 import org.apache.commons.logging.Log;
59 import org.apache.commons.logging.LogFactory;
60 import org.hibernate.collection.PersistentCollection;
61 import org.hibernate.engine.CollectionEntry;
62 import org.hibernate.event.AbstractCollectionEvent;
63 import org.hibernate.event.PostCollectionRecreateEvent;
64 import org.hibernate.event.PostCollectionRecreateEventListener;
65 import org.hibernate.event.PostDeleteEvent;
66 import org.hibernate.event.PostDeleteEventListener;
67 import org.hibernate.event.PostInsertEvent;
68 import org.hibernate.event.PostInsertEventListener;
69 import org.hibernate.event.PostUpdateEvent;
70 import org.hibernate.event.PostUpdateEventListener;
71 import org.hibernate.event.PreCollectionRemoveEvent;
72 import org.hibernate.event.PreCollectionRemoveEventListener;
73 import org.hibernate.event.PreCollectionUpdateEvent;
74 import org.hibernate.event.PreCollectionUpdateEventListener;
75
76 public class AuditListener implements PostDeleteEventListener,
77 PostInsertEventListener, PostUpdateEventListener, PreCollectionUpdateEventListener, PreCollectionRemoveEventListener,
78 PostCollectionRecreateEventListener {
79 protected static final long serialVersionUID = 1L;
80
81 protected static final Log logger = LogFactory.getLog(AuditListener.class);
82
83 public void onPostInsert(PostInsertEvent event) {
84 if (event.getId() == null) {
85 return;
86 }
87 if (bypass(event.getEntity().getClass())) {
88 return;
89 }
90
91 queue(Action.INSERT, event.getEntity().getClass().getName(), (Long) event
92 .getId(), event.getPersister().getPropertyNames(), null, event
93 .getState());
94 }
95
96 public void onPostUpdate(PostUpdateEvent event) {
97 if (bypass(event.getEntity().getClass())) {
98 return;
99 }
100
101 queue(Action.UPDATE, event.getEntity().getClass().getName(), (Long) event
102 .getId(), event.getPersister().getPropertyNames(), event
103 .getOldState(), event.getState());
104 }
105
106 public void onPostDelete(PostDeleteEvent event) {
107 if (bypass(event.getEntity().getClass())) {
108 return;
109 }
110
111 queue(Action.DELETE, event.getEntity().getClass().getName(), (Long) event
112 .getId(), null, null, null);
113 }
114
115 @SuppressWarnings("unchecked")
116 protected boolean bypass(Class entityClass) {
117 if (entityClass.isAnnotationPresent(BypassAudit.class)) {
118 return true;
119 }
120 return false;
121 }
122
123 protected void queue(final Action action, final String entityName,
124 final Long id, final String[] names, final Object[] oldValues,
125 final Object[] newValues) {
126
127 final AuditEntry ae = createAuditEntry(entityName, id, action);
128 if (names != null) {
129 addAuditFields(ae, names, oldValues, newValues);
130 }
131
132 publish(ae);
133 }
134
135 public static final String ACTION = "action";
136 public static final String ENTITY_NAME = "entityName";
137 public static final String ENTITY_ID = "entityId";
138 public static final String INSERT = "INSERT";
139 public static final String UPDATE = "UPDATE";
140 public static final String DELETE = "DELETE";
141
142 protected void publish(final AuditEntry ae) {
143 JmsSender.unmanagedSend("topic/net.taylor.AuditTopic", new MessageCallback() {
144 public Message createMessage(Session session) throws JMSException {
145 ObjectMessage msg = session.createObjectMessage();
146 msg.setStringProperty(ACTION, ae.getAction().name());
147 msg.setStringProperty(ENTITY_NAME, ae.getEntityName());
148 msg.setLongProperty(ENTITY_ID, ae.getEntityId());
149 msg.setObject(ae);
150 return msg;
151 }
152 });
153 }
154
155 protected AuditEntry createAuditEntry(String entityName, Long entityId,
156 Action action) {
157 AuditEntry ae = new AuditEntry();
158 if (JAASUtil.getPrincipal() == null) {
159 ae.setUser("system");
160 } else {
161 ae.setUser(JAASUtil.getPrincipal().getName());
162 }
163 ae.setTimestamp(new Date());
164 ae.setEntityName(entityName);
165 ae.setEntityId(entityId);
166 ae.setAction(action);
167
168 if (logger.isDebugEnabled()) {
169 logger.debug(ToStringBuilder.reflectionToString(ae,
170 ToStringStyle.MULTI_LINE_STYLE));
171 }
172
173 return ae;
174 }
175
176 protected AuditField createAuditField(String name, Object oldValue,
177 Object newValue) {
178 AuditField af = new AuditField();
179 af.setName(name);
180 af.setNewValue(getValue(newValue));
181 af.setOldValue(getValue(oldValue));
182
183 if (logger.isDebugEnabled()) {
184 logger.debug(ToStringBuilder.reflectionToString(af,
185 ToStringStyle.MULTI_LINE_STYLE));
186 }
187
188 return af;
189 }
190
191 protected String getValue(Object v) {
192 if (v == null) {
193 return null;
194 }
195 if (v.getClass().isAnnotationPresent(Entity.class)) {
196
197 String[] names = { "getName", "getCode", "getTitle", "getDescription" };
198 for (String name : names) {
199 String value = get(v, name);
200 if (value != null) {
201 return value;
202 }
203 }
204 return JpaUtil.getNaturalId(v).toString();
205 }
206 return v.toString();
207 }
208
209 private String get(Object o, String name) {
210 try {
211 Method m = o.getClass().getMethod(name, new Class[] {});
212 if (m == null) {
213 return null;
214 }
215 return (String) m.invoke(o, new Object[0]);
216 } catch (Exception ex) {
217 return null;
218 }
219 }
220
221 protected void addAuditFields(AuditEntry ae, String[] names,
222 Object[] oldValues, Object[] newValues) {
223 for (int i = 0; i < names.length; i++) {
224 Object ov = oldValues == null ? null : oldValues[i];
225 Object nv = newValues == null ? null : newValues[i];
226 if (isCollection(ov, nv)) {
227 continue;
228 } else if (isEmbedded(ov, nv)) {
229 handleEmbedded(ae, names[i], ov, nv);
230 } else if (hasChanged(ov, nv)) {
231 ae.addField(createAuditField(names[i], ov, nv));
232 }
233 }
234 }
235
236 protected boolean isCollection(Object oldValue, Object newValue) {
237 Object value = oldValue;
238 if (value == null) {
239 value = newValue;
240 }
241 if (value == null) {
242 return false;
243 }
244 return value instanceof Collection;
245 }
246
247 protected boolean isEmbedded(Object oldValue, Object newValue) {
248 Object value = oldValue;
249 if (value == null) {
250 value = newValue;
251 }
252 if (value == null) {
253 return false;
254 }
255 return value.getClass().isAnnotationPresent(Embeddable.class);
256 }
257
258 protected void handleEmbedded(AuditEntry ae, String name, Object oldValue,
259 Object newValue) {
260 try {
261 Map<String, Object> oldValues = describe(oldValue);
262 Map<String, Object> newValues = describe(newValue);
263 Set<String> names = newValues.keySet();
264 for (String key : names) {
265 if ("class".equals(key)) {
266 continue;
267 }
268 Object ov = oldValues.get(key);
269 Object nv = newValues.get(key);
270
271 if (isEmbedded(ov, nv)) {
272 handleEmbedded(ae, name + "." + key, ov, nv);
273 } else if (hasChanged(ov, nv)) {
274 ae.addField(createAuditField(name + "." + key, ov, nv));
275 }
276 }
277 } catch (Exception e) {
278 throw new RuntimeException(e);
279 }
280 }
281
282 protected boolean hasChanged(Object oldValue, Object newValue) {
283 return (oldValue != null && newValue != null && !oldValue
284 .equals(newValue))
285 || (oldValue != null && newValue == null)
286 || (oldValue == null && newValue != null);
287 }
288
289 protected Map<String, Object> describe(Object object) {
290 Map<String, Object> properties = new HashMap<String, Object>();
291 if (object != null) {
292 List<Method> list = new ArrayList<Method>();
293 Method[] methods = object.getClass().getMethods();
294 for (int i = 0; i < methods.length; i++) {
295 Method method = methods[i];
296 if (method.getName().startsWith("get")) {
297 list.add(method);
298 }
299 }
300 for (Method method : list) {
301 String name = method.getName();
302 name = name.substring(3);
303 name = StringUtils.uncapitalize(name);
304 properties.put(name, ReflectionUtil.invoke(method, object,
305 new Object[] {}));
306 }
307 }
308 return properties;
309 }
310
311
312
313
314
315 public void onPreUpdateCollection(PreCollectionUpdateEvent event) {
316 if (bypass(event.getAffectedOwnerOrNull().getClass())) {
317 return;
318 }
319 CollectionEntry collectionEntry = getCollectionEntry(event);
320
321 onCollectionAction(event, event.getCollection(), collectionEntry.getSnapshot(), collectionEntry);
322
323 }
324
325 public void onPreRemoveCollection(PreCollectionRemoveEvent event) {
326 if (bypass(event.getAffectedOwnerOrNull().getClass())) {
327 return;
328 }
329 CollectionEntry collectionEntry = getCollectionEntry(event);
330
331 onCollectionAction(event, null, collectionEntry.getSnapshot(), collectionEntry);
332
333 }
334
335 public void onPostRecreateCollection(PostCollectionRecreateEvent event) {
336 if (bypass(event.getAffectedOwnerOrNull().getClass())) {
337 return;
338 }
339 CollectionEntry collectionEntry = getCollectionEntry(event);
340
341 onCollectionAction(event, event.getCollection(), null, collectionEntry);
342
343 }
344
345 protected CollectionEntry getCollectionEntry(AbstractCollectionEvent event) {
346 return event.getSession().getPersistenceContext().getCollectionEntry(event.getCollection());
347 }
348
349 protected void onCollectionAction(AbstractCollectionEvent event, PersistentCollection newColl, Serializable oldColl,
350 CollectionEntry collectionEntry) {
351
352 final AuditEntry ae = createAuditEntry(event
353 .getAffectedOwnerEntityName(), (Long) event
354 .getAffectedOwnerIdOrNull(), Action.UPDATE);
355
356 Collection<?> added = (newColl instanceof Collection ? (Collection<?>) newColl : null);
357 Collection<?> removed = (oldColl instanceof Collection ? (Collection<?>) oldColl : null);
358 if (added != null && removed != null) {
359 Collection<?> intersection = CollectionUtils.intersection(added, removed);
360 added.removeAll(intersection);
361 removed.removeAll(intersection);
362 }
363
364 if (added != null) {
365 String role = event.getCollection().getRole();
366 if (role != null) {
367 role = role.substring(role.lastIndexOf(".") + 1);
368 }
369 for (Object object : added) {
370 AuditField af = new AuditField();
371 af.setName(role);
372 String value = JpaUtil.getNaturalId(object).toString();
373 value = value.replace("{", "").replace("}", "");
374 af.setNewValue(getValue(value));
375 ae.addField(af);
376 }
377 }
378
379 if (removed != null) {
380 String role = event.getCollection().getRole();
381 if (role != null) {
382 role = role.substring(role.lastIndexOf(".") + 1);
383 }
384 for (Object object : removed) {
385 AuditField af = new AuditField();
386 af.setName(role);
387 String value = JpaUtil.getNaturalId(object).toString();
388 value = value.replace("{", "").replace("}", "");
389 af.setOldValue(getValue(value));
390 ae.addField(af);
391 }
392 }
393
394 if (!ae.getFields().isEmpty()) {
395 publish(ae);
396 }
397 }
398 }