Announcement Announcement Module
Collapse
No announcement yet.
New User Question re AOP and Auditing Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • New User Question re AOP and Auditing

    I believe Spring AOP will be the perfect start for a component we need to develop for our application needs, which is transaction auditing. We need to be able to record (persist to db) who makes updates to what and when. The detail needs to be to the field level, capturing the original value and the new value, for auditing.

    I am looking to the Spring community for any pointers to articles or tutorials on how to accomplish this. We need to produce a reusable component that we can expose as a web service/services to use this functionality in any business app we develop (J2EE web apps).

    Thanks in advance,
    Ginni

  • #2
    I'm doing the same.

    When using hibernate, i use a EntityInterceptor and when not, i use a simple advice on methods that change auditable models.

    Advice:

    Code:
    	/**
    	 * Around advice for audit auditable models
    	 * 
    	 * @param pjp to pjp
    	 * @return object result
    	 * @throws Throwable on Exception
    	 */
    	public Object audit(ProceedingJoinPoint pjp) throws Throwable {
    		if (log.isDebugEnabled()) {
    			log.debug("Auditing on method: " + pjp.toShortString());
    		}
    
    	        Object result =  pjp.proceed(); 
    
    		if (pjp.getTarget() instanceof Auditable) {
    			Auditable auditable = (Auditable) (pjp.getTarget());
    			audit(auditable);
    		} else {
    			log.warn("Tried to audit a non-auditable object. Check your "
    					+ "AOP configuration on applicationContext.xml");
    		}
    		
    		return result;
    	}
    And for Hibernate:

    Code:
    **
     * Hibernate interceptor for Audit. 
     * 
     * @author Jose Luis Martin
     */
    public class AuditInterceptor extends EmptyInterceptor {
    	
    	/** */
    	private static final long serialVersionUID = 1L;
    	/** creation user property name */
    	private static final String CREATION_USER = "creationUser";
    	/** modification user property name */
    	private static final String MODIFICATION_USER = "modificationUser";
    	/** creation date property name */
    	private static final String CREATION_DATE = "creationDate";
    	/** modification date property name */
    	private static final String MODIFICATION_DATE = "modificationDate";
    	/** log */
    	private static Log log = LogFactory.getLog(AuditInterceptor.class);
    	/** updated entities on transaction */
    	private int updated;
    	/** deleted entities on transaction */
    	private int deleted;
    	/** loaded entities on transaction */
    	private int loaded;
    	/** created entities on transaction */
    	private int created;
    	
    	/**
    	 * Called before an object is saved. The interceptor may modify the
    	 * <tt>state</tt>, which will be used for
    	 * the SQL <tt>INSERT</tt> and propagated to the persistent object.
    	 * @param entity the entity
    	 * @param id the id
    	 * @param state the state
    	 * @param propertyNames the propertyNames
    	 * @param types the types
    	 * @return <tt>true</tt> if the user modified the <tt>state</tt> in any way.
    	 */
    	@Override
    	public boolean onSave(Object entity, Serializable id, Object[] state,
    			String[] propertyNames, Type[] types) {
    		
    		boolean modifyState = false;
    		if (entity instanceof Auditable) {
    			if (log.isDebugEnabled()) {
    				log.debug("Auditing entity:" + entity.getClass());
    			}
    			modifyState = true;
    			for (int i = 0; i < propertyNames.length; i++) {
    				if (CREATION_USER.equals(propertyNames[i])) {
    					state[i] = getUser();
    				} else if (CREATION_DATE.equals(propertyNames[i])) {
    					state[i] = new Date();
    				}
    			}
    		}
    		created++;
    		return modifyState;
    	}
    
    	/**
    	 * Called on updates
    	 * @param entity the entity
    	 * @param id the key
    	 * @param currentState currentState
    	 * @param previousState previousState
    	 * @param propertyNames propertyNames
    	 * @param types types
    	 * @return true if currentState is modified
    	 */
    	@Override
    	public boolean onFlushDirty(Object entity, Serializable id,
    			Object[] currentState, Object[] previousState,
    			String[] propertyNames, Type[] types) {
    		
    		boolean modifyState = false;
    		if (entity instanceof Auditable) {
    			if (log.isDebugEnabled()) {
    				log.debug("Auditing entity: " + entity.getClass());
    			}
    			modifyState = true;
    			for (int i = 0; i < propertyNames.length; i++) {
    				if (MODIFICATION_USER.equals(propertyNames[i])) {
    					currentState[i] = getUser();
    				} else if (MODIFICATION_DATE.equals(propertyNames[i])) {
    					currentState[i] = new Date();
    				}
    			}
    		}
    		updated++;
    		return modifyState;
    	}
    
    	/**
    	 * Called on loads
    	 * @param entity the entity
    	 * @param id the key
    	 * @param state state
    	 * @param propertyNames propertyNames
    	 * @param types types
    	 * @return true if state is modified
    	 */
    	public boolean onLoad(Object entity, Serializable id, Object[] state,
    			String[] propertyNames, Type[] types) {
    		loaded++;
    		return false;
    		
    	}
    
    	/**
    	 * Called after a transaction is committed or rolled back.
    	 * Log entity stats and reset counters
    	 * @param tx hibernate transaction
    	 */
    	public void afterTransactionCompletion(Transaction tx) {
    	        if (tx.wasCommitted() && log.isDebugEnabled()) {
    	            log.debug("Creations: " + created + ", Updates: " + updated
    	            		+ ", Loads: " + loaded + ", Deleted: " + deleted);
    	        }
    	        updated = 0;
    	        created = 0;
    	        loaded = 0;
    	        deleted = 0;
    	        
    	    }
    
    	/**
    	 * Only update counter, deletes are not audited
    	 * @param entity the entity
    	 * @param id the key
    	 * @param state state
    	 * @param propertyNames propertyNames
    	 * @param types types
    	 */
    	@Override
    	public void onDelete(Object entity, Serializable id, Object[] state,
    			String[] propertyNames, Type[] types) {
    		
    		deleted++;
    	}
    
    	/**
    	 * Try to get username from Acegi Pricipal.
    	 * @return username from Acegi Principal, Unknown otherwise
    	 */
    	private String getUser() {
    		SecurityContext context = SecurityContextHolder.getContext();
    		String username = "Unknown";
    		if (context != null) {
    			username = context.getAuthentication().getName();
    			log.debug("username:" + username);
    		}
    		return username;
    	}	
    }
    I cannot figure how to publish this as web service.

    HTH

    Comment


    • #3
      are you using hibernate? if so take a look at envers

      Comment

      Working...
      X