Coverage Report - net.taylor.audit.AuditListener
 
Classes in this File Line Coverage Branch Coverage Complexity
AuditListener
72% 
80% 
0
 
 1  
 /*
 2  
  * ============================================================================
 3  
  *                   GNU Lesser General Public License
 4  
  * ============================================================================
 5  
  *
 6  
  * Taylor - The Java Enterprise Application Framework.
 7  
  * Copyright (C) 2005 John Gilbert jgilbert01@users.sourceforge.net
 8  
  *
 9  
  * This library is free software; you can redistribute it and/or
 10  
  * modify it under the terms of the GNU Lesser General Public
 11  
  * License as published by the Free Software Foundation; either
 12  
  * version 2.1 of the License, or (at your option) any later version.
 13  
  *
 14  
  * This library is distributed in the hope that it will be useful,
 15  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17  
  * Lesser General Public License for more details.
 18  
  *
 19  
  * You should have received a copy of the GNU Lesser General Public
 20  
  * License along with this library; if not, write to the Free Software
 21  
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 22  
  *
 23  
  * John Gilbert
 24  
  * Email: jgilbert01@users.sourceforge.net
 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  18
 public class AuditListener implements PostDeleteEventListener,
 77  
                 PostInsertEventListener, PostUpdateEventListener, PreCollectionUpdateEventListener, PreCollectionRemoveEventListener,
 78  
         PostCollectionRecreateEventListener {
 79  
         protected static final long serialVersionUID = 1L;
 80  
 
 81  3
         protected static final Log logger = LogFactory.getLog(AuditListener.class);
 82  
 
 83  
         public void onPostInsert(PostInsertEvent event) {
 84  9
                 if (event.getId() == null) {
 85  0
                         return;
 86  
                 }
 87  9
                 if (bypass(event.getEntity().getClass())) {
 88  0
                         return;
 89  
                 }
 90  
 
 91  9
                 queue(Action.INSERT, event.getEntity().getClass().getName(), (Long) event
 92  
                                 .getId(), event.getPersister().getPropertyNames(), null, event
 93  
                                 .getState());
 94  9
         }
 95  
 
 96  
         public void onPostUpdate(PostUpdateEvent event) {
 97  3
                 if (bypass(event.getEntity().getClass())) {
 98  0
                         return;
 99  
                 }
 100  
 
 101  3
                 queue(Action.UPDATE, event.getEntity().getClass().getName(), (Long) event
 102  
                                 .getId(), event.getPersister().getPropertyNames(), event
 103  
                                 .getOldState(), event.getState());
 104  3
         }
 105  
 
 106  
         public void onPostDelete(PostDeleteEvent event) {
 107  9
                 if (bypass(event.getEntity().getClass())) {
 108  0
                         return;
 109  
                 }
 110  
 
 111  9
                 queue(Action.DELETE, event.getEntity().getClass().getName(), (Long) event
 112  
                                 .getId(), null, null, null);
 113  9
         }
 114  
 
 115  
         @SuppressWarnings("unchecked")
 116  
         protected boolean bypass(Class entityClass) {
 117  42
                 if (entityClass.isAnnotationPresent(BypassAudit.class)) {
 118  0
                         return true;
 119  
                 }
 120  42
                 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  21
                 final AuditEntry ae = createAuditEntry(entityName, id, action);
 128  21
                 if (names != null) {
 129  12
                         addAuditFields(ae, names, oldValues, newValues);
 130  
                 }
 131  
 
 132  21
                 publish(ae);
 133  21
         }
 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  30
                 JmsSender.unmanagedSend("topic/net.taylor.AuditTopic", new MessageCallback() {
 144  30
                         public Message createMessage(Session session) throws JMSException {
 145  30
                                 ObjectMessage msg = session.createObjectMessage();
 146  30
                                 msg.setStringProperty(ACTION, ae.getAction().name());
 147  30
                                 msg.setStringProperty(ENTITY_NAME, ae.getEntityName());
 148  30
                                 msg.setLongProperty(ENTITY_ID, ae.getEntityId());
 149  30
                                 msg.setObject(ae);
 150  30
                                 return msg;
 151  
                         }
 152  
                 });                        
 153  30
         }
 154  
         
 155  
         protected AuditEntry createAuditEntry(String entityName, Long entityId,
 156  
                         Action action) {
 157  42
                 AuditEntry ae = new AuditEntry();
 158  42
                 if (JAASUtil.getPrincipal() == null) {
 159  0
                         ae.setUser("system");
 160  0
                 } else {
 161  42
                         ae.setUser(JAASUtil.getPrincipal().getName());
 162  
                 }
 163  42
                 ae.setTimestamp(new Date());
 164  42
                 ae.setEntityName(entityName);
 165  42
                 ae.setEntityId(entityId);
 166  42
                 ae.setAction(action);
 167  
 
 168  42
                 if (logger.isDebugEnabled()) {
 169  42
                         logger.debug(ToStringBuilder.reflectionToString(ae,
 170  
                                         ToStringStyle.MULTI_LINE_STYLE));
 171  
                 }
 172  
 
 173  42
                 return ae;
 174  
         }
 175  
 
 176  
         protected AuditField createAuditField(String name, Object oldValue,
 177  
                         Object newValue) {
 178  18
                 AuditField af = new AuditField();
 179  18
                 af.setName(name);
 180  18
                 af.setNewValue(getValue(newValue));
 181  18
                 af.setOldValue(getValue(oldValue));
 182  
 
 183  18
                 if (logger.isDebugEnabled()) {
 184  18
                         logger.debug(ToStringBuilder.reflectionToString(af,
 185  
                                         ToStringStyle.MULTI_LINE_STYLE));
 186  
                 }
 187  
 
 188  18
                 return af;
 189  
         }
 190  
 
 191  
         protected String getValue(Object v) {
 192  48
                 if (v == null) {
 193  15
                         return null;
 194  
                 }
 195  33
                 if (v.getClass().isAnnotationPresent(Entity.class)) {
 196  
                         // handle display of m2o associations
 197  6
                         String[] names = { "getName", "getCode", "getTitle", "getDescription" };
 198  6
                         for (String name : names) {
 199  6
                                 String value = get(v, name);
 200  6
                                 if (value != null) {
 201  6
                                         return value;
 202  
                                 }
 203  
                         }
 204  0
                         return JpaUtil.getNaturalId(v).toString();
 205  
                 }
 206  27
                 return v.toString();
 207  
         }
 208  
 
 209  
         private String get(Object o, String name) {
 210  
                 try {
 211  6
                         Method m = o.getClass().getMethod(name, new Class[] {});
 212  6
                         if (m == null) {
 213  0
                                 return null;
 214  
                         }
 215  6
                         return (String) m.invoke(o, new Object[0]);
 216  0
                 } catch (Exception ex) {
 217  0
                         return null;
 218  
                 }
 219  
         }
 220  
 
 221  
         protected void addAuditFields(AuditEntry ae, String[] names,
 222  
                         Object[] oldValues, Object[] newValues) {
 223  48
                 for (int i = 0; i < names.length; i++) {
 224  36
                         Object ov = oldValues == null ? null : oldValues[i];
 225  36
                         Object nv = newValues == null ? null : newValues[i];
 226  36
                         if (isCollection(ov, nv)) {
 227  12
                                 continue;
 228  24
                         } else if (isEmbedded(ov, nv)) {
 229  0
                                 handleEmbedded(ae, names[i], ov, nv);
 230  0
                         } else if (hasChanged(ov, nv)) {
 231  18
                                 ae.addField(createAuditField(names[i], ov, nv));
 232  
                         }
 233  
                 }
 234  12
         }
 235  
 
 236  
         protected boolean isCollection(Object oldValue, Object newValue) {
 237  36
                 Object value = oldValue;
 238  36
                 if (value == null) {
 239  30
                         value = newValue;
 240  
                 }
 241  36
                 if (value == null) {
 242  6
                         return false;
 243  
                 }
 244  30
                 return value instanceof Collection;
 245  
         }
 246  
 
 247  
         protected boolean isEmbedded(Object oldValue, Object newValue) {
 248  24
                 Object value = oldValue;
 249  24
                 if (value == null) {
 250  21
                         value = newValue;
 251  
                 }
 252  24
                 if (value == null) {
 253  6
                         return false;
 254  
                 }
 255  18
                 return value.getClass().isAnnotationPresent(Embeddable.class);
 256  
         }
 257  
 
 258  
         protected void handleEmbedded(AuditEntry ae, String name, Object oldValue,
 259  
                         Object newValue) {
 260  
                 try {
 261  0
                         Map<String, Object> oldValues = describe(oldValue);
 262  0
                         Map<String, Object> newValues = describe(newValue);
 263  0
                         Set<String> names = newValues.keySet();
 264  0
                         for (String key : names) {
 265  0
                                 if ("class".equals(key)) {
 266  0
                                         continue;
 267  
                                 }
 268  0
                                 Object ov = oldValues.get(key);
 269  0
                                 Object nv = newValues.get(key);
 270  
 
 271  0
                                 if (isEmbedded(ov, nv)) {
 272  0
                                         handleEmbedded(ae, name + "." + key, ov, nv);
 273  0
                                 } else if (hasChanged(ov, nv)) {
 274  0
                                         ae.addField(createAuditField(name + "." + key, ov, nv));
 275  
                                 }
 276  0
                         }
 277  0
                 } catch (Exception e) {
 278  0
                         throw new RuntimeException(e);
 279  0
                 }
 280  0
         }
 281  
 
 282  
         protected boolean hasChanged(Object oldValue, Object newValue) {
 283  24
                 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  0
                 Map<String, Object> properties = new HashMap<String, Object>();
 291  0
                 if (object != null) {
 292  0
                         List<Method> list = new ArrayList<Method>();
 293  0
                         Method[] methods = object.getClass().getMethods();
 294  0
                         for (int i = 0; i < methods.length; i++) {
 295  0
                                 Method method = methods[i];
 296  0
                                 if (method.getName().startsWith("get")) {
 297  0
                                         list.add(method);
 298  
                                 }
 299  
                         }
 300  0
                         for (Method method : list) {
 301  0
                                 String name = method.getName();
 302  0
                                 name = name.substring(3);
 303  0
                                 name = StringUtils.uncapitalize(name);
 304  0
                                 properties.put(name, ReflectionUtil.invoke(method, object,
 305  
                                                 new Object[] {}));
 306  0
                         }
 307  
                 }
 308  0
                 return properties;
 309  
         }
 310  
         
 311  
         //---------------------------------------------------------------
 312  
         // collections
 313  
         //---------------------------------------------------------------
 314  
 
 315  
     public void onPreUpdateCollection(PreCollectionUpdateEvent event) {
 316  3
                 if (bypass(event.getAffectedOwnerOrNull().getClass())) {
 317  0
                         return;
 318  
                 }
 319  3
         CollectionEntry collectionEntry = getCollectionEntry(event);        
 320  
 //        if (!collectionEntry.getLoadedPersister().isInverse()) {
 321  3
             onCollectionAction(event, event.getCollection(), collectionEntry.getSnapshot(), collectionEntry);
 322  
 //        }
 323  3
     }
 324  
 
 325  
     public void onPreRemoveCollection(PreCollectionRemoveEvent event) {
 326  9
                 if (bypass(event.getAffectedOwnerOrNull().getClass())) {
 327  0
                         return;
 328  
                 }
 329  9
         CollectionEntry collectionEntry = getCollectionEntry(event);
 330  
 //        if (!collectionEntry.getLoadedPersister().isInverse()) {
 331  9
             onCollectionAction(event, null, collectionEntry.getSnapshot(), collectionEntry);
 332  
 //        }
 333  9
     }
 334  
 
 335  
     public void onPostRecreateCollection(PostCollectionRecreateEvent event) {
 336  9
                 if (bypass(event.getAffectedOwnerOrNull().getClass())) {
 337  0
                         return;
 338  
                 }
 339  9
         CollectionEntry collectionEntry = getCollectionEntry(event);
 340  
 //        if (!collectionEntry.getLoadedPersister().isInverse()) {
 341  9
             onCollectionAction(event, event.getCollection(), null, collectionEntry);
 342  
 //        }
 343  9
     }
 344  
 
 345  
     protected CollectionEntry getCollectionEntry(AbstractCollectionEvent event) {
 346  21
         return event.getSession().getPersistenceContext().getCollectionEntry(event.getCollection());
 347  
     }
 348  
     
 349  
     protected void onCollectionAction(AbstractCollectionEvent event, PersistentCollection newColl, Serializable oldColl,
 350  
             CollectionEntry collectionEntry) {
 351  
 
 352  21
                 final AuditEntry ae = createAuditEntry(event
 353  
                                 .getAffectedOwnerEntityName(), (Long) event
 354  
                                 .getAffectedOwnerIdOrNull(), Action.UPDATE);
 355  
                 
 356  21
                 Collection<?> added = (newColl instanceof Collection ? (Collection<?>) newColl : null);
 357  21
                 Collection<?> removed = (oldColl instanceof Collection ? (Collection<?>) oldColl : null);
 358  21
                 if (added != null && removed != null) {
 359  3
                         Collection<?> intersection = CollectionUtils.intersection(added, removed);
 360  3
                         added.removeAll(intersection);
 361  3
                         removed.removeAll(intersection);
 362  
                 }
 363  
 
 364  21
                 if (added != null) {
 365  12
                         String role = event.getCollection().getRole();
 366  12
                         if (role != null) {
 367  3
                                 role = role.substring(role.lastIndexOf(".") + 1);
 368  
                         }
 369  12
                         for (Object object : added) {
 370  6
                                 AuditField af = new AuditField();
 371  6
                                 af.setName(role);
 372  6
                                 String value = JpaUtil.getNaturalId(object).toString();
 373  6
                                 value = value.replace("{", "").replace("}", "");
 374  6
                                 af.setNewValue(getValue(value));
 375  6
                                 ae.addField(af);
 376  6
                         }
 377  
                 }
 378  
 
 379  21
                 if (removed != null) {
 380  12
                         String role = event.getCollection().getRole();
 381  12
                         if (role != null) {
 382  12
                                 role = role.substring(role.lastIndexOf(".") + 1);
 383  
                         }
 384  12
                         for (Object object : removed) {
 385  6
                                 AuditField af = new AuditField();
 386  6
                                 af.setName(role);
 387  6
                                 String value = JpaUtil.getNaturalId(object).toString();
 388  6
                                 value = value.replace("{", "").replace("}", "");
 389  6
                                 af.setOldValue(getValue(value));
 390  6
                                 ae.addField(af);
 391  6
                         }
 392  
                 }
 393  
 
 394  21
                 if (!ae.getFields().isEmpty()) {
 395  9
                         publish(ae);
 396  
                 }
 397  21
         }        
 398  
 }