/*
* Copyright 2011 ClamShell-Cli.
*
* 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.clamshellcli.jmx;
import org.clamshellcli.api.Command;
import org.clamshellcli.api.Context;
import org.clamshellcli.api.IOConsole;
import org.clamshellcli.core.ShellException;
import org.clamshellcli.jmx.Management.VmInfo;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import sun.jvmstat.monitor.MonitoredVm;
/**
* This Command interface implementation provides the JMX "connect" command-line.
* Using this command, users can connect to a local or remote JVM running the
* JMX MBeanServer. Format
*
* <pre>
* <code>connect [
* [host:<hosturl>] [user:<username> password:<password>]
* ] [pid:<procid>]
* </pre>
*
* <ul>
* <li>host: address of host to connect to</li>
* <li>user: username credential</li>
* <li>password: password credential</li>
* <li>pid : connect using JVM pid (from ps command)</li>
* </ul>
*
* Will connect to internal JMX platform mbean server if no connection params
* are provided.
* @author vladimir.vivien
*/
public class ConnectCommand implements Command{
public static final String CMD_NAME = "connect";
public static final String NAMESPACE = "jmx";
public static final String KEY_ARGS_HOST = "host";
public static final String KEY_ARGS_UNAME = "user";
public static final String KEY_ARGS_PWD = "password";
public static final String KEY_ARGS_PID = "pid";
private Command.Descriptor descriptor = null;
public Descriptor getDescriptor() {
return (descriptor != null ) ? descriptor : (
descriptor = new Command.Descriptor() {
public String getNamespace() {
return NAMESPACE;
}
public String getName() {
return CMD_NAME;
}
public String getDescription() {
return "Connects to local or remote JVM MBean server.";
}
public String getUsage() {
return "connect [pid:<ProcessId> host:<HostUrl> user:<UserName> password:<Password>]";
}
Map<String,String> args;
public Map<String, String> getArguments() {
if(args != null) return args;
args = new LinkedHashMap<String,String>();
args.put(KEY_ARGS_PID + ":<ProcessId>", "Local process id of JVM to connect to (see 'ps' command)");
args.put(KEY_ARGS_HOST + ":<HostUrl>", "Connection URL as <hostname>:<port> or service:jmx:<protocol>:<url>");
args.put(KEY_ARGS_UNAME + ":<UserName>", "Username for connection");
args.put(KEY_ARGS_PWD + ":<Password>", "Password for connection");
return args;
}
}
);
}
public Object execute(Context ctx) {
invalidatePreviousConnection(ctx);
Map<String,Object> argsMap = (Map<String,Object>) ctx.getValue(Context.KEY_COMMAND_LINE_ARGS);
Map<Integer,VmInfo> localVms = (Map<Integer,VmInfo>) ctx.getValue(Management.KEY_VMINFO_MAP);
if(localVms == null){
mapVmInfoToContext(ctx);
localVms = (Map<Integer,VmInfo>) ctx.getValue(Management.KEY_VMINFO_MAP);
}
final IOConsole c = ctx.getIoConsole();
MBeanServerConnection serverConnection = null;
JMXConnector connector = null;
String hostAddr = Management.getHostFromArgs(argsMap);
String uname = (argsMap != null && argsMap.get(KEY_ARGS_UNAME) != null)
? (String)argsMap.get(KEY_ARGS_UNAME) : null;
String pwd = (argsMap != null && argsMap.get(KEY_ARGS_PWD) != null)
? (String)argsMap.get(KEY_ARGS_PWD) : null;
Integer pid = null;
if(argsMap != null && argsMap.get(KEY_ARGS_PID) != null){
try{
// normalization due to json behavior
Object val = argsMap.get(KEY_ARGS_PID);
if(val instanceof String){
pid = Integer.valueOf((String)val);
}
if(val instanceof Double){
pid = ((Double)val).intValue();
}
}catch(Exception ex){
throw new ShellException(String.format("Unable to determine "
+ "value for pid [%s]: %s", argsMap.get(KEY_ARGS_PID), ex.getMessage()));
}
}
// extract JMX URL.
JMXServiceURL jmxUrl = null;
try{
jmxUrl = Management.getJmxUrlFrom("localhost");
if (pid != null) {
try{
VmInfo info = getVmInfoFromMap(pid, localVms);
if (info != null) {
hostAddr = info.getAddress();
jmxUrl = Management.getJmxUrlFrom(hostAddr);
}
// if vminfo not cached already, try to find and connect
else{
MonitoredVm mvm = Management.getMonitoredVmFromId(pid);
if(mvm != null){
Management.VmInfo vmInfo = new Management.VmInfo(mvm);
jmxUrl = Management.getJmxUrlFrom(vmInfo.getAddress());
}else{
throw new ShellException(String.format("Unable to find local VM "
+ " with pid value: [%s]", pid));
}
}
}catch(Exception ex){
throw new ShellException (String.format("Unable to understand "
+ "pid value: [%s]%", pid));
}
}
if (hostAddr != null){
jmxUrl = Management.getJmxUrlFrom(hostAddr);
}
ctx.putValue(Management.KEY_JMX_URL, jmxUrl);
}catch(Exception ex){
throw new ShellException(String.format("%nConnection URL "
+ "seems to be invalid: %s", ex.getMessage()));
}
// add credentials info
Map<String, String[]> env = null;
if(uname != null && pwd != null){
env = new HashMap<String,String[]>();
env.put(JMXConnector.CREDENTIALS, new String[]{uname, pwd});
}
// connect
try{
// if pid nor hostaddr provided, use platform/local mbean server
if(pid == null && hostAddr.equals(Management.VALUE_LOCALHOST)){
connector = null;
serverConnection = ManagementFactory.getPlatformMBeanServer();
c.writeOutput(String.format("%nConnected to internal server."));
}else{
connector = JMXConnectorFactory.connect(jmxUrl, env);
serverConnection = connector.getMBeanServerConnection();
c.writeOutput(String.format("%nConnected to server (%s).", connector.getConnectionId()));
}
c.writeOutput(String.format("%n%d MBean(s) registered with server.%n%n", serverConnection.getMBeanCount()));
ctx.putValue(Management.KEY_JMX_CONNECTOR, connector);
ctx.putValue(Management.KEY_JMX_MBEANSERVER, serverConnection);
}catch(Exception ex){
throw new ShellException(String.format("Unable to connect to"
+ " MBeanServer: %s", ex.getMessage()));
}
return null;
}
public void plug(Context plug) {
//throw new UnsupportedOperationException("Not supported yet.");
}
private void mapVmInfoToContext (Context ctx){
try {
Map<Integer,VmInfo> vmMap = Management.mapVmInfo(Management.VALUE_LOCALHOST);
ctx.putValue(Management.KEY_VMINFO_MAP, vmMap);
} catch (Exception ex) {
ctx.putValue(Management.KEY_VMINFO_MAP, null);
}
}
private void invalidatePreviousConnection(Context ctx){
JMXConnector connector = (JMXConnector) ctx.getValue(Management.KEY_JMX_CONNECTOR);
if(connector != null){
try {
connector.close();
} catch (IOException ex) {
throw new ShellException(String.format("Unable to close MBean "
+ "server connection: %s", ex.getMessage()));
}
}
// nullify references
ctx.putValue(Management.KEY_JMX_URL, null);
ctx.putValue(Management.KEY_JMX_CONNECTOR, null);
ctx.putValue(Management.KEY_JMX_MBEANSERVER, null);
}
private VmInfo getVmInfoFromMap(Integer key, Map<Integer,VmInfo> jvmMap){
if(jvmMap == null){
return null;
}
return jvmMap.get(key);
}
}