package de.twenty11.skysail.server.ext.osgi.monitor.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.List;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.twenty11.skysail.server.ext.osgi.monitor.agent.callback.CallbackDefinition;
import de.twenty11.skysail.server.ext.osgi.monitor.agent.callback.Callbacks;
import de.twenty11.skysail.server.ext.osgi.monitor.agent.callback.serviceregistration.ServiceRegistrationCallback;
import de.twenty11.skysail.server.ext.osgi.monitor.agent.config.JsonConfig;
import de.twenty11.skysail.server.ext.osgi.monitor.agent.descriptors.CallbackDescriptor;
/**
* A class file transformer which finds the Classes implementing the underlying OSGi framework and transforms them for
* monitoring the framework internally.
*
*/
public class OsgiFrameworkTransformer implements ClassFileTransformer {
private static final Logger logger = LoggerFactory.getLogger(OsgiFrameworkTransformer.class);
private ClassPool classPool;
private Callbacks callbacks;
private List<CallbackDescriptor> config;
public OsgiFrameworkTransformer(ClassPool classPool, Callbacks callbacks, Logger agentlogger) {
this.classPool = classPool;
this.callbacks = callbacks;
for (String packageName : callbacks.packages()) {
logger.debug("adding package {} to the classPool", packageName);
this.classPool.importPackage(packageName);
}
config = new JsonConfig().readConfig("/config/osgi.json");
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// "return early" if it makes sense
if (ignore(className)) {
return classfileBuffer;
}
try {
CtClass ctClass = classPool.get(className.replace("/", "."));
checkForInstrumentation(ctClass);
if (classImplements(ctClass, Const.OSGI_BUNDLE_CLASS_NAME)) {
instrumentBundleImplementor(ctClass);
} else if (classImplements(ctClass, Const.OSGI_BUNDLE_CONTEXT_CLASS_NAME)) {
instrumentBundleContextImplementor(ctClass, Const.OSGI_BUNDLE_CONTEXT_CLASS_NAME);
} else if (classImplements(ctClass, "org.apache.felix.framework.ext.FelixBundleContext")) {
instrumentBundleContextImplementor(ctClass, "org.apache.felix.framework.ext.FelixBundleContext");
} else if (classImplements(ctClass, Const.OSGI_SERVICE_REG_CLASS_NAME)) {
instrumentServiceRegistrationImplementor(ctClass);
} else {
return classfileBuffer;
}
classfileBuffer = ctClass.toBytecode();
ctClass.detach();
} catch (NotFoundException nfe) {
logger.trace("Javassist not found exception: {}", nfe.getMessage());
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return classfileBuffer;
}
private void checkForInstrumentation(CtClass ctClass) {
for (CallbackDescriptor cd : config) {
List<MethodIdentifier> methods = cd.getMethods();
for (MethodIdentifier methodIdentifier : methods) {
if (classImplements(ctClass, methodIdentifier.getClassname())) {
instrument(ctClass, cd.getCallbackClassName(), methodIdentifier);
break;
}
}
}
}
private void instrument(CtClass ctClass, String callbackClassName, MethodIdentifier methodIdentifier) {
instrumentMethod(callbackClassName, ctClass, methodIdentifier);
}
private void instrumentBundleImplementor(CtClass ctClass) {
// instrumentMethod(Const.BUNDLE_CALLBACK, ctClass, new MethodIdentifier(Const.OSGI_BUNDLE_CLASS_NAME, "start",
// "(I)V"));
// instrumentMethod(Const.BUNDLE_CALLBACK, Const.OSGI_BUNDLE_CLASS_NAME, ctClass, "start", "()V");
// instrumentMethod(Const.BUNDLE_CALLBACK, Const.OSGI_BUNDLE_CLASS_NAME, ctClass, "stop", "(I)V");
instrumentMethod(Const.BUNDLE_CALLBACK, Const.OSGI_BUNDLE_CLASS_NAME, ctClass, "stop", "()V");
// missing: update(InputStream)
instrumentMethod(Const.BUNDLE_CALLBACK, Const.OSGI_BUNDLE_CLASS_NAME, ctClass, "update", "()V");
instrumentMethod(Const.BUNDLE_CALLBACK, Const.OSGI_BUNDLE_CLASS_NAME, ctClass, "uninstall", "()V");
instrumentMethod(Const.BUNDLE_CALLBACK, Const.OSGI_BUNDLE_CLASS_NAME, ctClass, "getState", "()I");
}
private void instrumentBundleContextImplementor(CtClass ctClass, String className) {
instrumentMethod(Const.BUNDLE_CONTEXT_CALLBACK, className, ctClass, "registerService",
Const.REGISTER_SERVICE_SIGNATURE_1);
instrumentMethod(Const.BUNDLE_CONTEXT_CALLBACK, className, ctClass, "registerService",
Const.REGISTER_SERVICE_SIGNATURE_2);
instrumentMethod(Const.BUNDLE_CONTEXT_CALLBACK, className, ctClass, "registerService",
Const.REGISTER_SERVICE_SIGNATURE_3);
instrumentMethod(Const.BUNDLE_CONTEXT_CALLBACK, className, ctClass, "ungetService",
"(Lorg/osgi/framework/ServiceReference;)Z");
}
private void instrumentServiceRegistrationImplementor(CtClass ctClass) {
instrumentMethod(ServiceRegistrationCallback.class.getName(), Const.OSGI_SERVICE_REG_CLASS_NAME, ctClass,
"unregister", "()V");
}
private void instrumentMethod(String callbackClassName, String classIdentifier, CtClass ctClass, String methodName,
String desc) {
try {
CtMethod m = ctClass.getMethod(methodName, desc);
CallbackDefinition callbackDefinition = callbacks.get(callbackClassName);
callbackDefinition.instrument(classIdentifier, m, classPool);
} catch (Exception e) {
handleException(ctClass, e);
}
}
private void instrumentMethod(String callbackClassName, CtClass ctClass, MethodIdentifier methodIdentifier) {
try {
CtMethod m = ctClass.getMethod(methodIdentifier.getMethodName(), methodIdentifier.getSignature());
CallbackDefinition callbackDefinition = callbacks.get(callbackClassName);
callbackDefinition.instrument(methodIdentifier, m, classPool);
} catch (Exception e) {
handleException(ctClass, e);
}
}
/**
* http://stackoverflow.com/questions/20316965/get-a-name-of-a-method-parameter-using-javassist
*
*/
private boolean ignore(String className) {
if (className.startsWith("java/") || className.startsWith("javax/") || className.startsWith("sun/")) {
return true;
}
if (className.startsWith("org/junit") || className.startsWith("junit/framework")) {
return true;
}
if (className.startsWith("org/apache/maven/surefire")) {
return true;
}
if (className.startsWith("org/springframework")) {
return true;
}
return false;
}
private boolean classImplements(CtClass ctClass, String interfaceName) {
List<CtClass> interfaces;
try {
interfaces = Arrays.asList(ctClass.getInterfaces());
if (interfaces == null || interfaces.size() == 0) {
return false;
}
for (CtClass interfaceIdent : interfaces) {
if (interfaceIdent.getName().equals(interfaceName)) {
if (ctClass.isInterface()) {
System.out.println("ignoring '" + interfaceName + "' interface: " + ctClass.getName());
System.out
.println("maybe this class should be added to deal with the current framework correctly.");
return false;
}
System.out.println("found for " + interfaceName + " class: " + ctClass.getName());
return true;
}
}
} catch (NotFoundException e) {
e.printStackTrace();
}
return false;
}
private void handleException(CtClass ctClass, Exception e) {
e.printStackTrace();
List<CtMethod> methods = Arrays.asList(ctClass.getMethods());
for (CtMethod ctMethod : methods) {
System.out.println(ctMethod.getName() + ": " + ctMethod.getMethodInfo2().getDescriptor());
}
}
}