/* * Copyright 2008-2014 by Emeric Vernat * * This file is part of Java Melody. * * 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.bull.javamelody; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.management.Attribute; import javax.management.InstanceNotFoundException; import javax.management.JMException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; import net.bull.javamelody.MBeanNode.MBeanAttribute; /** * Objet récupérant une instance de MBeanServer lors de sa construction * et permettant de récupérer différentes données sur les MBeans. * @author Emeric Vernat */ final class MBeans { private static final String JAVA_LANG_MBEAN_DESCRIPTION = "Information on the management interface of the MBean"; private static final Comparator<MBeanNode> NODE_COMPARATOR = new Comparator<MBeanNode>() { @Override public int compare(MBeanNode o1, MBeanNode o2) { return o1.getName() != null ? o1.getName().compareTo(o2.getName()) : 0; } }; private static final Comparator<MBeanAttribute> ATTRIBUTE_COMPARATOR = new Comparator<MBeanAttribute>() { @Override public int compare(MBeanAttribute o1, MBeanAttribute o2) { return o1.getName().compareTo(o2.getName()); } }; private final MBeanServer mbeanServer; MBeans() { this(getPlatformMBeanServer()); } private MBeans(MBeanServer mbeanServer) { super(); this.mbeanServer = mbeanServer; } Set<ObjectName> getTomcatThreadPools() throws MalformedObjectNameException { return mbeanServer.queryNames(new ObjectName("*:type=ThreadPool,*"), null); } Set<ObjectName> getTomcatGlobalRequestProcessors() throws MalformedObjectNameException { return mbeanServer.queryNames(new ObjectName("*:type=GlobalRequestProcessor,*"), null); } Object getAttribute(ObjectName name, String attribute) throws JMException { return mbeanServer.getAttribute(name, attribute); } static List<MBeanNode> getAllMBeanNodes() throws JMException { initJRockitMBeansIfNeeded(); final List<MBeanNode> result = new ArrayList<MBeanNode>(); final MBeanServer platformMBeanServer = getPlatformMBeanServer(); final MBeanNode platformNode = new MBeanNode(""); // MBeans pour la plateforme final MBeans platformMBeans = new MBeans(); platformNode.getChildren().addAll(platformMBeans.getMBeanNodes()); result.add(platformNode); // pour JBoss 5.0.x, les MBeans de JBoss sont dans un autre MBeanServer for (final MBeanServer mbeanServer : getMBeanServers()) { if (!mbeanServer.equals(platformMBeanServer)) { final MBeanNode node = new MBeanNode(mbeanServer.getDefaultDomain()); final MBeans mbeans = new MBeans(mbeanServer); node.getChildren().addAll(mbeans.getMBeanNodes()); result.add(node); } } return result; } private static void initJRockitMBeansIfNeeded() { // si jrockit, on initialise les MBeans spécifiques jrockit lors de la première demande if (System.getProperty("java.vendor").contains("BEA")) { try { // initialisation des MBeans jrockit comme indiqué dans http://blogs.oracle.com/hirt/jrockit/ try { getPlatformMBeanServer().getMBeanInfo( new ObjectName("bea.jrockit.management:type=JRockitConsole")); } catch (final InstanceNotFoundException e1) { getPlatformMBeanServer().createMBean("bea.jrockit.management.JRockitConsole", null); LOG.debug("JRockit MBeans initialized"); } } catch (final JMException e) { throw new IllegalStateException(e); } } } private List<MBeanNode> getMBeanNodes() throws JMException { final List<MBeanNode> result = new ArrayList<MBeanNode>(); final Set<ObjectName> names = mbeanServer.queryNames(null, null); for (final ObjectName name : names) { final String domain = name.getDomain(); if ("jboss.deployment".equals(domain)) { // la partie "jboss.deployment" dans JBoss (5.0.x) est plutôt inutile et trop lourde continue; } MBeanNode domainNode = getMBeanNodeFromList(result, domain); if (domainNode == null) { domainNode = new MBeanNode(domain); result.add(domainNode); } final String keyPropertyListString = name.getKeyPropertyListString(); final String firstPropertyValue; final int indexOf = keyPropertyListString.indexOf('='); if (indexOf == -1) { // n'arrive probablement pas, mais au cas où firstPropertyValue = null; } else { firstPropertyValue = name.getKeyProperty(keyPropertyListString .substring(0, indexOf)); } if ("Servlet".equals(firstPropertyValue) && "jonas".equals(domain)) { // la partie "jonas:j2eeType=Servlet" dans Jonas (5.1.0) est trop lourde continue; } MBeanNode firstPropertyNode = getMBeanNodeFromList(domainNode.getChildren(), firstPropertyValue); if (firstPropertyNode == null) { firstPropertyNode = new MBeanNode(firstPropertyValue); domainNode.getChildren().add(firstPropertyNode); } final MBeanNode mbean = getMBeanNode(name); firstPropertyNode.getChildren().add(mbean); } sortMBeanNodes(result); return result; } private void sortMBeanNodes(List<MBeanNode> nodes) { if (nodes.size() > 1) { Collections.sort(nodes, NODE_COMPARATOR); } for (final MBeanNode node : nodes) { final List<MBeanNode> children = node.getChildren(); if (children != null) { sortMBeanNodes(children); } final List<MBeanAttribute> attributes = node.getAttributes(); if (attributes != null && attributes.size() > 1) { Collections.sort(attributes, ATTRIBUTE_COMPARATOR); } } } private static MBeanNode getMBeanNodeFromList(List<MBeanNode> list, String name) { for (final MBeanNode node : list) { if (node.getName().equals(name)) { return node; } } return null; } private MBeanNode getMBeanNode(ObjectName name) throws JMException { final String mbeanName = name.toString(); final MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo(name); final String description = formatDescription(mbeanInfo.getDescription()); final MBeanAttributeInfo[] attributeInfos = mbeanInfo.getAttributes(); final List<MBeanAttribute> attributes = getAttributes(name, attributeInfos); // les attributs seront triés par ordre alphabétique dans getMBeanNodes return new MBeanNode(mbeanName, description, attributes); } private List<MBeanAttribute> getAttributes(ObjectName name, MBeanAttributeInfo[] attributeInfos) { final List<String> attributeNames = new ArrayList<String>(attributeInfos.length); for (final MBeanAttributeInfo attribute : attributeInfos) { // on ne veut pas afficher l'attribut password, jamais // (notamment, dans users tomcat ou dans datasources tomcat) if (attribute.isReadable() && !"password".equalsIgnoreCase(attribute.getName())) { attributeNames.add(attribute.getName()); } } final String[] attributeNamesArray = attributeNames.toArray(new String[attributeNames .size()]); final List<MBeanAttribute> result = new ArrayList<MBeanAttribute>(); try { // issue 116: asList sur mbeanServer.getAttributes(name, attributeNamesArray) n'existe qu'en java 1.6 final List<Object> attributes = mbeanServer.getAttributes(name, attributeNamesArray); for (final Object object : attributes) { final Attribute attribute = (Attribute) object; final Object value = convertValueIfNeeded(attribute.getValue()); final String attributeDescription = getAttributeDescription(attribute.getName(), attributeInfos); final String formattedAttributeValue = formatAttributeValue(value); final MBeanAttribute mbeanAttribute = new MBeanAttribute(attribute.getName(), attributeDescription, formattedAttributeValue); result.add(mbeanAttribute); } } catch (final Exception e) { // issue 201: do not stop to render MBeans tree when exception in mbeanServer.getAttributes final MBeanAttribute mbeanAttribute = new MBeanAttribute("exception", null, e.toString()); result.add(mbeanAttribute); } return result; } private String formatAttributeValue(Object attributeValue) { try { if (attributeValue instanceof List) { final StringBuilder sb = new StringBuilder(); sb.append('['); boolean first = true; for (final Object value : (List<?>) attributeValue) { if (first) { first = false; } else { sb.append(",\n"); } sb.append(String.valueOf(value)); } sb.append(']'); return sb.toString(); } return String.valueOf(attributeValue); } catch (final Exception e) { return e.toString(); } } private String formatDescription(String description) { // les descriptions des MBeans de java.lang n'apportent aucune information utile if (description == null || JAVA_LANG_MBEAN_DESCRIPTION.equals(description)) { return null; } int indexOf = description.indexOf(" "); if (indexOf != -1) { // certaines descriptions de MBeans ou d'attributs dans Tomcat 6 et 7 contiennent de nombreux espaces qui se suivent final StringBuilder sb = new StringBuilder(description); while (indexOf != -1) { sb.deleteCharAt(indexOf); indexOf = sb.indexOf(" "); } return sb.toString(); } return description; } private Object convertValueIfNeeded(Object value) { if (value instanceof CompositeData) { final CompositeData data = (CompositeData) value; final Map<String, Object> values = new TreeMap<String, Object>(); for (final String key : data.getCompositeType().keySet()) { values.put(key, convertValueIfNeeded(data.get(key))); } return values; } else if (value instanceof CompositeData[]) { final List<Object> list = new ArrayList<Object>(); for (final CompositeData data : (CompositeData[]) value) { list.add(convertValueIfNeeded(data)); } return list; } else if (value instanceof Object[]) { return Arrays.asList((Object[]) value); } else if (value instanceof TabularData) { final TabularData tabularData = (TabularData) value; return convertValueIfNeeded(tabularData.values()); } else if (value instanceof Collection) { final List<Object> list = new ArrayList<Object>(); for (final Object data : (Collection<?>) value) { list.add(convertValueIfNeeded(data)); } return list; } return convertJRockitValueIfNeeded(value); } private static Object convertJRockitValueIfNeeded(Object value) { if (value instanceof double[]) { // pour jrockit MBeans final List<Double> list = new ArrayList<Double>(); for (final double data : (double[]) value) { list.add(data); } return list; } else if (value instanceof int[]) { // pour jrockit MBeans final List<Integer> list = new ArrayList<Integer>(); for (final int data : (int[]) value) { list.add(data); } return list; } return value; } static String getConvertedAttributes(String jmxValueParameter) { initJRockitMBeansIfNeeded(); final StringBuilder sb = new StringBuilder(); boolean first = true; final List<MBeanServer> mBeanServers = getMBeanServers(); for (final String mbeansAttribute : jmxValueParameter.split("[|]")) { final int lastIndexOfPoint = mbeansAttribute.lastIndexOf('.'); if (lastIndexOfPoint <= 0) { throw new IllegalArgumentException(mbeansAttribute); } final String name = mbeansAttribute.substring(0, lastIndexOfPoint); final String attribute = mbeansAttribute.substring(lastIndexOfPoint + 1); // on ne veut pas afficher l'attribut password, jamais // (notamment, dans users tomcat ou dans datasources tomcat) if ("password".equalsIgnoreCase(attribute)) { throw new IllegalArgumentException(name + '.' + attribute); } if (first) { first = false; } else { sb.append('|'); } InstanceNotFoundException instanceNotFoundException = null; for (final MBeanServer mbeanServer : mBeanServers) { try { final MBeans mbeans = new MBeans(mbeanServer); final Object jmxValue = mbeans.convertValueIfNeeded(mbeans.getAttribute( new ObjectName(name), attribute)); sb.append(jmxValue); instanceNotFoundException = null; // ObjectName trouvé dans ce MBeanServer, inutile de chercher dans les suivants // où il n'est d'ailleurs pas break; } catch (final InstanceNotFoundException e) { // ObjectName non trouvé dans ce MBeanServer, donc on cherche dans le suivant // (nécessaire pour JBoss 5.0.x) instanceNotFoundException = e; continue; } catch (final JMException e) { throw new IllegalArgumentException(name + '.' + attribute, e); } } if (instanceNotFoundException != null) { throw new IllegalArgumentException(name + '.' + attribute, instanceNotFoundException); } } return sb.toString(); } private String getAttributeDescription(String name, MBeanAttributeInfo[] attributeInfos) { for (final MBeanAttributeInfo attributeInfo : attributeInfos) { if (name.equals(attributeInfo.getName())) { // certaines descriptions d'attributs comme les NamingResources dans Tomcat 7 contiennent aussi des espaces qui se suivent final String attributeDescription = formatDescription(attributeInfo .getDescription()); if (attributeDescription == null || name.equals(attributeDescription) || attributeDescription.length() == 0) { // les attributs des MBeans de java.lang ont des descriptions égales aux noms, // ce sont des descriptions inutiles return null; } return attributeDescription; } } return null; } /** * Retourne le javax.management.MBeanServer de la plateforme. * @return MBeanServer */ static MBeanServer getPlatformMBeanServer() { return ManagementFactory.getPlatformMBeanServer(); // alternative (sauf pour Hudson/Jenkins slaves): // final List<MBeanServer> mBeanServers = MBeanServerFactory.findMBeanServer(null); // if (!mBeanServers.isEmpty()) { // // il existe déjà un MBeanServer créé précédemment par Tomcat ou bien ci-dessous // return mBeanServers.get(0); // } // final MBeanServer server = MBeanServerFactory.createMBeanServer(); // return server; } /** * Retourne la liste de tous les javax.management.MBeanServer. * @return List */ private static List<MBeanServer> getMBeanServers() { // par exemple avec JBoss 5.0.x, il y a un MBeanServer de la plateforme (defaultDomain null) // et un MBeanServer de JBoss (defaultDomain "jboss") return MBeanServerFactory.findMBeanServer(null); } }