package org.springmodules.location;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
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.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.util.ClassUtils;
/**
*
* Immature, experimental, not necessary to the prototype.
* This can optionally choose routing between a local object
* and a space. It does this as an interceptor in the AOP
* invocation chain, which can simply invoke a target method and
* prevent progress down the chain towards the terminal interceptor
* that would write and take from a space.
*
* @author Rod Johnson
*
* TODO local spaces: compare performance
*
* Assumes that calling proceed will invoke remotely
*
* TODO what about taking into account server load!?
* Frequency of invocations to this service? But what
* about other services?
*
* TODO pluggable algorithm... round robin etc.
*
*/
public class LocationInterceptor implements MethodInterceptor, BeanFactoryAware {
private static final Log log = LogFactory.getLog(LocationInterceptor.class);
private Map methodStats = new HashMap();
private long thresholdMillis = 0;
private Map interfaceToObjectMap = new HashMap();
private int localInvokes;
private int remoteInvokes;
// TODO synchronization!?
private String[] beanNames;
public void setBeanNames(String[] beanNames) {
this.beanNames = beanNames;
}
public void setThresholdMillis(long thresholdMillis) {
this.thresholdMillis = thresholdMillis;
}
public void setBeanFactory(BeanFactory bf) {
// Get all singletons
for (int i = 0; i < beanNames.length; i++) {
if (bf.isSingleton(beanNames[i])) {
Class type = bf.getType(beanNames[i]);
if (type != null) {
Class[] interfaces = ClassUtils.getAllInterfacesForClass(type);
for (int j = 0; j < interfaces.length; j++) {
Class intf = interfaces[i];
// TODO exclude Spring
// TODO ban duplicates
// TODO MAPPING FROM INTERFACE TO BEAN!?
interfaceToObjectMap.put(intf, bf.getBean(beanNames[i]));
// TODO what about classes
}
}
}
}
}
protected Object localBean(MethodInvocation mi) {
return interfaceToObjectMap.get(mi.getMethod().getDeclaringClass());
}
protected Object invokeLocally(MethodInvocation mi) throws Throwable {
return AopUtils.invokeJoinpointUsingReflection(null, mi.getMethod(), mi.getArguments());
}
public Object invoke(MethodInvocation mi) throws Throwable {
Object localBean = localBean(mi);
if (localBean == null) {
// No option
if (log.isDebugEnabled())
log.debug("Must invoke space: not available locally");
++remoteInvokes;
return mi.proceed();
}
// It is available locally and remotely, let's see if it was annotated
MethodStats ms = null;
synchronized (methodStats) {
ms = (MethodStats) methodStats.get(mi.getMethod());
}
if (ms != null) {
// Need to outsource to space
if (log.isDebugEnabled())
log.debug("Marked as slow: invoke space");
++remoteInvokes;
return mi.proceed();
}
else {
// Available locally and remotely, let's invoke locally and time
if (log.isDebugEnabled())
log.debug("Invoking locally");
++localInvokes;
long started = System.currentTimeMillis();
try {
return AopUtils.invokeJoinpointUsingReflection(localBean, mi.getMethod(), mi.getArguments());
}
finally {
long elapsed = System.currentTimeMillis() - started;
if (elapsed > this.thresholdMillis) {
// Candidate for outsourcing
// TODO keep track of average
synchronized (methodStats) {
this.methodStats.put(mi.getMethod(), new MethodStats(mi.getMethod()));
}
}
}
}
}
public int getLocalInvokes() {
return localInvokes;
}
public int getRemoteInvokes() {
return remoteInvokes;
}
private class MethodStats {
private Method m;
public MethodStats(Method m) {
this.m = m;
}
}
}