blob: 99438146e07d07575c5b85471a31b14b5710ca68 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.superbiz.dynamic.mbean;
import javax.annotation.PreDestroy;
import javax.management.Attribute;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Need a @PreDestroy method to disconnect the remote host when used in remote mode.
*/
public class DynamicMBeanHandler implements InvocationHandler {
private final Map<Method, ConnectionInfo> infos = new ConcurrentHashMap<Method, ConnectionInfo>();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final String methodName = method.getName();
if (method.getDeclaringClass().equals(Object.class) && "toString".equals(methodName)) {
return getClass().getSimpleName() + " Proxy";
}
if (method.getAnnotation(PreDestroy.class) != null) {
return destroy();
}
final ConnectionInfo info = getConnectionInfo(method);
final MBeanInfo infos = info.getMBeanInfo();
if (methodName.startsWith("set") && methodName.length() > 3 && args != null && args.length == 1
&& (Void.TYPE.equals(method.getReturnType()) || Void.class.equals(method.getReturnType()))) {
final String attributeName = attributeName(infos, methodName, method.getParameterTypes()[0]);
info.setAttribute(new Attribute(attributeName, args[0]));
return null;
} else if (methodName.startsWith("get") && (args == null || args.length == 0) && methodName.length() > 3) {
final String attributeName = attributeName(infos, methodName, method.getReturnType());
return info.getAttribute(attributeName);
}
// operation
return info.invoke(methodName, args, getSignature(method));
}
public Object destroy() {
for (ConnectionInfo info : infos.values()) {
info.clean();
}
infos.clear();
return null;
}
private String[] getSignature(Method method) {
String[] args = new String[method.getParameterTypes().length];
for (int i = 0; i < method.getParameterTypes().length; i++) {
args[i] = method.getParameterTypes()[i].getName();
}
return args; // note: null should often work...
}
private String attributeName(MBeanInfo infos, String methodName, Class<?> type) {
String found = null;
String foundBackUp = null; // without checking the type
final String attributeName = methodName.substring(3, methodName.length());
final String lowerName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4, methodName.length());
for (MBeanAttributeInfo attribute : infos.getAttributes()) {
final String name = attribute.getName();
if (attributeName.equals(name)) {
foundBackUp = attributeName;
if (attribute.getType().equals(type.getName())) {
found = name;
}
} else if (found == null && ((lowerName.equals(name) && !attributeName.equals(name))
|| lowerName.equalsIgnoreCase(name))) {
foundBackUp = name;
if (attribute.getType().equals(type.getName())) {
found = name;
}
}
}
if (found == null && foundBackUp == null) {
throw new UnsupportedOperationException("cannot find attribute " + attributeName);
}
if (found != null) {
return found;
}
return foundBackUp;
}
private synchronized ConnectionInfo getConnectionInfo(Method method) throws Exception {
if (!infos.containsKey(method)) {
synchronized (infos) {
if (!infos.containsKey(method)) { // double check for synchro
org.superbiz.dynamic.mbean.ObjectName on = method.getAnnotation(org.superbiz.dynamic.mbean.ObjectName.class);
if (on == null) {
Class<?> current = method.getDeclaringClass();
do {
on = method.getDeclaringClass().getAnnotation(org.superbiz.dynamic.mbean.ObjectName.class);
current = current.getSuperclass();
} while (on == null && current != null);
if (on == null) {
throw new UnsupportedOperationException("class or method should define the objectName to use for invocation: " + method.toGenericString());
}
}
final ConnectionInfo info;
if (on.url().isEmpty()) {
info = new LocalConnectionInfo();
((LocalConnectionInfo) info).server = ManagementFactory.getPlatformMBeanServer(); // could use an id...
} else {
info = new RemoteConnectionInfo();
final Map<String, String[]> environment = new HashMap<String, String[]>();
if (!on.user().isEmpty()) {
environment.put(JMXConnector.CREDENTIALS, new String[]{on.user(), on.password()});
}
// ((RemoteConnectionInfo) info).connector = JMXConnectorFactory.newJMXConnector(new JMXServiceURL(on.url()), environment);
((RemoteConnectionInfo) info).connector = JMXConnectorFactory.connect(new JMXServiceURL(on.url()), environment);
}
info.objectName = new ObjectName(on.value());
infos.put(method, info);
}
}
}
return infos.get(method);
}
private abstract static class ConnectionInfo {
protected ObjectName objectName;
public abstract void setAttribute(Attribute attribute) throws Exception;
public abstract Object getAttribute(String attribute) throws Exception;
public abstract Object invoke(String operationName, Object params[], String signature[]) throws Exception;
public abstract MBeanInfo getMBeanInfo() throws Exception;
public abstract void clean();
}
private static class LocalConnectionInfo extends ConnectionInfo {
private MBeanServer server;
@Override
public void setAttribute(Attribute attribute) throws Exception {
server.setAttribute(objectName, attribute);
}
@Override
public Object getAttribute(String attribute) throws Exception {
return server.getAttribute(objectName, attribute);
}
@Override
public Object invoke(String operationName, Object[] params, String[] signature) throws Exception {
return server.invoke(objectName, operationName, params, signature);
}
@Override
public MBeanInfo getMBeanInfo() throws Exception {
return server.getMBeanInfo(objectName);
}
@Override
public void clean() {
// no-op
}
}
private static class RemoteConnectionInfo extends ConnectionInfo {
private JMXConnector connector;
private MBeanServerConnection connection;
private void before() throws IOException {
connection = connector.getMBeanServerConnection();
}
private void after() throws IOException {
// no-op
}
@Override
public void setAttribute(Attribute attribute) throws Exception {
before();
connection.setAttribute(objectName, attribute);
after();
}
@Override
public Object getAttribute(String attribute) throws Exception {
before();
try {
return connection.getAttribute(objectName, attribute);
} finally {
after();
}
}
@Override
public Object invoke(String operationName, Object[] params, String[] signature) throws Exception {
before();
try {
return connection.invoke(objectName, operationName, params, signature);
} finally {
after();
}
}
@Override
public MBeanInfo getMBeanInfo() throws Exception {
before();
try {
return connection.getMBeanInfo(objectName);
} finally {
after();
}
}
@Override
public void clean() {
try {
connector.close();
} catch (IOException e) {
// no-op
}
}
}
}