blob: 472a58c5a1b72c1a7f65df4d1beb8069f091f544 [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.felix.jmxintrospector;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
/*import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
*/
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.NotificationEmitter;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Predicate;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.InterfaceMaker;
import org.objectweb.asm.Type;
/**
*
* The MBeanProxyFactory is the central element of the jmxintrospector library. The main method is the {@link MBeanProxyFactory#newProxyInstance(String)}
* method, that accepts an objectname in String form and returns a proxy for that mbean.
* Internally, it uses the cglib library, although it be made to work with other bytecode libraries. More specifically,prior versions used the Javassist library
*/
public class MBeanProxyFactory {
private MBeanServerConnection mbeanServer;
public MBeanProxyFactory(){
}
public MBeanProxyFactory(MBeanServerConnection mbeanServer) {
super();
this.mbeanServer = mbeanServer;
}
/**
* Internal method for generating the Class object for the dynamically-generated interface for the mbean.
* The name given to the class is based on the actual classname of the mbean, although some extensions are added
* to it by the cglib library to avoid namespace clashes.
* @param oname
* @return
* @throws IOException
* @throws InstanceNotFoundException
* @throws ClassNotFoundException
* @throws ReflectionException
* @throws IntrospectionException
* @throws
* @throws MalformedObjectNameException
* @throws Exception
*/
private Class getInterface(String oname) throws MalformedObjectNameException, InstanceNotFoundException, IOException, IntrospectionException, ReflectionException {
ObjectName objectName=ObjectName.getInstance(oname);
String ifaceName=mbeanServer.getObjectInstance(objectName).getClassName();
//uses the ifaceName as the prefix for the class name
InterfaceMaker maker=new MBeanInterfaceMaker(ifaceName);
for (Signature s : getSignatures(objectName)) {
//add each method
maker.add(s, null);
}
return maker.create();
}
private Type getType(String type) throws ClassNotFoundException{
return JmxAsmHelper.getAsmType(type);
}
/**
* Internal method for generating the signatures of the mbeans.
* @param objectName
* @return
* @throws IOException
* @throws ReflectionException
* @throws IntrospectionException
* @throws InstanceNotFoundException
*/
private List<Signature> getSignatures(ObjectName objectName)throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException{
List<Signature> methods=new ArrayList<Signature>();
MBeanInfo minfo;
MBeanAttributeInfo[] attributes=null;
minfo = mbeanServer.getMBeanInfo(objectName);
attributes=minfo.getAttributes();
for (MBeanAttributeInfo info : attributes) {
String name=info.getName().substring(0, 1).toUpperCase()+info.getName().substring(1);
if(info.isReadable()){
//For each readable attribute, we generate a getter method (following the isXX for booleans
//when it is being used on the remote side)
if(info.isIs()){
methods.add(new Signature("is"+name, Type.BOOLEAN_TYPE, new Type[0]));
}
else{
try{
methods.add(new Signature("get"+name, getType(info.getType()), new Type[0]));
}catch(ClassNotFoundException cnfe){
System.out.println("JMXINTROSPECTOR WARNING: "+info.getType()+" could not be found. Attribute will not be added to proxy.");
continue;
}
}
}
//Same with each writable att, but with setters.
if(info.isWritable()){
try{
Type [] params=new Type[]{getType(info.getType())};
Signature s=new Signature("set"+name, Type.VOID_TYPE, params);
methods.add(s);
}catch(ClassNotFoundException cnfe){
System.out.println("JMXINTROSPECTOR WARNING: "+info.getType()+" could not be found. Attribute will not be added to proxy.");
continue;
}
}
}
//same for each operation
for (MBeanOperationInfo info : minfo.getOperations()) {
try{
Type[] params=new Type[info.getSignature().length];
for (int i = 0; i < params.length; i++) {
params[i]=getType(info.getSignature()[i].getType());
}
Signature s=new Signature(info.getName(), getType(info.getReturnType()), params);
methods.add(s);
}catch(ClassNotFoundException cnfe){
System.out.println("JMXINTROSPECTOR WARNING: "+info.toString()+" could not be created. Operation will not be added to proxy.");
continue;
}
}
return methods;
}
/**
* Returns a proxy object for the MBean specified by the object name oname in the
* mbean server associated with this factory. This proxy object implements the generated interface plus
* the MBean interface. It will also be a notification broadcaster if the underlying mbean broadcasts notifications.
* @param oname
* @return
* @throws IOException
* @throws ReflectionException
* @throws IntrospectionException
* @throws InstanceNotFoundException
* @throws
* @throws MalformedObjectNameException
* @throws Exception
*/
public Object newProxyInstance(String oname) throws MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, ReflectionException, IOException{
ObjectName objectName=ObjectName.getInstance(oname);
Class iface=getInterface(oname);
MBeanInfo info=mbeanServer.getMBeanInfo(objectName);
boolean isBroadcaster=false;
MBeanNotificationInfo[] notifs=info.getNotifications();
if (notifs!=null && notifs.length!=0) isBroadcaster=true;
//We first create the proxy for the remote mbean. If broadcasting supported, then it adds the broadcasting interface
Object proxy=MBeanServerInvocationHandler.newProxyInstance(mbeanServer, objectName, iface, isBroadcaster);
//We get the underlying invocation handler, needed for the wrapper handler. The wrapper adds the mbean interface functionality
//and integrates JMX invocation handler.
InvocationHandler h=Proxy.getInvocationHandler(proxy);
InvocationHandler wrapper=new JMXInvocationHandler(oname,mbeanServer, mbeanServer.getMBeanInfo(objectName), h);
Class[] ifaces;
if (isBroadcaster) {
ifaces=new Class[]{iface, NotificationEmitter.class, MBean.class};
}else ifaces=new Class[]{iface, MBean.class};
//finally, we create the proxy with the appropriate classloader, the interfaces and the invocation handler
Object mbeanProxy=Proxy.newProxyInstance(proxy.getClass().getClassLoader(), ifaces, wrapper);
return mbeanProxy;
}
private class JMXInvocationHandler implements InvocationHandler, MBean{
private String objectName;
private MBeanServerConnection mBeanServer;
private InvocationHandler mbeanHandler;
private MBeanInfo mbeanInfo;
public JMXInvocationHandler(String objectName, MBeanServerConnection mbeanServer, MBeanInfo mBeanInfo, InvocationHandler mbeanHandler) {
super();
this.objectName = objectName;
this.mBeanServer = mbeanServer;
this.mbeanHandler = mbeanHandler;
this.mbeanInfo =mBeanInfo;
}
//FIXME: hashCode() and equals do not work if not exposed in management interface. I have not thought of a workaround for that yet.
//note that this is needed for hashmaps and comparisons.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//decide wether to invoke the JMXInvocationHandler we have implemented
//or use the one that was created by the MBeanServerInvocationHandler
if(method.getDeclaringClass().equals(MBean.class)){
return this.getClass().getMethod(method.getName(), null).invoke(this, args);
}
else return mbeanHandler.invoke(proxy, method, args);
}
public MBeanServerConnection getMBeanServer() {
return mBeanServer;
}
public String getObjectName() {
return objectName;
}
public MBeanInfo getMBeanInfo() {
return mbeanInfo;
}
}
public MBeanServerConnection getMbeanServer() {
return mbeanServer;
}
public void setMbeanServer(MBeanServerConnection mbs) {
this.mbeanServer = mbs;
}
/**
* This class is used to be able to modify the super class,
* because setNamePrefix and setAttemptLoad are protected methods
*
*/
private class MBeanInterfaceMaker extends InterfaceMaker{
public MBeanInterfaceMaker(String namePrefix) {
super();
super.setNamePrefix(namePrefix);
super.setAttemptLoad(true);
}
}
}