/*
* Copyright (C) The MX4J Contributors.
* All rights reserved.
*
* This software is distributed under the terms of the MX4J License version 1.0.
* See the terms of the MX4J License in the documentation provided with this software.
*/
package mx4j.server;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import mx4j.log.Log;
import mx4j.log.Logger;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.ARRAYLENGTH;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
/**
* MBeanInvoker that generates on-the-fly implementations to call standard MBeans directly, instead of using reflection.
* <br />
* It uses the <a href="http://jakarta.apache.org/bcel">BCEL</a> to generate the required bytecode on-the-fly.
* The generated class is named "mx4j.server.BCELMBeanInvokerGenerated", and it's loaded into the JVM by a different
* classloader for each MBean. This classloader has the MBean classloader as parent. <br>
* Below is an example of the generated code; beware that the management interface and all parameter's classes must be
* public, otherwise an IllegalAccessError is thrown and the invocation falls back to use reflection (but with a significant
* overhead - throwing an exception is expensive).
* <pre>
* public interface ServiceMBean
* {
* public void start();
* public Collection getServices(ServiceSelector selector);
* }
* <p/>
* public class BCELMBeanInvokerGenerated extends BCELMBeanInvoker
* {
* protected Object invokeImpl(MBeanMetaData metadata, String method, String[] signature, Object[] args)
* throws Throwable
* {
* if (method.equals("start") && args.length == 0)
* {
* try
* {
* ((ServiceMBean)metadata.mbean).start();
* return null;
* }
* catch (ClassCastException x) {}
* catch (IllegalAccessError x) {}
* }
* else if (method.equals("getServices") && args.length == 1)
* {
* try
* {
* return ((ServiceMBean)metadata.mbean).getServices((ServiceSelector)args[0]);
* }
* catch (ClassCastException x) {}
* catch (IllegalAccessError x) {}
* }
* return super.invokeImpl(metadata, method, signature, args);
* }
* }
* </pre>
*
* @version $Revision: 1.14 $
*/
public class BCELMBeanInvoker extends CachingReflectionMBeanInvoker
{
private static final String LOGGER_CATEGORY = BCELMBeanInvoker.class.getName();
/*
public static void main(String[] args) throws Exception
{
MBeanMetaData metadata = new MBeanMetaData();
metadata.classloader = ClassLoader.getSystemClassLoader();
metadata.management = Object.class;
Object generated = create(metadata);
System.out.println("generated = " + generated);
}
private static void dump(ClassGen classGen)
{
try
{
java.io.FileOutputStream fos = new java.io.FileOutputStream(new java.io.File("C:\\Simon\\OpenSource\\mx4j\\mx4j\\classes\\core\\mx4j\\server\\BCELMBeanInvokerGenerated.class"));
classGen.getJavaClass().dump(fos);
fos.close();
}
catch (java.io.IOException x)
{
x.printStackTrace();
}
}
*/
protected BCELMBeanInvoker()
{
}
/**
* Creates a new MBeanInvoker created on-the-fly by using BCEL.
* It must be synchronized since BCEL is not thread safe, and uses static variables
* (refer to org.apache.bcel.generic.Type.getArgumentTypes(...) for further details)
*/
public synchronized static MBeanInvoker create(final MBeanMetaData metadata)
{
String parentName = BCELMBeanInvoker.class.getName();
final String name = parentName + "Generated";
ClassGen classGen = new ClassGen(name, // Qualified class name
parentName, // Qualified superclass name
"<generated-on-the-fly>", // File name
Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SUPER, // Modifiers
null); // Implemented interfaces
classGen.addEmptyConstructor(Constants.ACC_PUBLIC);
classGen.addMethod(createInvokeImpl(metadata, classGen, name));
// For debug purposes only
// dump(classGen);
final byte[] bytes = classGen.getJavaClass().getBytes();
try
{
// Must shield clients, since creating a new classloader requires a permission
return (BCELMBeanInvoker)AccessController.doPrivileged(new PrivilegedExceptionAction()
{
public Object run() throws Exception
{
Class cls = new BCELClassLoader(metadata.getClassLoader(), bytes).loadClass(name);
return cls.newInstance();
}
});
}
catch (Throwable x)
{
Logger logger = Log.getLogger(LOGGER_CATEGORY);
if (logger.isEnabledFor(Logger.INFO)) logger.info("Cannot create on-the-fly MBeanInvoker class, going with reflection MBeanInvoker", x);
return new CachingReflectionMBeanInvoker();
}
}
private static org.apache.bcel.classfile.Method createInvokeImpl(MBeanMetaData metadata, ClassGen classGen, String clsName)
{
InstructionList implementation = new InstructionList();
ObjectType metadataType = new ObjectType(MBeanMetaData.class.getName());
Type[] signature = new Type[]{metadataType, Type.STRING, new ArrayType(Type.STRING, 1), new ArrayType(Type.OBJECT, 1)};
// Method definition
MethodGen mthd = new MethodGen(Constants.ACC_PROTECTED, // Modifiers
Type.OBJECT, // Return type
signature, // Signature
new String[]{"metadata", "method", "params", "args"}, // Parameter names
"invokeImpl", // Method name
clsName, // Class name
implementation, // Implementation
classGen.getConstantPool()); // Pool
mthd.addException("java.lang.Throwable");
// Now I should create the implementation
InstructionFactory factory = new InstructionFactory(classGen);
Method[] methods = metadata.getMBeanInterface().getMethods();
List tests = new ArrayList();
List catches = new ArrayList();
for (int i = 0; i < methods.length; ++i)
{
Method method = methods[i];
catches.addAll(generateDirectInvokeBranch(classGen, mthd, implementation, factory, metadata.getMBeanInterface().getName(), method, tests));
}
// To close the last branch, I must jump to super.invokeImpl(), so I need its first instruction here
InstructionHandle invokeSuper = implementation.append(factory.createThis());
for (int i = 0; i < tests.size(); ++i)
{
BranchInstruction branch = (BranchInstruction)tests.get(i);
branch.setTarget(invokeSuper);
}
tests.clear();
for (int i = 0; i < catches.size(); ++i)
{
BranchInstruction branch = (BranchInstruction)catches.get(i);
branch.setTarget(invokeSuper);
}
catches.clear();
//
// return super.invokeImpl(metadata, method, params, args);
//
// Again, it's invokeImpl(super, args) instead of super.invokeImpl(args)
// Use 'this' as first argument, and invokespecial instead of invokevirtual to call super
// 'this' is created above, to close the last branch
implementation.append(factory.createLoad(metadataType, 1));
implementation.append(factory.createLoad(Type.STRING, 2));
implementation.append(factory.createLoad(new ArrayType(Type.STRING, 1), 3));
implementation.append(factory.createLoad(new ArrayType(Type.OBJECT, 1), 4));
implementation.append(factory.createInvoke(BCELMBeanInvoker.class.getName(), "invokeImpl", Type.OBJECT, signature, Constants.INVOKESPECIAL));
implementation.append(factory.createReturn(Type.OBJECT));
mthd.setMaxStack();
org.apache.bcel.classfile.Method method = mthd.getMethod();
// Reuse instruction handles
implementation.dispose();
return method;
}
private static List generateDirectInvokeBranch(ClassGen classGen, MethodGen methodGen, InstructionList implementation, InstructionFactory factory, String management, Method method, List tests)
{
ArrayList catches = new ArrayList();
//
// if (method.equals("<operation>") && args.length == <num>)
//
// The first instruction, will be where I should go if the previous branch fails
InstructionHandle startTest = implementation.append(factory.createLoad(Type.STRING, 2));
// The first time it will be empty, the second time will be the first time's uninitialized jump instructions
for (int i = 0; i < tests.size(); ++i)
{
BranchInstruction branch = (BranchInstruction)tests.get(i);
// Initialize previous jump instruction to jump to the next branch
branch.setTarget(startTest);
}
tests.clear();
implementation.append(new PUSH(classGen.getConstantPool(), method.getName()));
implementation.append(factory.createInvoke(String.class.getName(), "equals", Type.BOOLEAN, new Type[]{Type.OBJECT}, Constants.INVOKEVIRTUAL));
// IFEQ compares the stack with 0, which means "if the previous comparison is false, ..."
BranchInstruction test1 = factory.createBranchInstruction(Constants.IFEQ, null);
tests.add(test1);
implementation.append(test1);
implementation.append(factory.createLoad(new ArrayType(Type.OBJECT, 1), 4));
implementation.append(new ARRAYLENGTH());
implementation.append(new PUSH(classGen.getConstantPool(), method.getParameterTypes().length));
// Here I should test if args.length == <num>, if not equal then go to the next branch
// Create branch instructions with no offset, since it cannot be handled now, see above
BranchInstruction test2 = factory.createBranchInstruction(Constants.IF_ICMPNE, null);
tests.add(test2);
implementation.append(test2);
// Here I am on the right method, unless someone created 2 methods with same names and same number of
// parameters but of different type. In this last case if we're lucky it's the right method and we go
// via direct call, otherwise we will have a class cast exception and go via reflection
// Cast and invoke
// Put the metadata on the stack, to access its 'mbean' field, that will be put on the stack
// It's also the start of the try block
InstructionHandle tryStart = implementation.append(factory.createLoad(new ObjectType(MBeanMetaData.class.getName()), 1));
implementation.append(factory.createInvoke(MBeanMetaData.class.getName(), "getMBean", Type.OBJECT, new Type[0], Constants.INVOKEVIRTUAL));
// Cast the 'mbean' field to the proper type, the stack will contain the casted mbean
implementation.append(factory.createCheckCast(new ObjectType(management)));
// Now add all the arguments to the stack
Class[] signature = method.getParameterTypes();
Type[] invokeSignature = new Type[signature.length];
for (int i = 0; i < signature.length; ++i)
{
Class param = signature[i];
// Load all args on the stack
implementation.append(factory.createLoad(new ArrayType(Type.OBJECT, 1), 4));
// I want index 'i'
implementation.append(new PUSH(classGen.getConstantPool(), i));
// Now on the stack there is args[i]
implementation.append(factory.createArrayLoad(Type.OBJECT));
// Save the signature for the invocation
invokeSignature[i] = convertClassToType(param);
if (param.isPrimitive())
{
// On the stack I have the wrapper object, I have to convert them to primitive
replaceObjectWithPrimitive(param, implementation, factory);
}
else if (param.isArray())
{
// Cast args[i] to the proper class
implementation.append(factory.createCheckCast((ReferenceType)invokeSignature[i]));
}
else
{
// Cast args[i] to the proper class
implementation.append(factory.createCheckCast((ReferenceType)invokeSignature[i]));
}
}
Class returnClass = method.getReturnType();
Type returnType = convertClassToType(returnClass);
// On the stack we now have the casted mbean and all the casted arguments, invoke
implementation.append(factory.createInvoke(management, method.getName(), returnType, invokeSignature, Constants.INVOKEINTERFACE));
if (returnClass == Void.TYPE)
{
implementation.append(InstructionConstants.ACONST_NULL);
}
else if (returnClass.isArray())
{
// Thanks to the fact that we can assign any array to Object, we do nothing here
}
else if (returnClass.isPrimitive())
{
replacePrimitiveWithObject(returnClass, methodGen, implementation, factory);
}
InstructionHandle tryEnd = implementation.append(factory.createReturn(Type.OBJECT));
// In case of class cast exception, eat the exception and call super (hence using reflection)
// catch (ClassCastException x) {/* do nothing */}
// On the stack there is the exception, we assign it to local variable 'x'
ObjectType exceptionTypeCCE = new ObjectType("java.lang.ClassCastException");
LocalVariableGen x = methodGen.addLocalVariable("x", exceptionTypeCCE, null, null);
InstructionHandle handler = implementation.append(factory.createStore(exceptionTypeCCE, x.getIndex()));
x.setStart(handler);
x.setEnd(handler);
methodGen.addExceptionHandler(tryStart, tryEnd, handler, exceptionTypeCCE);
// This catch block is followed by another one, and I don't exit with a throw or a return
BranchInstruction skip = factory.createBranchInstruction(Constants.GOTO, null);
catches.add(skip);
implementation.append(skip);
// An IllegalAccessError is thrown if the MBean interface or a parameter class is not public
// We eat it and fall back to call super (hence using reflection)
// catch (IllegalAccessError x) {/* do nothing */}
ObjectType errorTypeIAE = new ObjectType("java.lang.IllegalAccessError");
x = methodGen.addLocalVariable("x", errorTypeIAE, null, null);
handler = implementation.append(factory.createStore(errorTypeIAE, x.getIndex()));
x.setStart(handler);
x.setEnd(handler);
methodGen.addExceptionHandler(tryStart, tryEnd, handler, errorTypeIAE);
// This catch block is followed by another one, and I don't exit with a throw or a return,
skip = factory.createBranchInstruction(Constants.GOTO, null);
catches.add(skip);
implementation.append(skip);
return catches;
}
private static Type convertClassToType(Class cls)
{
if (cls == void.class) return Type.VOID;
if (cls == boolean.class) return Type.BOOLEAN;
if (cls == byte.class) return Type.BYTE;
if (cls == char.class) return Type.CHAR;
if (cls == short.class) return Type.SHORT;
if (cls == int.class) return Type.INT;
if (cls == long.class) return Type.LONG;
if (cls == float.class) return Type.FLOAT;
if (cls == double.class) return Type.DOUBLE;
if (cls == Object.class) return Type.OBJECT;
if (cls == String.class) return Type.STRING;
if (cls.isArray())
{
int dimensions = 0;
Class c = null;
while ((c = cls.getComponentType()) != null)
{
++dimensions;
cls = c;
}
Type t = convertClassToType(cls);
return new ArrayType(t, dimensions);
}
return new ObjectType(cls.getName());
}
private static void replaceObjectWithPrimitive(Class type, InstructionList implementation, InstructionFactory factory)
{
// Put as first the most common ones
if (type == int.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Integer.class.getName())));
implementation.append(factory.createInvoke(Integer.class.getName(), "intValue", Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else if (type == boolean.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Boolean.class.getName())));
implementation.append(factory.createInvoke(Boolean.class.getName(), "booleanValue", Type.BOOLEAN, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else if (type == long.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Long.class.getName())));
implementation.append(factory.createInvoke(Long.class.getName(), "longValue", Type.LONG, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else if (type == byte.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Byte.class.getName())));
implementation.append(factory.createInvoke(Byte.class.getName(), "byteValue", Type.BYTE, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else if (type == char.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Character.class.getName())));
implementation.append(factory.createInvoke(Character.class.getName(), "charValue", Type.CHAR, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else if (type == short.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Short.class.getName())));
implementation.append(factory.createInvoke(Short.class.getName(), "shortValue", Type.SHORT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else if (type == float.class)
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Float.class.getName())));
implementation.append(factory.createInvoke(Float.class.getName(), "floatValue", Type.FLOAT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
else
{
// Cast the operand in the stack and get the value
implementation.append(factory.createCheckCast(new ObjectType(Double.class.getName())));
implementation.append(factory.createInvoke(Double.class.getName(), "doubleValue", Type.DOUBLE, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
}
}
private static void replacePrimitiveWithObject(Class type, MethodGen methodGen, InstructionList implementation, InstructionFactory factory)
{
// Put as first the most common ones
if (type == int.class)
{
// Create a new instance of the wrapper
LocalVariableGen i = methodGen.addLocalVariable("i", Type.INT, null, null);
i.setStart(implementation.append(factory.createStore(Type.INT, i.getIndex())));
implementation.append(factory.createNew(new ObjectType(Integer.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.INT, i.getIndex()));
i.setEnd(implementation.append(factory.createInvoke(Integer.class.getName(), "<init>", Type.VOID, new Type[]{Type.INT}, Constants.INVOKESPECIAL)));
}
else if (type == boolean.class)
{
// Create a new instance of the wrapper
LocalVariableGen b = methodGen.addLocalVariable("b", Type.BOOLEAN, null, null);
b.setStart(implementation.append(factory.createStore(Type.BOOLEAN, b.getIndex())));
implementation.append(factory.createNew(new ObjectType(Boolean.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.BOOLEAN, b.getIndex()));
b.setEnd(implementation.append(factory.createInvoke(Boolean.class.getName(), "<init>", Type.VOID, new Type[]{Type.BOOLEAN}, Constants.INVOKESPECIAL)));
}
else if (type == long.class)
{
// Create a new instance of the wrapper
LocalVariableGen l = methodGen.addLocalVariable("l", Type.LONG, null, null);
l.setStart(implementation.append(factory.createStore(Type.LONG, l.getIndex())));
implementation.append(factory.createNew(new ObjectType(Long.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.LONG, l.getIndex()));
l.setEnd(implementation.append(factory.createInvoke(Long.class.getName(), "<init>", Type.VOID, new Type[]{Type.LONG}, Constants.INVOKESPECIAL)));
}
else if (type == byte.class)
{
// Create a new instance of the wrapper
LocalVariableGen b = methodGen.addLocalVariable("b", Type.BYTE, null, null);
b.setStart(implementation.append(factory.createStore(Type.BYTE, b.getIndex())));
implementation.append(factory.createNew(new ObjectType(Byte.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.BYTE, b.getIndex()));
b.setEnd(implementation.append(factory.createInvoke(Byte.class.getName(), "<init>", Type.VOID, new Type[]{Type.BYTE}, Constants.INVOKESPECIAL)));
}
else if (type == char.class)
{
// Create a new instance of the wrapper
LocalVariableGen c = methodGen.addLocalVariable("c", Type.CHAR, null, null);
c.setStart(implementation.append(factory.createStore(Type.CHAR, c.getIndex())));
implementation.append(factory.createNew(new ObjectType(Character.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.CHAR, c.getIndex()));
c.setEnd(implementation.append(factory.createInvoke(Character.class.getName(), "<init>", Type.VOID, new Type[]{Type.CHAR}, Constants.INVOKESPECIAL)));
}
else if (type == short.class)
{
// Create a new instance of the wrapper
LocalVariableGen s = methodGen.addLocalVariable("s", Type.SHORT, null, null);
s.setStart(implementation.append(factory.createStore(Type.SHORT, s.getIndex())));
implementation.append(factory.createNew(new ObjectType(Short.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.SHORT, s.getIndex()));
s.setEnd(implementation.append(factory.createInvoke(Short.class.getName(), "<init>", Type.VOID, new Type[]{Type.SHORT}, Constants.INVOKESPECIAL)));
}
else if (type == float.class)
{
// Create a new instance of the wrapper
LocalVariableGen f = methodGen.addLocalVariable("f", Type.FLOAT, null, null);
f.setStart(implementation.append(factory.createStore(Type.FLOAT, f.getIndex())));
implementation.append(factory.createNew(new ObjectType(Float.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.FLOAT, f.getIndex()));
f.setEnd(implementation.append(factory.createInvoke(Float.class.getName(), "<init>", Type.VOID, new Type[]{Type.FLOAT}, Constants.INVOKESPECIAL)));
}
else
{
// Create a new instance of the wrapper
LocalVariableGen d = methodGen.addLocalVariable("d", Type.DOUBLE, null, null);
d.setStart(implementation.append(factory.createStore(Type.DOUBLE, d.getIndex())));
implementation.append(factory.createNew(new ObjectType(Double.class.getName())));
implementation.append(InstructionConstants.DUP);
implementation.append(factory.createLoad(Type.DOUBLE, d.getIndex()));
d.setEnd(implementation.append(factory.createInvoke(Double.class.getName(), "<init>", Type.VOID, new Type[]{Type.DOUBLE}, Constants.INVOKESPECIAL)));
}
}
private Logger getLogger()
{
return Log.getLogger(LOGGER_CATEGORY);
}
protected Object invokeImpl(MBeanMetaData metadata, String method, String[] signature, Object[] args) throws Throwable
{
Logger logger = getLogger();
if (logger.isEnabledFor(Logger.INFO))
{
logger.info("BCEL invocation failed for method " + method + "" + Arrays.asList(signature) + ", using reflection");
}
return super.invokeImpl(metadata, method, signature, args);
}
private static class BCELClassLoader extends SecureClassLoader
{
private byte[] m_bytes;
private BCELClassLoader(ClassLoader parent, byte[] bytecode)
{
super(parent);
m_bytes = bytecode;
}
protected Class findClass(final String name) throws ClassNotFoundException
{
try
{
return (Class)AccessController.doPrivileged(new PrivilegedExceptionAction()
{
public Object run() throws ClassNotFoundException
{
try
{
return defineClass(name, m_bytes, 0, m_bytes.length, BCELClassLoader.this.getClass().getProtectionDomain());
}
catch (ClassFormatError x)
{
throw new ClassNotFoundException("Class Format Error", x);
}
}
}, null);
}
catch (PrivilegedActionException x)
{
throw (ClassNotFoundException)x.getException();
}
}
}
}