/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.repo.management; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Array; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javax.management.JMException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import org.alfresco.util.exec.RuntimeExec; import org.alfresco.util.exec.RuntimeExec.ExecutionResult; /** * A utility class providing a method to dump a local or remote MBeanServer's entire object tree for support purposes. * Nested arrays and CompositeData objects in MBean attribute values are handled. * * @author dward */ public class JmxDumpUtil { /** Table header for attribute names. */ private static final String NAME_HEADER = "Attribute Name"; /** Table header for attribute values. */ private static final String VALUE_HEADER = "Attribute Value"; /** Place holder for nulls. */ private static final String NULL_VALUE = ""; /** Place holder for unreadable values. */ private static final String UNREADABLE_VALUE = ""; private static final String OS_NAME = "os.name"; /** * Dumps a local or remote MBeanServer's entire object tree for support purposes. Nested arrays and CompositeData * objects in MBean attribute values are handled. * * @param connection * the server connection (or server itself) * @param out * PrintWriter to write the output to * @throws IOException * Signals that an I/O exception has occurred. */ public static void dumpConnection(MBeanServerConnection connection, PrintWriter out) throws IOException { JmxDumpUtil.showStartBanner(out); // Get all the object names Set objectNames = connection.queryNames(null, null); // Sort the names (don't assume ObjectName implements Comparable in JDK 1.5) Set newObjectNames = new TreeSet(new Comparator() { public int compare(ObjectName o1, ObjectName o2) { return o1.toString().compareTo(o2.toString()); } }); newObjectNames.addAll(objectNames); objectNames = newObjectNames; // Dump each MBean for (ObjectName objectName : objectNames) { try { printMBeanInfo(connection, objectName, out); } catch (JMException e) { // Sometimes beans can disappear while we are examining them } } } /** * Dumps the details of a single MBean. * * @param connection * the server connection (or server itself) * @param objectName * the object name * @param out * PrintWriter to write the output to * @throws IOException * Signals that an I/O exception has occurred. * @throws JMException * Signals a JMX error */ private static void printMBeanInfo(MBeanServerConnection connection, ObjectName objectName, PrintWriter out) throws IOException, JMException { Map attributes = new TreeMap(); MBeanInfo info = connection.getMBeanInfo(objectName); attributes.put("** Object Name", objectName.toString()); attributes.put("** Object Type", info.getClassName()); for (MBeanAttributeInfo element : info.getAttributes()) { Object value; if (element.isReadable()) { try { value = connection.getAttribute(objectName, element.getName()); } catch (Exception e) { value = JmxDumpUtil.UNREADABLE_VALUE; } } else { value = JmxDumpUtil.UNREADABLE_VALUE; } attributes.put(element.getName(), value); } if (objectName.getCanonicalName().equals("Alfresco:Name=SystemProperties")) { String osName = (String) attributes.get(OS_NAME); if (osName != null && osName.toLowerCase().startsWith("linux")) { attributes.put(OS_NAME, updateOSNameAttributeForLinux(osName)); } } tabulate(JmxDumpUtil.NAME_HEADER, JmxDumpUtil.VALUE_HEADER, attributes, out, 0); } /** * Adds a Linux version * * @param osName os.name attribute * @return */ public static String updateOSNameAttributeForLinux(String osName) { RuntimeExec exec = new RuntimeExec(); Map commandMap = new HashMap(3, 1.0f); commandMap.put("Linux", new String[] { "lsb_release", "-d" }); exec.setCommandsAndArguments(commandMap); ExecutionResult ret = exec.execute(); if (ret.getSuccess()) { osName += " (" + ret.getStdOut().replace("\n", "") + ")"; } else { commandMap.put("Linux", new String[] { "uname", "-a" }); exec.setCommandsAndArguments(commandMap); ret = exec.execute(); if (ret.getSuccess()) { osName += " (" + ret.getStdOut().replace("\n", "") + ")"; } else { osName += " (Unknown)"; } } return osName; } /** * Dumps the details of a single CompositeData object. * * @param composite * the composite object * @param out * PrintWriter to write the output to * @param nestLevel * the nesting level * @throws IOException * Signals that an I/O exception has occurred. */ @SuppressWarnings("unchecked") private static void printCompositeInfo(CompositeData composite, PrintWriter out, int nestLevel) throws IOException { Map attributes = new TreeMap(); for (String key : (Set)composite.getCompositeType().keySet()) { Object value; try { value = composite.get(key); } catch (Exception e) { value = JmxDumpUtil.UNREADABLE_VALUE; } attributes.put(key, value); } tabulate(JmxDumpUtil.NAME_HEADER, JmxDumpUtil.VALUE_HEADER, attributes, out, nestLevel); } /** * Tabulates a given String -> Object Map. * * @param keyHeader * the key header * @param valueHeader * the value header * @param rows * Map containing key value pairs forming the rows * @param out * PrintWriter to write the output to * @param nestLevel * the nesting level * @throws IOException * Signals that an I/O exception has occurred. */ private static void tabulate(String keyHeader, String valueHeader, Map rows, PrintWriter out, int nestLevel) throws IOException { if (rows.isEmpty()) { return; } // Calculate column lengths int maxKeyLength = keyHeader.length(), maxValLength = valueHeader.length(); for (Map.Entry entry : rows.entrySet()) { maxKeyLength = Math.max(maxKeyLength, entry.getKey().length()); maxValLength = Math.max(maxValLength, getValueLength(entry.getValue())); } // Output Header outputRow(out, maxKeyLength, keyHeader, valueHeader, nestLevel); indent(out, nestLevel); for (int col = 0; col < maxKeyLength; col++) { out.print('-'); } out.print(' '); for (int col = 0; col < maxValLength; col++) { out.print('-'); } out.println(); // Output Body for (Map.Entry entry : rows.entrySet()) { outputRow(out, maxKeyLength, entry.getKey(), entry.getValue(), nestLevel); } out.println(); } /** * Outputs spaces in the left hand margin appropriate for the given nesting level. * * @param out * PrintWriter to write the output to * @param nestLevel * the nesting level */ private static void indent(PrintWriter out, int nestLevel) { int size = nestLevel * 3; for (int i = 0; i < size; i++) { out.print(' '); } } /** * Outputs a single row in a two-column table. The first column is padded with spaces so that the second column is * aligned. * * @param out * PrintWriter to write the output to * @param maxKeyLength * maximum number of characters in the first column * @param key * the first column value * @param value * the second column value * @param nestLevel * the nesting level * @throws IOException * Signals that an I/O exception has occurred. */ private static void outputRow(PrintWriter out, int maxKeyLength, String key, Object value, int nestLevel) throws IOException { indent(out, nestLevel); out.print(key); for (int i = key.length() - 1; i < maxKeyLength; i++) { out.print(' '); } outputValue(out, value, nestLevel); } /** * Outputs a single value, dealing with nested arrays and CompositeData objects. * * @param out * PrintWriter to write the output to * @param value * the value to output * @param nestLevel * the nesting level * @throws IOException * Signals that an I/O exception has occurred. */ private static void outputValue(PrintWriter out, Object value, int nestLevel) throws IOException { if (value == null) { out.println(JmxDumpUtil.NULL_VALUE); } else if (value.getClass().isArray()) { int length = Array.getLength(value); if (length == 0) { out.println("[]"); } else { out.println(); indent(out, nestLevel + 1); out.println('['); for (int i = 0; i < length; i++) { indent(out, nestLevel + 2); outputValue(out, Array.get(value, i), nestLevel + 2); if (i + 1 < length) { indent(out, nestLevel + 1); out.println(','); } } indent(out, nestLevel + 1); out.println(']'); } } else if (value instanceof CompositeData) { out.println(); indent(out, nestLevel + 1); out.println('['); printCompositeInfo((CompositeData) value, out, nestLevel + 2); indent(out, nestLevel + 1); out.println(']'); } else { out.println(value.toString()); } } /** * Gets the number of characters required to encode a value. * * @param value * the value to be encoded * @return the number of characters */ private static int getValueLength(Object value) { if (value == null) { return JmxDumpUtil.NULL_VALUE.length(); } else if (value.getClass().isArray() || value instanceof CompositeData) { // We continue arrays and composites on a new line return 0; } else { return value.toString().length(); } } /** * Show a message stating the JmxDumper has been started, with the current date and time. */ private static void showStartBanner(PrintWriter out) { DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); out.println(JmxDumpUtil.class.getSimpleName() + " started: " + df.format(new Date())); out.println(); } }