1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.model;
19
20 import java.lang.management.ManagementFactory;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31 import java.util.TreeMap;
32
33 import javax.management.Attribute;
34 import javax.management.InstanceNotFoundException;
35 import javax.management.JMException;
36 import javax.management.MBeanAttributeInfo;
37 import javax.management.MBeanInfo;
38 import javax.management.MBeanServer;
39 import javax.management.MBeanServerFactory;
40 import javax.management.ObjectName;
41 import javax.management.openmbean.CompositeData;
42 import javax.management.openmbean.TabularData;
43
44 import net.bull.javamelody.internal.common.I18N;
45 import net.bull.javamelody.internal.common.LOG;
46 import net.bull.javamelody.internal.model.MBeanNode.MBeanAttribute;
47
48 /**
49  * Objet récupérant une instance de {@link MBeanServer} lors de sa construction
50  * et permettant de récupérer différentes données sur les MBeans.
51  * @author Emeric Vernat
52  */

53 public final class MBeans {
54     /**
55      * Separator between mbeans attributes in the External API.
56      */

57     public static final char ATTRIBUTES_SEPARATOR = '|';
58
59     private static final String JAVA_LANG_MBEAN_DESCRIPTION = "Information on the management interface of the MBean";
60     private static final Comparator<MBeanNode> NODE_COMPARATOR = new Comparator<MBeanNode>() {
61         @Override
62         public int compare(MBeanNode o1, MBeanNode o2) {
63             return o1.getName() != null ? o1.getName().compareTo(o2.getName()) : 0;
64         }
65     };
66     private static final Comparator<MBeanAttribute> ATTRIBUTE_COMPARATOR = new Comparator<MBeanAttribute>() {
67         @Override
68         public int compare(MBeanAttribute o1, MBeanAttribute o2) {
69             return o1.getName().compareTo(o2.getName());
70         }
71     };
72     private final MBeanServer mbeanServer;
73
74     MBeans() {
75         this(getPlatformMBeanServer());
76     }
77
78     private MBeans(MBeanServer mbeanServer) {
79         super();
80         this.mbeanServer = mbeanServer;
81     }
82
83     Object getAttribute(ObjectName name, String attribute) throws JMException {
84         return mbeanServer.getAttribute(name, attribute);
85     }
86
87     public static List<MBeanNode> getAllMBeanNodes() throws JMException {
88         initJRockitMBeansIfNeeded();
89
90         final List<MBeanNode> result = new ArrayList<>();
91         final MBeanServer platformMBeanServer = getPlatformMBeanServer();
92         final MBeanNode platformNode = new MBeanNode("");
93         // MBeans pour la plateforme
94         final MBeans platformMBeans = new MBeans();
95         platformNode.getChildren().addAll(platformMBeans.getMBeanNodes());
96         result.add(platformNode);
97
98         // pour JBoss 5.0.x, les MBeans de JBoss sont dans un autre MBeanServer
99         for (final MBeanServer mbeanServer : getMBeanServers()) {
100             if (!mbeanServer.equals(platformMBeanServer)) {
101                 final MBeanNode node = new MBeanNode(mbeanServer.getDefaultDomain());
102                 final MBeans mbeans = new MBeans(mbeanServer);
103                 node.getChildren().addAll(mbeans.getMBeanNodes());
104                 result.add(node);
105             }
106         }
107         return result;
108     }
109
110     private static void initJRockitMBeansIfNeeded() {
111         // si jrockit, on initialise les MBeans spécifiques jrockit lors de la première demande
112         if (System.getProperty("java.vendor").contains("BEA")) {
113             try {
114                 // initialisation des MBeans jrockit comme indiqué dans http://blogs.oracle.com/hirt/jrockit/
115                 try {
116                     getPlatformMBeanServer().getMBeanInfo(
117                             new ObjectName("bea.jrockit.management:type=JRockitConsole"));
118                 } catch (final InstanceNotFoundException e1) {
119                     getPlatformMBeanServer().createMBean("bea.jrockit.management.JRockitConsole",
120                             null);
121                     LOG.debug("JRockit MBeans initialized");
122                 }
123             } catch (final JMException e) {
124                 throw new IllegalStateException(e);
125             }
126         }
127     }
128
129     private List<MBeanNode> getMBeanNodes() throws JMException {
130         final List<MBeanNode> result = new ArrayList<>();
131         final Set<ObjectName> names = mbeanServer.queryNames(nullnull);
132         for (final ObjectName name : names) {
133             final String domain = name.getDomain();
134             if ("jboss.deployment".equals(domain)) {
135                 // la partie "jboss.deployment" dans JBoss (5.0.x) est plutôt inutile et trop lourde
136                 continue;
137             }
138             MBeanNode domainNode = getMBeanNodeFromList(result, domain);
139             if (domainNode == null) {
140                 domainNode = new MBeanNode(domain);
141                 result.add(domainNode);
142             }
143             final String keyPropertyListString = name.getKeyPropertyListString();
144             final String firstPropertyValue;
145             final int indexOf = keyPropertyListString.indexOf('=');
146             if (indexOf == -1) {
147                 // n'arrive probablement pas, mais au cas où
148                 firstPropertyValue = null;
149             } else {
150                 firstPropertyValue = name
151                         .getKeyProperty(keyPropertyListString.substring(0, indexOf));
152             }
153             MBeanNode firstPropertyNode = getMBeanNodeFromList(domainNode.getChildren(),
154                     firstPropertyValue);
155             if (firstPropertyNode == null) {
156                 firstPropertyNode = new MBeanNode(firstPropertyValue);
157                 domainNode.getChildren().add(firstPropertyNode);
158             }
159             try {
160                 final MBeanNode mbean = getMBeanNode(name);
161                 firstPropertyNode.getChildren().add(mbean);
162             } catch (final IllegalStateException e) {
163                 // for JBoss EAP 6 (#757)
164                 continue;
165             }
166         }
167         sortMBeanNodes(result);
168         return result;
169     }
170
171     private void sortMBeanNodes(List<MBeanNode> nodes) {
172         if (nodes.size() > 1) {
173             Collections.sort(nodes, NODE_COMPARATOR);
174         }
175
176         for (final MBeanNode node : nodes) {
177             final List<MBeanNode> children = node.getChildren();
178             if (children != null) {
179                 sortMBeanNodes(children);
180             }
181             final List<MBeanAttribute> attributes = node.getAttributes();
182             if (attributes != null && attributes.size() > 1) {
183                 Collections.sort(attributes, ATTRIBUTE_COMPARATOR);
184             }
185         }
186     }
187
188     private static MBeanNode getMBeanNodeFromList(List<MBeanNode> list, String name) {
189         for (final MBeanNode node : list) {
190             if (node.getName().equals(name)) {
191                 return node;
192             }
193         }
194         return null;
195     }
196
197     private MBeanNode getMBeanNode(ObjectName name) throws JMException {
198         final String mbeanName = name.toString();
199         final MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo(name);
200         final String description = formatDescription(mbeanInfo.getDescription());
201         final MBeanAttributeInfo[] attributeInfos = mbeanInfo.getAttributes();
202         final List<MBeanAttribute> attributes = getAttributes(name, attributeInfos);
203         // les attributs seront triés par ordre alphabétique dans getMBeanNodes
204         return new MBeanNode(mbeanName, description, attributes);
205     }
206
207     private List<MBeanAttribute> getAttributes(ObjectName name,
208             MBeanAttributeInfo[] attributeInfos) {
209         final List<String> attributeNames = new ArrayList<>(attributeInfos.length);
210         for (final MBeanAttributeInfo attribute : attributeInfos) {
211             // on ne veut pas afficher l'attribut password, jamais
212             // (notamment, dans users tomcat ou dans datasources tomcat)
213             // et on ne veut pas afficher l'attribut configurationAsProperties d'infinispan (issue 1180)
214             if (attribute.isReadable() && !"password".equalsIgnoreCase(attribute.getName())
215                     && !"configurationAsProperties".equalsIgnoreCase(attribute.getName())) {
216                 attributeNames.add(attribute.getName());
217             }
218         }
219         final String[] attributeNamesArray = attributeNames.toArray(new String[0]);
220         final List<MBeanAttribute> result = new ArrayList<>();
221         try {
222             // issue 116: asList sur mbeanServer.getAttributes(name, attributeNamesArray) n'existe qu'en java 1.6
223             final List<Object> attributes = mbeanServer.getAttributes(name, attributeNamesArray);
224             for (final Object object : attributes) {
225                 final Attribute attribute = (Attribute) object;
226                 final Object value = convertValueIfNeeded(attribute.getValue());
227                 final String attributeDescription = getAttributeDescription(attribute.getName(),
228                         attributeInfos);
229                 final String formattedAttributeValue = formatAttributeValue(value);
230                 final MBeanAttribute mbeanAttribute = new MBeanAttribute(attribute.getName(),
231                         attributeDescription, formattedAttributeValue);
232                 result.add(mbeanAttribute);
233             }
234         } catch (final Exception e) {
235             // issue 201: do not stop to render MBeans tree when exception in mbeanServer.getAttributes
236             final MBeanAttribute mbeanAttribute = new MBeanAttribute("exception"null,
237                     e.toString());
238             result.add(mbeanAttribute);
239         }
240         return result;
241     }
242
243     private String formatAttributeValue(Object attributeValue) {
244         try {
245             if (attributeValue instanceof List) {
246                 final StringBuilder sb = new StringBuilder();
247                 sb.append('[');
248                 boolean first = true;
249                 for (final Object value : (List<?>) attributeValue) {
250                     if (first) {
251                         first = false;
252                     } else {
253                         sb.append(",\n");
254                     }
255                     if (attributeValue instanceof Number) {
256                         sb.append(I18N.createIntegerFormat().format(attributeValue));
257                     } else {
258                         sb.append(value);
259                     }
260                 }
261                 sb.append(']');
262                 return sb.toString();
263             } else if (attributeValue instanceof Map) {
264                 @SuppressWarnings("unchecked")
265                 final Map<Object, Object> map = (Map<Object, Object>) attributeValue;
266                 final LinkedHashMap<Object, Object> mapToString = new LinkedHashMap<>();
267                 for (final Entry<Object, Object> e : map.entrySet()) {
268                     final Object v = e.getValue();
269                     if (v instanceof Number) {
270                         mapToString.put(e.getKey(), I18N.createIntegerFormat().format(v));
271                     } else {
272                         mapToString.put(e.getKey(), attributeValue);
273                     }
274                 }
275                 return mapToString.toString();
276             } else if (attributeValue instanceof Number) {
277                 return I18N.createIntegerFormat().format(attributeValue);
278             }
279             return String.valueOf(attributeValue);
280         } catch (final Exception e) {
281             return e.toString();
282         }
283     }
284
285     private String formatDescription(String description) {
286         // les descriptions des MBeans de java.lang n'apportent aucune information utile
287         if (description == null || JAVA_LANG_MBEAN_DESCRIPTION.equals(description)) {
288             return null;
289         }
290         int indexOf = description.indexOf("  ");
291         if (indexOf != -1) {
292             // certaines descriptions de MBeans ou d'attributs dans Tomcat 6 et 7 contiennent de nombreux espaces qui se suivent
293             final StringBuilder sb = new StringBuilder(description);
294             while (indexOf != -1) {
295                 sb.deleteCharAt(indexOf);
296                 indexOf = sb.indexOf("  ");
297             }
298             return sb.toString();
299         }
300         return description;
301     }
302
303     private Object convertValueIfNeeded(Object value) {
304         if (value instanceof CompositeData) {
305             final CompositeData data = (CompositeData) value;
306             final Map<String, Object> values = new TreeMap<>();
307             for (final String key : data.getCompositeType().keySet()) {
308                 values.put(key, convertValueIfNeeded(data.get(key)));
309             }
310             return values;
311         } else if (value instanceof CompositeData[]) {
312             final List<Object> list = new ArrayList<>();
313             for (final CompositeData data : (CompositeData[]) value) {
314                 list.add(convertValueIfNeeded(data));
315             }
316             return list;
317         } else if (value instanceof Object[]) {
318             return Arrays.asList((Object[]) value);
319         } else if (value instanceof TabularData) {
320             final TabularData tabularData = (TabularData) value;
321             return convertValueIfNeeded(tabularData.values());
322         } else if (value instanceof Collection) {
323             final List<Object> list = new ArrayList<>();
324             for (final Object data : (Collection<?>) value) {
325                 list.add(convertValueIfNeeded(data));
326             }
327             return list;
328         }
329         return convertJRockitValueIfNeeded(value);
330     }
331
332     private static Object convertJRockitValueIfNeeded(Object value) {
333         if (value instanceof double[]) {
334             // pour jrockit MBeans
335             final List<Double> list = new ArrayList<>();
336             for (final double data : (double[]) value) {
337                 list.add(data);
338             }
339             return list;
340         } else if (value instanceof int[]) {
341             // pour jrockit MBeans
342             final List<Integer> list = new ArrayList<>();
343             for (final int data : (int[]) value) {
344                 list.add(data);
345             }
346             return list;
347         }
348         return value;
349     }
350
351     private static List<Object> getConvertedAttributes(List<String> mbeanAttributes) {
352         initJRockitMBeansIfNeeded();
353
354         final List<Object> result = new ArrayList<>();
355         final List<MBeanServer> mBeanServers = getMBeanServers();
356         for (final String mbeansAttribute : mbeanAttributes) {
357             final int lastIndexOfPoint = mbeansAttribute.lastIndexOf('.');
358             if (lastIndexOfPoint <= 0) {
359                 throw new IllegalArgumentException(mbeansAttribute);
360             }
361             final String name = mbeansAttribute.substring(0, lastIndexOfPoint);
362             final String attribute = mbeansAttribute.substring(lastIndexOfPoint + 1);
363             // on ne veut pas afficher l'attribut password, jamais
364             // (notamment, dans users tomcat ou dans datasources tomcat)
365             if ("password".equalsIgnoreCase(attribute)) {
366                 throw new IllegalArgumentException(name + '.' + attribute);
367             }
368             InstanceNotFoundException instanceNotFoundException = null;
369             for (final MBeanServer mbeanServer : mBeanServers) {
370                 try {
371                     final MBeans mbeans = new MBeans(mbeanServer);
372                     final Object jmxValue = mbeans.convertValueIfNeeded(
373                             mbeans.getAttribute(new ObjectName(name), attribute));
374                     result.add(jmxValue);
375                     instanceNotFoundException = null;
376                     // ObjectName trouvé dans ce MBeanServer, inutile de chercher dans les suivants
377                     // où il n'est d'ailleurs pas
378                     break;
379                 } catch (final InstanceNotFoundException e) {
380                     // ObjectName non trouvé dans ce MBeanServer, donc on cherche dans le suivant
381                     // (nécessaire pour JBoss 5.0.x)
382                     instanceNotFoundException = e;
383                     continue;
384                 } catch (final JMException e) {
385                     throw new IllegalArgumentException(name + '.' + attribute, e);
386                 }
387             }
388             if (instanceNotFoundException != null) {
389                 throw new IllegalArgumentException(name + '.' + attribute,
390                         instanceNotFoundException);
391             }
392         }
393         return result;
394     }
395
396     public static String getConvertedAttributes(String jmxValueParameter) {
397         final List<String> mbeanAttributes = Arrays
398                 .asList(jmxValueParameter.split("[" + ATTRIBUTES_SEPARATOR + ']'));
399         final List<Object> jmxValues = getConvertedAttributes(mbeanAttributes);
400         final StringBuilder sb = new StringBuilder();
401         boolean first = true;
402         for (final Object jmxValue : jmxValues) {
403             if (first) {
404                 first = false;
405             } else {
406                 sb.append(ATTRIBUTES_SEPARATOR);
407             }
408             sb.append(jmxValue);
409         }
410         return sb.toString();
411     }
412
413     private String getAttributeDescription(String name, MBeanAttributeInfo[] attributeInfos) {
414         for (final MBeanAttributeInfo attributeInfo : attributeInfos) {
415             if (name.equals(attributeInfo.getName())) {
416                 // certaines descriptions d'attributs comme les NamingResources dans Tomcat 7 contiennent aussi des espaces qui se suivent
417                 final String attributeDescription = formatDescription(
418                         attributeInfo.getDescription());
419                 if (attributeDescription == null || name.equals(attributeDescription)
420                         || attributeDescription.isEmpty()) {
421                     // les attributs des MBeans de java.lang ont des descriptions égales aux noms,
422                     // ce sont des descriptions inutiles
423                     return null;
424                 }
425                 return attributeDescription;
426             }
427         }
428         return null;
429     }
430
431     /**
432      * Retourne le javax.management.MBeanServer de la plateforme.
433      * @return MBeanServer
434      */

435     public static MBeanServer getPlatformMBeanServer() {
436         return ManagementFactory.getPlatformMBeanServer();
437         // alternative (sauf pour Jenkins slaves):
438         //        final List<MBeanServer> mBeanServers = MBeanServerFactory.findMBeanServer(null);
439         //        if (!mBeanServers.isEmpty()) {
440         //            // il existe déjà un MBeanServer créé précédemment par Tomcat ou bien ci-dessous
441         //            return mBeanServers.get(0);
442         //        }
443         //        final MBeanServer server = MBeanServerFactory.createMBeanServer();
444         //        return server;
445     }
446
447     /**
448      * Retourne la liste de tous les {@link javax.management.MBeanServer}.
449      * @return List
450      */

451     private static List<MBeanServer> getMBeanServers() {
452         // par exemple avec JBoss 5.0.x, il y a un MBeanServer de la plateforme (defaultDomain null)
453         // et un MBeanServer de JBoss (defaultDomain "jboss")
454         return MBeanServerFactory.findMBeanServer(null);
455     }
456 }
457