/* * 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.weblogic; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Hashtable; import java.util.Properties; import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import javax.naming.Context; import javax.naming.NamingException; import org.hyperic.hq.plugin.weblogic.jmx.AttributeGetter; import org.hyperic.hq.plugin.weblogic.jmx.ObjectNameCache; import org.hyperic.hq.plugin.weblogic.jmx.WeblogicAttributes; 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.hyperic.util.config.ConfigResponse; import weblogic.jndi.Environment; import weblogic.management.MBeanHome; import weblogic.management.RemoteMBeanServer; import weblogic.management.runtime.ServerLifeCycleRuntimeMBean; public class WeblogicUtil { private static final boolean useAttrGetter = true; private static final String MBEAN_HOME = "MBeanHome"; // these bean types can only be seen on the admin server. private static final String[] ADMIN_MBEAN_TYPES = { "Application", "WebAppComponent", "EJBComponent", }; private static final HashMap ADMIN_MBEANS; static { HashMap beans = new HashMap(); for (int i = 0; i < ADMIN_MBEAN_TYPES.length; i++) { beans.put(ADMIN_MBEAN_TYPES[i], Boolean.TRUE); } ADMIN_MBEANS = beans; } static Object getRemoteMBeanValue(Metric metric) throws MetricNotFoundException, MetricUnreachableException, PluginException { return getRemoteMBeanValue(metric, metric.getAttributeName()); } // we only cache RemoteMBeanServer handles for measurements. // jndi lookup won't have much impact for control or discovery private static HashMap serverCache = new HashMap(); private static HashMap managedServerConnectionCache = new HashMap(); static Object getRemoteMBeanValue(Metric metric, String attributeName) throws MetricNotFoundException, MetricUnreachableException, PluginException { // weblogic 8.1 still uses an old jmxri implementation // which does not cache ObjectName parsing. ObjectName objName; try { objName = ObjectNameCache.getInstance(metric.getObjectName()); } catch (MalformedObjectNameException e) { // will only happen if hq-plugin.xml has a bogus metric. String msg = "Malformed ObjectName '" + metric.getObjectName() + "'"; throw new MetricInvalidException(msg, e); } Properties props = metric.getProperties(); Properties objProps = metric.getObjectProperties(); String home = props.getProperty(MBEAN_HOME); /* * certain MBeans can only been seen through the local mbean server, * others only through the admin. the Metrics do not change, only the * jndi name we lookup. this name also needs to be used for the cache * key. so we transform the Metric property string once, the first time * a value is collected for each metric. */ if (home == null) { String type = objProps.getProperty("Type"); if (ADMIN_MBEANS.get(type) == Boolean.TRUE) { home = MBeanHome.ADMIN_JNDI_NAME; } else { home = MBeanHome.LOCAL_JNDI_NAME; } props.setProperty(MBEAN_HOME, home); metric.setPropString(metric.getPropString() + "," + MBEAN_HOME + "=" + home); } RemoteMBeanServer mServer = null; boolean cached = true, retry = false; String cacheKey = metric.getPropString(); synchronized (serverCache) { mServer = (RemoteMBeanServer) serverCache.get(cacheKey); } if (mServer == null) { cached = false; mServer = getMBeanServer(metric); // jndi lookup synchronized (serverCache) { serverCache.put(cacheKey, mServer); } } Object value; synchronized (mServer) { try { if (useAttrGetter) { AttributeGetter getter = AttributeGetter.getInstance(WeblogicAttributes.instance, objName); if (getter != null) { return getter.getAttribute(mServer, attributeName); } } value = mServer.getAttribute(objName, attributeName); } catch (MBeanException e) { String msg = "MBeanException: " + e.getMessage(); throw new PluginException(msg, e); } catch (AttributeNotFoundException e) { String msg = "Attribute '" + attributeName + "' " + "not found for '" + objName + "'"; throw new MetricNotFoundException(msg, e); } catch (InstanceNotFoundException e) { String msg = "MBean '" + objName + "' not found"; throw new MetricNotFoundException(msg, e); } catch (ReflectionException e) { String msg = "ReflectionException: " + e.getMessage(); throw new PluginException(msg, e); } catch (Exception e) { // likely weblogic.rmi.extensions.RemoteRuntimeException // but compiler won't let us catch that. if (cached) { serverCache.remove(cacheKey); WeblogicAuth.clearCache(); // domain credential may have // changed. value = null; retry = true; // will try again; server may have been restarted } else { String msg = "Unknown failure: " + e.getMessage(); throw new PluginException(msg, e); } } } if (retry) { // note we only recurse once. if we had a cached mServer // but got a RemoteRuntimeException return getRemoteMBeanValue(metric); } return value; } /** * Returns a CACHED JMXConnector to the WebLogic runtime MBeanServer. Uses * JNDI lookup that works in v9.0+, as opposed to retrieval via MBEAN_HOME, * which the other connection methods are doing Cached connectors should be * used for metric retrieval. This method will attempt to connect to the * cached MBeanServer to verify that connection is no longer stale, and * reconnect if a problem occurs * @param metric A metric whose properties contain the connection * properties, should contain an Admin and/or Server URL, username, and * password * @return A JMXConnector to the runtime MBeanServer at the specified URL * @throws PluginException */ public static JMXConnector getManagedServerConnection(Metric metric) throws PluginException { String cacheKey = metric.getPropString(); JMXConnector mServer = null; synchronized (managedServerConnectionCache) { mServer = (JMXConnector) managedServerConnectionCache.get(cacheKey); if (mServer != null) { try { mServer.getMBeanServerConnection().getDefaultDomain(); } catch (Exception e) { mServer = null; } } if (mServer == null) { mServer = getManagedServerConnection(metric.getProperties()); // lookup managedServerConnectionCache.put(cacheKey, mServer); } } return mServer; } /** * Returns a JMXConnector to the WebLogic runtime MBeanServer. Uses JNDI * lookup that works in v9.0+, as opposed to retrieval via MBEAN_HOME, which * the other connection methods are doing * @param props The connection properties, should contain an Admin and/or * Server URL, username, and password * @return A JMXConnector to the runtime MBeanServer at the specified URL * @throws PluginException */ public static JMXConnector getManagedServerConnection(Properties props) throws PluginException { String adminUrl = props.getProperty(WeblogicMetric.PROP_ADMIN_URL); String url = props.getProperty(WeblogicMetric.PROP_SERVER_URL, adminUrl); // domainruntime String jndiUrl = "service:jmx:" + url + "/jndi/weblogic.management.mbeanservers.runtime"; JMXServiceURL serviceURL; try { serviceURL = new JMXServiceURL(jndiUrl); Hashtable h = new Hashtable(); h.put(Context.SECURITY_PRINCIPAL, props.getProperty(WeblogicMetric.PROP_ADMIN_USERNAME)); h.put(Context.SECURITY_CREDENTIALS, props.getProperty(WeblogicMetric.PROP_ADMIN_PASSWORD, "")); h.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "weblogic.management.remote"); JMXConnector connector = JMXConnectorFactory.connect(serviceURL, h); return connector; } catch (Exception e) { throw new PluginException(e); } } // XXX implement caching MBeanHome public static RemoteMBeanServer getMBeanServer(Properties props) throws MetricNotFoundException, MetricUnreachableException, PluginException { Environment env = new Environment(); String adminUrl = props.getProperty(WeblogicMetric.PROP_ADMIN_URL); String url = props.getProperty(WeblogicMetric.PROP_SERVER_URL, adminUrl); env.setProviderUrl(url); env.setSecurityPrincipal(props.getProperty(WeblogicMetric.PROP_ADMIN_USERNAME)); env.setSecurityCredentials(props.getProperty(WeblogicMetric.PROP_ADMIN_PASSWORD, "")); if (WeblogicProductPlugin.useSSL2Ways()) { if (WeblogicProductPlugin.getSSL2WaysCert() != null && WeblogicProductPlugin.getSSL2WaysKey() != null) { try { InputStream[] chain = new InputStream[2]; chain[0] = new FileInputStream(new File(WeblogicProductPlugin.getSSL2WaysKey())); chain[1] = new FileInputStream(new File(WeblogicProductPlugin.getSSL2WaysCert())); env.setSSLClientCertificate(chain); env.setSSLClientKeyPassword(WeblogicProductPlugin.getSSL2WaysKeyPass()); } catch (IOException e) { throw new MetricUnreachableException("Bad SSL2Ways config", e); } } } Context ctx; try { ctx = env.getInitialContext(); } catch (NamingException e) { String msg = "Failed to connect to MBeanServer: " + url; String cause = e.getMessage(); String reason; if (cause != null) { reason = cause; } else { reason = "invalid URL or credentials"; } msg += " (" + reason + ")"; throw new MetricUnreachableException(msg, e); } MBeanHome home; String mbeanHome = props.getProperty(MBEAN_HOME, MBeanHome.LOCAL_JNDI_NAME); try { home = (MBeanHome) ctx.lookup(mbeanHome); } catch (NamingException e) { String msg = "Failed to contact MBeanServer: " + e.getMessage(); throw new MetricUnreachableException(msg, e); } finally { try { ctx.close(); } catch (NamingException e) { String msg = "Failed to close MBeanServer context: " + e.getMessage(); throw new PluginException(msg, e); } } try { return home.getMBeanServer(); } catch (SecurityException e) { // this exception is not declared to be thrown, but may happen // when an agent is talking to two domains final String doc = "http://e-docs.bea.com/wls/docs70/secmanage/domain.html#1171534"; String msg = "SecurityException getting MBeanServer (jaas=" + WeblogicProductPlugin.useJAAS() + "): " + e.getMessage() + "\n" + "Likely cause: domain credential mismatch\n" + "See: " + doc; throw new PluginException(msg); } } static RemoteMBeanServer getMBeanServer(Metric metric) throws MetricNotFoundException, MetricUnreachableException, PluginException { return getMBeanServer(metric.getProperties()); } // e.g. can be used by control static RemoteMBeanServer getMBeanServer(ConfigResponse config) throws MetricNotFoundException, MetricUnreachableException, PluginException { return getMBeanServer(config.toProperties()); } static Object invoke(Metric metric, String method) throws MetricNotFoundException, MetricUnreachableException, PluginException { return invoke(metric, method, new Object[0], new String[0]); } static Object invoke(Metric metric, String method, Object[] args, String[] sig) throws MetricNotFoundException, MetricUnreachableException, PluginException { ObjectName obj; RemoteMBeanServer mServer; try { mServer = getMBeanServer(metric); } catch (PluginException e) { throw new PluginException(e.getMessage(), e); } try { obj = ObjectNameCache.getInstance(metric.getObjectName()); } catch (MalformedObjectNameException e) { throw new MetricInvalidException(e.getMessage()); } try { return mServer.invoke(obj, method, args, sig); } catch (InstanceNotFoundException e) { throw new MetricInvalidException(e.getMessage()); } catch (MBeanException e) { throw new PluginException(e.getMessage(), e); } catch (ReflectionException e) { throw new PluginException(e.getMessage(), e); } catch (Exception e) { throw new PluginException(e.getMessage(), e); } } static double convertStateVal(Object state) { if (state instanceof Integer) { // server return convertStateVal((Integer) state); } else if (state instanceof Boolean) { // application boolean deployed = ((Boolean) state).booleanValue(); return deployed ? Metric.AVAIL_UP : Metric.AVAIL_DOWN; } else if (state instanceof String) { if ("DEPLOYED".equals(state)) { // webapp return Metric.AVAIL_UP; } else if ("running".equalsIgnoreCase((String) state)) { // server // 6.1 == "Running" // 7.0+ == "RUNNING" return Metric.AVAIL_UP; } else { // exq. XXX need a better check // but being able to get any attribute with this // ObjectName means the exq is up. return Metric.AVAIL_UP; } } return Metric.AVAIL_UNKNOWN; } static double convertStateVal(Integer state) { switch (state.intValue()) { case ServerLifeCycleRuntimeMBean.SRVR_RUNNING: return Metric.AVAIL_UP; case ServerLifeCycleRuntimeMBean.SRVR_SHUTDOWN: case ServerLifeCycleRuntimeMBean.SRVR_STARTING: case ServerLifeCycleRuntimeMBean.SRVR_STANDBY: case ServerLifeCycleRuntimeMBean.SRVR_SUSPENDING: case ServerLifeCycleRuntimeMBean.SRVR_RESUMING: case ServerLifeCycleRuntimeMBean.SRVR_SHUTTING_DOWN: case ServerLifeCycleRuntimeMBean.SRVR_FAILED: return Metric.AVAIL_DOWN; case ServerLifeCycleRuntimeMBean.SRVR_UNKNOWN: default: return Metric.AVAIL_UNKNOWN; } } }