OpenMRS AOP
Aspect Oriented Programming is meant to aid programmers with cross-cutting programming concerns. Typically, you'll find AOP being used for logging, authorization, and authentication. See Wikipedia's AOP entry Aspect-oriented programming
OpenMRS uses AOP mainly for the module system to allow the core API (Services) to be extended in an infinite number of ways. Instead of only providing hooks at certain points in the code for specific uses, every service method within the API can be wrapped by a method in a module. Module services can be extended via AOP as well.
There are three ways to wrap an API method:
- before
- The module's method will be called before the api method is called.
- The module's method will receive all parameters that the api method is receiving.
- after
- The module's method will be called after the api method is called.
- The module's method will receive all parameters that the api method received
- The module's method will receive the return value of the api method
- The module has the option of returning a different value to whomever called the api.
- around
- The module's method will be called before the api method is called.
- The module's method will receive all parameters that the api method is set to receive
- The module's method has the option of invoking the api method or not.
- The module's method can receive the return value of the api method (if invoked).
- The module's method can return either the value of the api method or another arbitrary value.
Example
AOP advice is added on a class basis. Lets say we want our method to be called after the PatientService.getPatient(Integer) method. We need to first create an advice class.
package org.openmrs.module.helloWorld.advice;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;
public class CountingAfterAdvice implements AfterReturningAdvice {
private Log log = LogFactory.getLog(this.getClass());
private int count = 0;
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
if (method.getName().equals("getPatient"))
log.debug("Method: " + method.getName() + ". After advice called " + (++count) + " time(s) now.");
}
}
This class will be wrapped around the entire PatientService class. Every API call in PatientService will now pass through our class after returning. The afterReturning method distinguishes between different method calls using the java.lang.reflect.Method class.
In order to wrap this advice class around the PatientService, we add an entry in the module's /metadata/config.xml file:
<advice> <point>org.openmrs.api.PatientService</point> <class>org.openmrs.module.helloWorld.advice.CountingAfterAdvice</class> </advice>
Before advice works in a very similar way. The only difference is that the org.springframework.aop.MethodBeforeAdvice interface is implemented instead of org.springframework.aop.AfterReturningAdvice.
Around Advice
Around advice is the most powerful type of AOP that you can do. The around advice classes need to implement org.springframework.aop.Advisor. An example of the most basic type:
package org.openmrs.module.helloWorld.advice;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class PrintingAroundAdvisor extends StaticMethodMatcherPointcutAdvisor implements Advisor {
private static final long serialVersionUID = 3333L;
private Log log = LogFactory.getLog(this.getClass());
public boolean matches(Method method, Class targetClass) {
// only 'run' this advice on the getter methods
if (method.getName().startsWith("get"))
return true;
return false;
}
@Override
public Advice getAdvice() {
log.debug("Getting new around advice");
return new PrintingAroundAdvice();
}
private class PrintingAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
log.debug("Before " + invocation.getMethod().getName() + ".");
// the proceed() method does not have to be called
Object o = invocation.proceed();
log.debug("After " + invocation.getMethod().getName() + ".");
return o;
}
}
}
Note that in this example, invocation.proceed() passes all arguments to the original service class. If you don't want the original service class methods to be called (methods starting with "get" in this example), simply omit invocation.proceed(), and return a dummy object, or you can call invocation.proceed() conditionally.
Around advice uses the same metadata in config.xml as after and before advice -- the point tag specifies which service api to wrap around, and the class tag allows you to specify your advice class (PrintingAroundAdvisor, in this case).
Also, since the spring 2.5 upgrade, make sure that your moduleApplicationContext file's basicUrlMapping has the "order" property set to a value lower than 99 (if this property ever disappears from openMRS's core basicUrlMapping, disregard this). If this is not set, advice may not be loaded properly.
Using AOP in Core OpenMRS Code
Core OpenMRS code can add AOP methods around the services as well. There are methods on Context code> that add either Advice or an Advisor. (An advisor is used for 'around' AOP, and an advice object is for 'before'/'after' AOP.)
Context.addAdvisor(Class cls, Advisor advisor) Context.removeAdvisor(Class cls, Advisor advisor)
Context.addAdvice(Class cls, Advice advice) Context.removeAdvice(Class cls, Advice advice)
