/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.plugin.tomcat; import java.io.IOException; import java.rmi.RemoteException; import java.util.Arrays; import java.util.HashMap; import java.util.Properties; import javax.management.Attribute; import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.InvalidAttributeValueException; import javax.management.MBeanInfo; import javax.management.MalformedObjectNameException; import javax.management.MBeanException; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.RuntimeMBeanException; import javax.management.j2ee.statistics.CountStatistic; import javax.management.j2ee.statistics.RangeStatistic; import javax.management.j2ee.statistics.Stats; import javax.management.j2ee.statistics.Statistic; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; //import org.hyperic.hq.plugin.jboss.jmx.ServerQuery; import org.hyperic.hq.product.ControlPlugin; import org.hyperic.hq.product.Metric; import org.hyperic.hq.product.MetricInvalidException; import org.hyperic.hq.product.MetricNotFoundException; import org.hyperic.hq.product.MetricUnreachableException; import org.hyperic.hq.product.PluginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class JBossUtil { private static final String PROP_NAMING_CONNECTOR = "connector.jndi.name"; private static final String JNDI_FACTORY = "org.jboss.security.jndi.JndiLoginInitialContextFactory"; private static final String PROP_JNP_TIMEOUT = "jnp.timeout"; private static final String PROP_JNP_SOTIMEOUT = "jnp.sotimeout"; private static final String DEFAULT_JNP_TIMEOUT = System.getProperty(PROP_JNP_TIMEOUT, String.valueOf(30 * 1000)); private static final String DEFAULT_JNP_SOTIMEOUT = System.getProperty(PROP_JNP_SOTIMEOUT, DEFAULT_JNP_TIMEOUT); //http://wiki.jboss.org/wiki/Wiki.jsp?page=NamingContextFactory private static final String[][] NAMING_PROPS = { { PROP_NAMING_CONNECTOR, "jmx/rmi/RMIAdaptor" }, { Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory" }, { Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces" }, { "jnp.disableDiscovery", "true" }, { PROP_JNP_TIMEOUT, DEFAULT_JNP_TIMEOUT }, { PROP_JNP_SOTIMEOUT, DEFAULT_JNP_SOTIMEOUT } }; private static final String[] STAT_PROVIDER = { "StatisticsProvider", "Stats" }; private static final String[] STAT_PROVIDER_4 = { "statisticsProvider", "stats" }; private static Log log = LogFactory.getLog("JBossUtil"); public static MBeanServerConnection getMBeanServerConnection(Properties config) throws NamingException, RemoteException { MBeanServerConnection adaptor; Properties props = new Properties(); for (int i=0; i<NAMING_PROPS.length; i++) { props.setProperty(NAMING_PROPS[i][0], NAMING_PROPS[i][1]); } props.putAll(config); props.put("java.naming.provider.url", config.get("jmx.url")); if (props.getProperty(Context.SECURITY_PRINCIPAL) != null) { props.setProperty(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); } InitialContext ctx = new InitialContext(props); try { Object o=ctx.lookup(props.getProperty(PROP_NAMING_CONNECTOR)); log.debug("=> "+Arrays.asList(o.getClass().getInterfaces())); adaptor = (MBeanServerConnection)o; } finally { ctx.close(); } return adaptor; } /* * @deprecated */ public static MBeanServerConnection getMBeanServerConnection(String url) throws NamingException, RemoteException { Properties config = new Properties(); config.setProperty(Context.PROVIDER_URL, url); return getMBeanServerConnection(config); } private static String getServerURL(Metric metric) { return metric.getProperties().getProperty(Context.PROVIDER_URL); } public static MBeanServerConnection getMBeanServerConnection(Metric metric) throws NamingException, RemoteException { return getMBeanServerConnection(metric.getProperties()); } //we only cache RemoteMBeanServerConnection handles for measurements. //jndi lookup won't have much impact for control or discovery private static HashMap serverCache = new HashMap(); // Maps attrNames to their lowercase equivalents. After static initialization, // get-only access. private static HashMap jsr77LowerCase = new HashMap(); // Maps urls to a Boolean indicating whether or not the jsr77 attrNames // from the url are lowercase or not. Needs synchronization. private static HashMap lowerCaseURLMappings = new HashMap(); // //thanks JBoss, for changing the attribute case in 3.2.8 // static { // String[] attrs = { // JBossMeasurementPlugin.ATTR_STATE_MANAGEABLE, // }; // for (int i=0; i<attrs.length; i++) { // String attr = attrs[i]; // String lcAttr = // Character.toLowerCase(attr.charAt(0)) + // attr.substring(1); // jsr77LowerCase.put(attr, lcAttr); // } // } private static MetricInvalidException invalid(Metric metric, Exception e) { String msg = "Malformed ObjectName [" + metric.getObjectName() + "]"; return new MetricInvalidException(msg, e); } private static MetricUnreachableException unreachable(Metric metric, Exception e) { String msg = "Can't connect to MBeanServerConnection [" + metric.toDebugString() + "]: " + e; return new MetricUnreachableException(msg, e); } private static MetricNotFoundException notfound(Metric metric, Exception e) { String msg = "Metric not found [" + metric.toDebugString() + "]: " + e; return new MetricNotFoundException(msg, e); } private static PluginException error(Metric metric, Exception e) { String msg = "Invocation error [" + metric.toDebugString() + "]: " + e; return new PluginException(msg, e); } private static PluginException error(Metric metric, Exception e, String method) { String msg = "Method '" + method + "' invocation error [" + metric.toDebugString() + "]: " + e; return new PluginException(msg, e); } //dealing with the attribute case change had been done with the HQ server //types JBoss 3.2 and JBoss 4.0, but then 3.2.8 threw that off by making //the same changes as 4.0 // static void determineJSR77Case(String url, MBeanServerConnection mServer) { // try { // ObjectName server = // new ObjectName(ServerQuery.SERVER_NAME); // String version = // (String)mServer.getAttribute(server, ServerQuery.ATTR_VERSION); // if (version.length() < 5) { // return; // } // boolean lc; // int majorVersion = Character.getNumericValue(version.charAt(0)); // if (majorVersion >= 4) { // //4.x, 5.x // lc = true; // } // else if (Character.getNumericValue(version.charAt(4)) >= 8) { // //3.2.8 // lc = true; // } // else { // //<= 3.2.7 // lc = false; // } // // synchronized (lowerCaseURLMappings) { // lowerCaseURLMappings.put(url, lc ? Boolean.TRUE : Boolean.FALSE); // } // // if (log.isDebugEnabled()) { // log.debug(url + " " + version + " jsr77LowerCase=" + lc); // } // } catch (Exception e) { // //unlikely, but in this case, leave it to the server type to determine the case // } // } static Object getRemoteMBeanValue(Metric metric) throws MetricNotFoundException, MetricInvalidException, MetricUnreachableException, PluginException { MBeanServerConnection mServer = null; boolean cached = true; String url = getServerURL(metric); synchronized (serverCache) { mServer = (MBeanServerConnection)serverCache.get(url); } if (mServer == null) { cached = false; try { mServer = getMBeanServerConnection(metric); //jndi lookup } catch (NamingException e) { throw unreachable(metric, e); } catch (RemoteException e) { throw unreachable(metric, e); } // determineJSR77Case(url, mServer); synchronized (serverCache) { serverCache.put(url, mServer); } } String attrName = metric.getAttributeName(); boolean lc; Boolean jsr77Case; synchronized (lowerCaseURLMappings) { jsr77Case = (Boolean) lowerCaseURLMappings.get(url); } if (jsr77Case != null) { lc = jsr77Case.booleanValue(); } else { lc = Character.isLowerCase(attrName.charAt(0)); } String lcAttr; //another 3.2.8 hack if (lc && ((lcAttr = (String)jsr77LowerCase.get(attrName)) != null)) { attrName = lcAttr; } try { ObjectName objName = new ObjectName(metric.getObjectName()); if (attrName.substring(1).startsWith(/*S*/"tatistic")) { return getJSR77Statistic(mServer, objName, metric, lc); } else if (attrName.equals("__INSTANCE__")) { //cheap hack for an avail metric for MBeans that dont //have anything better we can use, e.g. Hibernate. try { mServer.getObjectInstance(objName); return Boolean.TRUE; } catch (Exception e) { return Boolean.FALSE; } } else { return mServer.getAttribute(objName, attrName); } } catch (MalformedObjectNameException e) { throw invalid(metric, e); } catch (InstanceNotFoundException e) { throw notfound(metric, e); } catch (AttributeNotFoundException e) { //XXX not all MBeans have a reasonable attribute to //determine availability, so just assume if we get this far //the MBean exists and is alive. if (attrName.equals(Metric.ATTR_AVAIL)) { return new Double(Metric.AVAIL_UP); } throw notfound(metric, e); } catch (ReflectionException e) { throw error(metric, e); } catch (MBeanException e) { throw error(metric, e); } catch (RuntimeMBeanException e) { throw error(metric, e); } catch (Exception e) { //CommunicationException, NamingException, RemoteException, etc. if (cached) { //retry once, in the event the cached connection was stale synchronized (serverCache) { serverCache.remove(url); } log.debug("MBeanServerConnection cache cleared for " + url); return getRemoteMBeanValue(metric); } else { throw unreachable(metric, e); } } } // static Double getJSR77Statistic(MBeanServerConnection mServer, ObjectName objName, Metric metric, boolean lc) throws MetricNotFoundException, MetricInvalidException, MetricUnreachableException, PluginException { //jboss changed attribute case in version 4.0 String[] attrs; if (lc) { attrs = STAT_PROVIDER_4; } else { attrs = STAT_PROVIDER; } Stats stats; try { Boolean provider = (Boolean) mServer.getAttribute(objName, attrs[0]); if ((provider == null) || !provider.booleanValue()) { String msg = "MBeanServerConnection does not provide statistics"; throw new PluginException(msg); } stats = (Stats)mServer.getAttribute(objName, attrs[1]); } catch (RemoteException e) { throw unreachable(metric, e); } catch (InstanceNotFoundException e) { throw notfound(metric, e); } catch (AttributeNotFoundException e) { throw notfound(metric, e); } catch (ReflectionException e) { throw error(metric, e); } catch (MBeanException e) { throw error(metric, e); } catch (IOException e) { throw error(metric, e); } if (stats == null) { throw new PluginException("MBeanServerConnection has no stats"); } String statName = metric.getAttributeName().substring(9); Statistic stat = stats.getStatistic(statName); if (stat == null) { String msg = "Statistic '" + statName + "' not found [" + metric + "]"; throw new MetricNotFoundException(msg); } long value; if (stat instanceof CountStatistic) { value = ((CountStatistic)stat).getCount(); } else if (stat instanceof RangeStatistic) { value = ((RangeStatistic)stat).getCurrent(); } else { String msg = "Unsupported statistic type [" + statName.getClass().getName() + " for [" + metric + "]"; throw new MetricInvalidException(msg); } return new Double(value); } // // public static Object invoke(Metric metric, String method) // throws MetricUnreachableException, // MetricNotFoundException, // PluginException { // return invoke(metric, method, new Object[0], new String[0]); // } // // private static Object setAttribute(MBeanServerConnection mServer, ObjectName obj, // String name, Object value) // throws MetricUnreachableException, // MetricNotFoundException, // PluginException, // ReflectionException, // InstanceNotFoundException, // MBeanException, // IOException { // // if (name.startsWith("set")) { // name = name.substring(3); // } // // Attribute attr = new Attribute(name, value); // // try { // mServer.setAttribute(obj, attr); // } catch (AttributeNotFoundException e) { // throw new MetricNotFoundException(e.getMessage(), e); // } catch (InvalidAttributeValueException e) { // throw new ReflectionException(e); // } // // return null; // } // // public static Object invoke(Metric metric, String method, // Object[] args, String[] sig) // throws MetricUnreachableException, // MetricNotFoundException, // PluginException { // try { // MBeanServerConnection mServer = getMBeanServerConnection(metric); // ObjectName obj = new ObjectName(metric.getObjectName()); // MBeanInfo info = mServer.getMBeanInfo(obj); // // if (sig.length == 0) { // MBeanUtil.OperationParams params = // MBeanUtil.getOperationParams(info, method, args); // if (params.isAttribute) { // return setAttribute(mServer, obj, // method, params.arguments[0]); // } // sig = params.signature; // args = params.arguments; // } // // return mServer.invoke(obj, method, args, sig); // } catch (NamingException e) { // throw unreachable(metric, e); // } catch (RemoteException e) { // throw unreachable(metric, e); // } catch (MalformedObjectNameException e) { // throw invalid(metric, e); // } catch (InstanceNotFoundException e) { // throw notfound(metric, e); // } catch (ReflectionException e) { // throw error(metric, e, method); // } catch (IntrospectionException e) { // throw error(metric, e); // } catch (MBeanException e) { // throw error(metric, e, method); // } catch (IOException e) { // throw error(metric, e); // } // } // // public static Metric configureMetric(ControlPlugin plugin, String template) { // log.debug("[configureMetric] template = '"+template+"'"); // template = JBoss5MeasurementPlugin.translateMetic(template, plugin.getConfig()); // log.debug("[configureMetric] template = '"+template+"'"); // String metric = Metric.translate(template, plugin.getConfig()); // // try { // return Metric.parse(metric); //parsing will be cached // } catch (Exception e) { // e.printStackTrace(); //XXX; but aint gonna happen // return null; // } // } }