/*
* Copyright 2013 The Generic MBean CLI Project
*
* The Generic MBean CLI Project 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 com.sohail.alam.generic.mbean.cli.jmx;
import com.sohail.alam.generic.mbean.cli.CliProperties;
import com.sohail.alam.generic.mbean.cli.HelperMethods;
import com.sohail.alam.generic.mbean.cli.logger.Logger;
import com.sohail.alam.generic.mbean.cli.security.Authentication;
import com.sohail.alam.generic.mbean.cli.security.DefaultAuthentication;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
* TODO: After JMX Connection Closed
* Any Attribute/Operation commands tells the user to connect to a JMX server before executing it
*/
/**
* <p/>
* The Default implementation of {@link JMXInitializer} interface.
* <p/>
*
* @author Sohail Alam
* @version 1.0.0 Date: 19/5/13
* Time: 6:39 PM
* @since 1.0.0
*/
public class DefaultJMXInitializer implements JMXInitializer {
/**
* The constant PING_TIMEOUT.
*/
public static final int PING_TIMEOUT = 2000;
/**
* The PROPERTIES.
*/
private final CliProperties PROPERTIES;
/**
* The AUTHENTICATION.
*/
private final Authentication AUTHENTICATION;
/**
* The Filter object names.
*/
private final ArrayList<String> filterObjectNames;
/**
* The BEAN OBJECT NAMES.
*/
private final ArrayList<ObjectName> BEAN_OBJECT_NAMES;
/**
* The Storage.
*/
private DataStorage storage;
/**
* The Jmxc.
*/
private JMXConnector jmxc = null;
/**
* The Mbsc.
*/
private MBeanServerConnection mbsc = null;
/**
* Instantiates a new JMX initializer implementation.
*/
private DefaultJMXInitializer() {
PROPERTIES = CliProperties.getInstance();
AUTHENTICATION = DefaultAuthentication.getInstance();
BEAN_OBJECT_NAMES = new ArrayList<ObjectName>();
// All Beans containing these as the domain name will be rejected
filterObjectNames = new ArrayList<String>();
// JAVA DEFAULTS
filterObjectNames.add("com.sun.management");
filterObjectNames.add("java.lang");
filterObjectNames.add("java.util.logging");
filterObjectNames.add("JMImplementation");
// EXTRAS
filterObjectNames.add("org.eclipse.jetty.jmx");
filterObjectNames.add("org.eclipse.jetty.util.log");
filterObjectNames.add("org.eclipse.jetty.server");
filterObjectNames.add("org.eclipse.jetty.server.handler");
}
/**
* Gets instance.
*
* @return the instance
*/
public static JMXInitializer getInstance() {
return SingletonHolder.instance;
}
/**
* Connect boolean.
*
* @return the boolean
*/
@Override
public boolean connect() {
String jmxIP = PROPERTIES.getJmxIP();
int jmxPort = PROPERTIES.getJmxPort();
try {
InetAddress inetAddress = InetAddress.getByName(jmxIP);
if (inetAddress.isReachable(PING_TIMEOUT)) {
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi:///jndi/rmi://" +
jmxIP + ":" +
jmxPort + "/jmxrmi");
jmxc = JMXConnectorFactory.connect(url);
jmxc.addConnectionNotificationListener(new NotificationListener() {
@Override
public void handleNotification(Notification notification, Object handback) {
String notificationMessage = notification.getMessage();
String notificationType = notification.getType();
long notificationSequenceNumber = notification.getSequenceNumber();
Logger.logMessage("## JMX NOTIFICATION ##");
Logger.logMessage("Notification Sequence Number : " + notificationSequenceNumber);
Logger.logMessage("Notification Type : " + notificationType);
Logger.logMessage("Notification Message : " + notificationMessage);
if (notificationType.contains("jmx.remote.connection.failed")) {
System.err.println("## THE JMX SERVER CONNECTION HAS FAILED ##");
HelperMethods.getInstance().exit(-1);
}
}
}, null, null);
mbsc = jmxc.getMBeanServerConnection();
Logger.logMessage("You have successfully logged in to JMX Server: " +
jmxIP + ":" + jmxPort);
// Initialize Data Storage
initializeDataStorage();
} else {
Logger.logMessage("The JMX Server Address -> " +
jmxIP + ":" + jmxPort +
" is not reachable!! Please Try Again");
}
return true;
} catch (Exception e) {
jmxc = null;
mbsc = null;
Logger.logMessage("Can not login to JMX Server: " +
jmxIP + ":" + jmxPort);
Logger.logException(e, getClass());
return false;
}
}
/**
* Initialize data storage.
*
* @throws IOException the iO exception
* @throws IOException the iO exception
* @throws IOException the iO exception
* @throws IOException the iO exception
*/
private void initializeDataStorage() throws IOException, InstanceNotFoundException, IntrospectionException, ReflectionException {
// Get all the MBean Object Names
Set<ObjectName> beanObjectNames = mbsc.queryNames(null, null);
// Iterate all these
for (ObjectName beanObjectName : beanObjectNames) {
String domainName = beanObjectName.getDomain();
// Filtering out the unnecessary beans
if (!filterObjectNames.contains(domainName)) {
// Store the MBean Object Name
BEAN_OBJECT_NAMES.add(beanObjectName);
// Create an instance of DataStorage
storage = DefaultDataStorage.getInstance(beanObjectName);
// gets the information from MBean server
MBeanInfo mBeanInfo = getMbsc().getMBeanInfo(beanObjectName);
// Get the attributes info and put them in the map
MBeanAttributeInfo[] attributes = mBeanInfo.getAttributes();
for (MBeanAttributeInfo mBeanAttributeInfo : attributes) {
String name = mBeanAttributeInfo.getName();
String className = mBeanAttributeInfo.getType();
String readable = String.valueOf(mBeanAttributeInfo.isReadable());
String writeable = String.valueOf(mBeanAttributeInfo.isWritable());
String isGetter = String.valueOf(mBeanAttributeInfo.isIs());
String description = mBeanAttributeInfo.getDescription();
storage.addToBeanAttribute(name, className, readable, writeable, isGetter, description);
}
// Get the operations info and put them in the map
MBeanOperationInfo[] operations = mBeanInfo.getOperations();
for (MBeanOperationInfo mBeanOperationInfo : operations) {
String name = mBeanOperationInfo.getName();
String description = mBeanOperationInfo.getDescription();
String returnType = mBeanOperationInfo.getReturnType();
MBeanParameterInfo[] mBeanParameterInfo = mBeanOperationInfo.getSignature();
ArrayList<Map<String, String>> parameters = new ArrayList<Map<String, String>>();
Map<String, String> parameterInfo;
for (MBeanParameterInfo info : mBeanParameterInfo) {
// TODO: change HashMap to something which can guaranty ordering of the parameters
parameterInfo = new HashMap<String, String>();
parameterInfo.put(DataStorage.NAME, info.getName());
parameterInfo.put(DataStorage.TYPE, info.getType());
parameterInfo.put(DataStorage.DESCRIPTION, info.getDescription());
parameters.add(parameterInfo);
}
storage.addToMBeanOperations(name, description, returnType, parameters);
}
}
}
}
/**
* Disconnect boolean.
*
* @return the boolean
*/
@Override
public boolean disconnect() {
if (jmxc != null) {
try {
jmxc.close();
Logger.logMessage("You have successfully disconnected from JMX Server: " +
PROPERTIES.getJmxIP() + ":" + PROPERTIES.getJmxPort());
// AUTHENTICATION.logout();
storage.purgeDataStorage();
getMBeanObjectNames().clear();
} catch (IOException e) {
Logger.logException(e, getClass());
return false;
}
}
return true;
}
/**
* Gets jmxc.
*
* @return the jmxc
*/
@Override
public JMXConnector getJmxc() {
return jmxc;
}
/**
* Gets mbsc.
*
* @return the mbsc
*/
@Override
public MBeanServerConnection getMbsc() {
return mbsc;
}
/**
* Reload the Data Structure containing the MBean Information
*/
@Override
public void reload() {
try {
initializeDataStorage();
} catch (Exception ex) {
Logger.logException(ex, getClass());
}
}
/**
* Gets bean object names.
*
* @return the bean object names
*/
@Override
public ArrayList<ObjectName> getMBeanObjectNames() {
return BEAN_OBJECT_NAMES;
}
/**
* Gets MBean object name.
*
* @param mbeanName the mbean name
*
* @return the m bean object name
*/
@Override
public ObjectName getMBeanObjectName(String mbeanName) {
for (ObjectName beanObjectName : getMBeanObjectNames()) {
if (beanObjectName.getCanonicalName().equalsIgnoreCase(mbeanName)) {
return beanObjectName;
}
}
return null;
}
/**
* Gets m bean object names.
*
* @param mbeanName the mbean name
*
* @return the m bean object names
*/
@Override
public ArrayList<ObjectName> getMBeanObjectNames(String mbeanName) {
ArrayList<ObjectName> objectNameArrayList = new ArrayList<ObjectName>();
String userMBeanName;
boolean startsWithWildcard = false;
boolean endsWithWildcard = false;
if (mbeanName.contains("*")) {
startsWithWildcard = mbeanName.startsWith("*");
endsWithWildcard = mbeanName.endsWith("*");
userMBeanName = mbeanName.replaceAll("[*]", "").toLowerCase();
if (startsWithWildcard && endsWithWildcard) {
for (ObjectName beanObjectName : getMBeanObjectNames()) {
if (beanObjectName.getCanonicalName().toLowerCase().contains(userMBeanName)) {
objectNameArrayList.add(beanObjectName);
}
}
} else if (startsWithWildcard) {
for (ObjectName beanObjectName : getMBeanObjectNames()) {
if (beanObjectName.getCanonicalName().toLowerCase().endsWith(userMBeanName)) {
objectNameArrayList.add(beanObjectName);
}
}
} else if (endsWithWildcard) {
for (ObjectName beanObjectName : getMBeanObjectNames()) {
if (beanObjectName.getCanonicalName().toLowerCase().startsWith(userMBeanName)) {
objectNameArrayList.add(beanObjectName);
}
}
}
} else {
for (ObjectName beanObjectName : getMBeanObjectNames()) {
if (beanObjectName.getCanonicalName().equalsIgnoreCase(mbeanName)) {
objectNameArrayList.add(beanObjectName);
}
}
}
return objectNameArrayList;
}
/**
* Sets iP.
*
* @param ip the jmx_ip
*/
@Override
public void setIP(String ip) {
//TODO: Validate the IP Address
if (true) {
Logger.logTrace("Setting JMX IP -> " + ip, getClass(), false);
PROPERTIES.setJmxIP(ip);
} else {
Logger.logMessage("The given IP is not in the valid IP Address format");
}
}
/**
* Sets jmx_port.
*
* @param port the jmx_port
*/
@Override
public void setPort(int port) {
Logger.logTrace("Setting JMX PORT -> " + port, getClass(), false);
//TODO: Validate the Port Number
if (true) {
PROPERTIES.setJmxPort(port);
} else {
Logger.logMessage("Port Number is out of range (1-65535)");
}
}
/**
* The type Singleton holder.
* <p/>
* Initialization on Demand Holder (IODH) idiom which requires very little code and
* has zero synchronization overhead. Zero, as in even faster than volatile.
* IODH requires the same number of lines of code as plain old synchronization, and it's faster than DCL!
* <p/>
* {@code SOURCE: http://blog.crazybob.org/2007/01/lazy-loading-singletons.html}
*/
static class SingletonHolder {
/**
* The Instance.
*/
static JMXInitializer instance = new DefaultJMXInitializer();
}
}