3

I have been going through some Spring / AOP tutorials and have somewhat familiarized myself with the related concepts.

Now coming to my requirements, I need to create an Activities Log implementation which will save the activities of a logged-in user in the DB which can range from applying for a service or creating new users in case of Admin users, etc. On invocation of any method having an annotation (say @ActivityLog), this information is to be persisted in the form of actorId, actionComment, actionTime, actedUponId, ... etc.

Now, if I create a POJO class (that maps to a ActivityLog table in the DB) and want to save this data from inside the Advice (preferably using the same transaction as the method, method uses @Transactional annotation), how do I actually populate the variables in this POJO?? I can probably get the actorId from the session object & actionTime can simply be new Date() but how about the dynamic values for actionComment / actedUponId?

Any help will be brilliant! (BTW, I have a requirement to not use Hibernate Interceptors.)

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
Sumit
  • 540
  • 9
  • 20

3 Answers3

3

Here is a complete example:

@Aspect
@Component
public class WebMethodAuditor {

protected final Log logger = LogFactory.getLog(getClass());

public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";

@Autowired
AuditRecordDAO auditRecordDAO; 

@Before("execution(* com.mycontrollers.*.*(..))")
public void beforeWebMethodExecution(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    String methodName = joinPoint.getSignature().getName();
    User principal = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    Timestamp timestamp = new Timestamp(new java.util.Date().getTime());
    // only log those methods called by an end user
    if(principal.getUsername() != null) {
        for(Object o : args) {
            Boolean doInspect = true;
            if(o instanceof ServletRequestDataBinder) doInspect = false;
            if(o instanceof ExtendedModelMap) doInspect = false;
            if(doInspect) {
                if(o instanceof BaseForm ) {
                    // only show form objects
                    AuditRecord ar = new AuditRecord();
                    ar.setUsername(principal.getUsername());
                    ar.setClazz(o.getClass().getCanonicalName());
                    ar.setMethod(methodName);
                    ar.setAsString(o.toString());
                    ar.setAudit_timestamp(timestamp);
                    auditRecordDAO.save(ar);
                }
            }
        }
    }
}

}
atrain
  • 9,139
  • 1
  • 36
  • 40
2

If you are looking to get the actionComment and actedUponId from arguments to the annotated method (assuming they're both strings), you can add binding terms to your @Around pointcut like this:

@Around("@annotation(ActivityLog) && args(actionComment,actedUponId)")
public Object logActivity(ProceedingJoinPoint pjp,
        String actionComment, String actedUponId) throws Throwable {
    // ... get other values from context, etc. ...
    // ... write to log ...
    pjp.proceed();
}

The args binding in a pointcut can be used in partially-specified mode, in case there are other arguments about that you aren't interested in, and since the aspect is itself a bean, it can be wired into everything else that is going on in the normal way.

Note that if you're mixing declarative transaction management on the same method calls, you've got to get the order of aspects correct. That's done in part by making the aspect bean also implement the Spring Ordered interface, and by controlling the precedence of transactions through the order attribute to <tx:annotation-driven/>. (If that's impossible, you'll be forced to do clever things with direct transaction handling; that's a vastly more painful option to get right…)

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
0

You will be getting a reference to the org.aspectj.lang.JoinPoint in your advice.You can get the name of target method being executed with toShortString().You can have a loop-up/property file with the method-name=comments entries.This comments can be populated into the POJO.actionComment.method-name can be set to POJO.actedUponId.

I hope the advice should run within the same transaction, if the data-access method is adviced and the service method uses @Transactional.

Ahamed Mustafa M
  • 3,069
  • 1
  • 24
  • 34
  • Thanks for the reply. Interesting approach but the actionComment might be coming from user input!! Also wanted to add that I am guessing I should use After Advice (although not sure if it will use the same transaction as the original method)! Thanks, Sumit – Sumit May 18 '12 at 11:14
  • BTW actedUponId is supposed to be the userId of the person an operation (add, delete, etc) is being performed upon by an Admin. – Sumit May 18 '12 at 12:01
  • If spring-security is used, principal/user must be available with `SecurityContextHolder..getUserPrincipal()`.How are you planning to get the userId ? – Ahamed Mustafa M May 18 '12 at 18:54
  • Yes, thats how I will be getting the logged-in user, the issue is with the userId of the user being created / deleted / managed by Admins! – Sumit May 21 '12 at 06:22