// 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 net.sourceforge.eclipsejetty.starter.console.command;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import net.sourceforge.eclipsejetty.starter.console.AbstractCommand;
import net.sourceforge.eclipsejetty.starter.console.ArgumentException;
import net.sourceforge.eclipsejetty.starter.console.ConsoleAdapter;
import net.sourceforge.eclipsejetty.starter.console.Process;
import net.sourceforge.eclipsejetty.starter.console.util.WildcardUtils;
import net.sourceforge.eclipsejetty.starter.util.Utils;
/**
* Calls MBeans.
*
* @author Manfred Hantschel
*/
public class MBeanCommand extends AbstractCommand
{
private final MBeanServer server;
public MBeanCommand(ConsoleAdapter consoleAdapter)
{
super(consoleAdapter, "mbean", "mb");
server = ManagementFactory.getPlatformMBeanServer();
}
/**
* {@inheritDoc}
*
* @see net.sourceforge.eclipsejetty.starter.console.Command#getFormat()
*/
public String getFormat()
{
return "[command [params]]";
}
/**
* {@inheritDoc}
*
* @see net.sourceforge.eclipsejetty.starter.console.Command#getDescription()
*/
public String getDescription()
{
return "Access local MBeans.";
}
/**
* {@inheritDoc}
*
* @see net.sourceforge.eclipsejetty.starter.console.AbstractCommand#help(net.sourceforge.eclipsejetty.starter.console.Process)
*/
@Override
public int help(Process process) throws Exception
{
String name = process.args.consumeString();
if (name == null)
{
return super.help(process);
}
boolean hit = false;
List<ObjectName> objectNames = findObjectNames(getLeft(name));
String operationPattern = Utils.ensure(getRight(name), "*");
for (ObjectName objectName : objectNames)
{
MBeanInfo info = getMBeanInfo(objectName);
List<MBeanOperationInfo> operationInfos = findMBeanOperationInfos(info, operationPattern, -1);
for (MBeanOperationInfo operationInfo : operationInfos)
{
StringBuilder builder = new StringBuilder(getNames()[0]);
builder.append(" ").append(getName(objectName));
builder.append(".").append(operationInfo.getName());
MBeanParameterInfo[] parameterInfos = operationInfo.getSignature();
for (MBeanParameterInfo parameterInfo : parameterInfos)
{
builder.append(" ").append(toParameter(toClass(parameterInfo.getType())));
}
process.out.println(builder);
hit = true;
}
}
if (!hit)
{
throw new ArgumentException(String.format("No bean operation matches %s", name));
}
return 0;
}
/**
* {@inheritDoc}
*
* @see net.sourceforge.eclipsejetty.starter.console.AbstractCommand#getHelpDescription()
*/
@Override
protected String getHelpDescription()
{
return "Access local MBeans.\n\n" //
+ "Commands:\n" //
+ "---------\n\n" //
+ "list, l List available MBeans.\n"
+ "attr, a <NAME>[.<ATTR>] Shows the attributes.\n"
+ "call, c <NAME>.<OPER> {<PARAMS>} Calls the specified operation.\n"//
+ "help, h, ? <NAME>[.<OPER>] Show help for the specified MBean.\n\n"
+ "The <NAME>, <ATTR> and <OPER> arguments may contain wildcards. If the operation or attribute is unambigous ,"
+ "you can omit the command.\n\n"
+ "The command does not support all types of operations (e.g. commands with arrays are not supported). If you "
+ "need to call these operations, use JConsole or VisualVM.";
}
/**
* {@inheritDoc}
*
* @see net.sourceforge.eclipsejetty.starter.console.Command#getOrdinal()
*/
public int getOrdinal()
{
return 2000;
}
/**
* {@inheritDoc}
*
* @see net.sourceforge.eclipsejetty.starter.console.Command#execute(java.lang.String,
* net.sourceforge.eclipsejetty.starter.console.Process)
*/
public int execute(String commandName, Process process) throws Exception
{
String command = process.args.consumeString();
if ((command == null) || ("list".equalsIgnoreCase(command)) || ("l".equalsIgnoreCase(command)))
{
return list(process);
}
if (("attr".equalsIgnoreCase(command)) || ("a".equalsIgnoreCase(command)))
{
return attr(process);
}
if (("call".equalsIgnoreCase(command)) || ("c".equalsIgnoreCase(command)))
{
return call(process);
}
if (("help".equalsIgnoreCase(command)) || ("h".equalsIgnoreCase(command)) || ("?".equalsIgnoreCase(command)))
{
return help(process);
}
return guess(process, command);
}
protected int list(Process process) throws MalformedObjectNameException
{
@SuppressWarnings("unchecked")
Set<ObjectName> objectNames = server.queryNames(new ObjectName("*.*:*"), null);
for (ObjectName objectName : objectNames)
{
process.out.println(getName(objectName));
}
return 0;
}
protected int attr(Process process) throws MalformedObjectNameException, IntrospectionException,
InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException
{
String name = process.args.consumeString();
if (name == null)
{
throw new ArgumentException("<NAME> is missing.");
}
boolean hit = false;
List<ObjectName> objectNames = findObjectNames(getLeft(name));
String attributePattern = Utils.ensure(getRight(name), "*");
for (ObjectName objectName : objectNames)
{
MBeanInfo info = getMBeanInfo(objectName);
List<MBeanAttributeInfo> attributeInfos = findMBeanAttributeInfos(info, attributePattern);
hit = attributeInfos.size() > 0;
attr(process, objectName, attributeInfos);
}
if (!hit)
{
throw new ArgumentException(String.format("Not matching attribute found: %s %s", name, attributePattern));
}
return 0;
}
private int attr(Process process, ObjectName objectName, List<MBeanAttributeInfo> attributeInfos)
{
for (MBeanAttributeInfo attributeInfo : attributeInfos)
{
Object value;
try
{
value = server.getAttribute(objectName, attributeInfo.getName());
}
catch (Exception e)
{
value = String.format("<%s: %s>", e.getClass().getName(), e.getMessage());
}
process.out.println(String.format("%s.%s = %s", getName(objectName), attributeInfo.getName(),
toString(value)));
}
return 0;
}
protected int call(Process process) throws MalformedObjectNameException, IntrospectionException,
InstanceNotFoundException, ReflectionException, InstanceAlreadyExistsException, NotCompliantMBeanException,
MBeanRegistrationException, MBeanException
{
String name = process.args.consumeString();
if (name == null)
{
throw new ArgumentException("<NAME> is missing.");
}
ObjectName objectName = findObjectName(getLeft(name));
if (objectName == null)
{
throw new ArgumentException(String.format("No match for %s.", name));
}
MBeanInfo info = getMBeanInfo(objectName);
MBeanOperationInfo operationInfo =
findMBeanOperationInfo(info, Utils.ensure(getRight(name), "*"), process.args.size());
if (operationInfo == null)
{
throw new ArgumentException(String.format("No match for %s with %s arguments.", name, process.args.size()));
}
return call(process, objectName, operationInfo);
}
protected int call(Process process, ObjectName objectName, MBeanOperationInfo operationInfo)
throws ReflectionException, InstanceNotFoundException, MBeanException
{
List<Object> params = new ArrayList<Object>();
List<String> sig = new ArrayList<String>();
MBeanParameterInfo[] signature = operationInfo.getSignature();
for (int i = 0; i < signature.length; i += 1)
{
try
{
params.add(convert(signature[i].getType(), process.args.consumeString()));
sig.add(signature[i].getType());
}
catch (Exception e)
{
throw new ArgumentException(String.format("Invalid argument #%d: %s", (i + 1), e.toString()));
}
}
Object result =
server.invoke(objectName, operationInfo.getName(), params.toArray(), sig.toArray(new String[sig.size()]));
if ("void".equals(operationInfo.getReturnType()))
{
process.out.println("Invocation successful.");
}
else
{
process.out.println(toString(result));
}
return -1;
}
protected int guess(Process process, String name) throws IntrospectionException, InstanceNotFoundException,
ReflectionException, MBeanException, MalformedObjectNameException
{
ObjectName objectName = findObjectName(getLeft(name));
if (objectName == null)
{
throw new ArgumentException(String.format("Unknown command: %s", name));
}
MBeanInfo info = getMBeanInfo(objectName);
if (process.args.size() == 0)
{
List<MBeanAttributeInfo> attributeInfos = findMBeanAttributeInfos(info, getRight(name));
if (attributeInfos.size() > 0)
{
return attr(process, objectName, attributeInfos);
}
}
MBeanOperationInfo operationInfo =
findMBeanOperationInfo(info, Utils.ensure(getRight(name), "*"), process.args.size());
if (operationInfo != null)
{
return call(process, objectName, operationInfo);
}
throw new ArgumentException(String.format("Invalid argument: %s", name));
}
protected MBeanAttributeInfo findMBeanAttributeInfo(MBeanInfo info, String attributeName)
{
List<MBeanAttributeInfo> results = findMBeanAttributeInfos(info, attributeName);
if (results.size() == 0)
{
return null;
}
if (results.size() > 1)
{
throw new ArgumentException(String.format("Non-unique match for %s", attributeName));
}
return results.get(0);
}
protected List<MBeanAttributeInfo> findMBeanAttributeInfos(MBeanInfo info, String attributePattern)
{
List<MBeanAttributeInfo> results = new ArrayList<MBeanAttributeInfo>();
if (attributePattern != null)
{
for (MBeanAttributeInfo attributeInfo : info.getAttributes())
{
if (WildcardUtils.match(attributeInfo.getName().toLowerCase(), attributePattern.toLowerCase()))
{
results.add(attributeInfo);
}
}
}
return results;
}
protected MBeanOperationInfo findMBeanOperationInfo(MBeanInfo info, String operationName, int signatureLength)
{
List<MBeanOperationInfo> results = findMBeanOperationInfos(info, operationName, signatureLength);
if (results.size() == 0)
{
return null;
}
if (results.size() > 1)
{
throw new ArgumentException(String.format("Non-unique match for %s", operationName));
}
return results.get(0);
}
protected List<MBeanOperationInfo> findMBeanOperationInfos(MBeanInfo info, String operationPattern,
int signatureLength)
{
List<MBeanOperationInfo> results = new ArrayList<MBeanOperationInfo>();
if (operationPattern != null)
{
for (MBeanOperationInfo operationInfo : info.getOperations())
{
if (!isSupported(operationInfo))
{
continue;
}
if ((signatureLength >= 0) && (operationInfo.getSignature().length != signatureLength))
{
continue;
}
if (WildcardUtils.match(operationInfo.getName().toLowerCase(), operationPattern.toLowerCase()))
{
results.add(operationInfo);
}
}
}
return results;
}
protected MBeanInfo findMBeanInfo(String name) throws MalformedObjectNameException, IntrospectionException,
InstanceNotFoundException, ReflectionException
{
ObjectName objectName = findObjectName(name);
return getMBeanInfo(objectName);
}
private MBeanInfo getMBeanInfo(ObjectName objectName) throws ReflectionException, IntrospectionException,
InstanceNotFoundException
{
if (objectName == null)
{
return null;
}
return server.getMBeanInfo(objectName);
}
protected ObjectName findObjectName(String name) throws MalformedObjectNameException
{
List<ObjectName> results = findObjectNames(name);
if (results.size() == 0)
{
return null;
}
if (results.size() > 1)
{
throw new ArgumentException(String.format("Non-unique match for %s", name));
}
return results.get(0);
}
protected List<ObjectName> findObjectNames(String pattern) throws MalformedObjectNameException
{
List<ObjectName> results = new ArrayList<ObjectName>();
if (pattern != null)
{
@SuppressWarnings("unchecked")
Set<ObjectName> objectNames = server.queryNames(new ObjectName("*.*:*"), null);
for (ObjectName objectName : objectNames)
{
if (WildcardUtils.match(getName(objectName).toLowerCase(), pattern.toLowerCase()))
{
results.add(objectName);
}
}
}
return results;
}
protected static String getName(ObjectName objectName)
{
String name = objectName.getKeyProperty("name");
if (name == null)
{
name = objectName.getKeyProperty("type");
}
name = name.replace(' ', '_');
return name;
}
protected static String getLeft(String argument)
{
if (argument == null)
{
return null;
}
int index = argument.lastIndexOf('.');
if (index < 0)
{
return argument;
}
return argument.substring(0, index);
}
protected static String getRight(String argument)
{
if (argument == null)
{
return null;
}
int index = argument.lastIndexOf('.');
if (index < 0)
{
return null;
}
return argument.substring(index + 1);
}
protected static Class<?> toClass(String name)
{
if ("boolean".equals(name))
{
return Boolean.TYPE;
}
if ("byte".equals(name))
{
return Byte.TYPE;
}
if ("short".equals(name))
{
return Short.TYPE;
}
if ("int".equals(name))
{
return Integer.TYPE;
}
if ("long".equals(name))
{
return Long.TYPE;
}
if ("float".equals(name))
{
return Float.TYPE;
}
if ("double".equals(name))
{
return Double.TYPE;
}
if ("char".equals(name))
{
return Character.TYPE;
}
try
{
return Class.forName(name);
}
catch (ClassNotFoundException e)
{
return null;
}
}
protected static boolean isSupported(MBeanOperationInfo operationInfo)
{
MBeanParameterInfo[] signature = operationInfo.getSignature();
for (MBeanParameterInfo parameterInfo : signature)
{
if (!isArgumentSupported(parameterInfo.getType()))
{
return false;
}
}
return true;
}
protected static Object convert(String typeName, String value) throws IllegalArgumentException
{
if ("null".equals(value))
{
return null;
}
Class<?> type = toClass(typeName);
if (type == null)
{
throw new IllegalArgumentException(String.format("Unsupported parameter %s", toParameter(null)));
}
if ((type.isAssignableFrom(Boolean.class)) || (type == Boolean.TYPE))
{
return Boolean.valueOf(value);
}
if ((type.isAssignableFrom(Byte.class)) || (type == Boolean.TYPE))
{
return Byte.decode(value);
}
if ((type.isAssignableFrom(Short.class)) || (type == Short.TYPE))
{
return Short.decode(value);
}
if ((type.isAssignableFrom(Integer.class)) || (type == Integer.TYPE))
{
return Integer.decode(value);
}
if ((type.isAssignableFrom(Long.class)) || (type == Long.TYPE))
{
return Long.decode(value);
}
if ((type.isAssignableFrom(Float.class)) || (type == Float.TYPE))
{
return Float.valueOf(value);
}
if ((type.isAssignableFrom(Double.class)) || (type == Double.TYPE))
{
return Double.valueOf(value);
}
if ((type.isAssignableFrom(Character.class)) || (type == Character.TYPE))
{
if (value.length() != 1)
{
throw new IllegalArgumentException(String.format("Invalid character: %s", value));
}
return new Character(value.charAt(0));
}
if (type.isAssignableFrom(String.class))
{
return value;
}
throw new IllegalArgumentException(String.format("Unsupported parameter %s", toParameter(type)));
}
protected static boolean isArgumentSupported(String typeName)
{
Class<?> type = toClass(typeName);
if (type.isArray())
{
return false;
}
if (type.isPrimitive())
{
return true;
}
if (type.isAssignableFrom(Boolean.class))
{
return true;
}
if (type.isAssignableFrom(Number.class))
{
return true;
}
if (type.isAssignableFrom(Character.class))
{
return true;
}
if (type.isAssignableFrom(String.class))
{
return true;
}
return false;
}
protected static String toParameter(Class<?> typeName)
{
if (typeName == null)
{
typeName = Object.class;
}
return "<" + typeName.getSimpleName() + ">";
}
protected static String toString(Object object)
{
if (object == null)
{
return null;
}
if (object.getClass().isArray())
{
Class<?> componentType = object.getClass().getComponentType();
if (Boolean.TYPE == componentType)
{
return Arrays.toString((boolean[]) object);
}
if (Byte.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((byte[]) object);
}
if (Short.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((short[]) object);
}
if (Integer.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((int[]) object);
}
if (Long.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((long[]) object);
}
if (Float.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((float[]) object);
}
if (Double.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((double[]) object);
}
if (Character.TYPE == object.getClass().getComponentType())
{
return Arrays.toString((char[]) object);
}
return Arrays.toString((Object[]) object);
}
return object.toString();
}
}