1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.monitoring.reporting.web.plugin.jmx;
18
19 import org.apache.commons.codec.binary.Base64;
20 import org.apache.commons.monitoring.MonitoringException;
21 import org.apache.commons.monitoring.configuration.Configuration;
22 import org.apache.commons.monitoring.reporting.web.handler.api.Regex;
23 import org.apache.commons.monitoring.reporting.web.handler.api.Template;
24 import org.apache.commons.monitoring.reporting.web.template.MapBuilder;
25 import org.apache.commons.monitoring.util.ClassLoaders;
26
27 import javax.management.MBeanAttributeInfo;
28 import javax.management.MBeanInfo;
29 import javax.management.MBeanOperationInfo;
30 import javax.management.MBeanParameterInfo;
31 import javax.management.MBeanServerConnection;
32 import javax.management.ObjectInstance;
33 import javax.management.ObjectName;
34 import javax.management.openmbean.CompositeData;
35 import javax.management.openmbean.TabularData;
36 import java.io.IOException;
37 import java.lang.management.ManagementFactory;
38 import java.lang.reflect.Array;
39 import java.lang.reflect.Field;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.HashMap;
43 import java.util.LinkedList;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47
48 public class JMXEndpoints {
49 private static final boolean METHOD_INVOCATION_ALLOWED = Configuration.is(Configuration.COMMONS_MONITORING_PREFIX + "jmx.method.allowed", true);
50
51 private static final Map<String, Class<?>> WRAPPERS = new HashMap<String, Class<?>>();
52
53 static {
54 for (final Class<?> c : Arrays.<Class<?>>asList(Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Character.class, Boolean.class )) {
55 try {
56 final Field f = c.getField("TYPE");
57 Class<?> p = (Class<?>) f.get(null);
58 WRAPPERS.put(p.getName(), c);
59 } catch (Exception e) {
60 throw new AssertionError(e);
61 }
62 }
63 }
64
65 protected final MBeanServerConnection server;
66
67 public JMXEndpoints() {
68 this.server = ManagementFactory.getPlatformMBeanServer();
69 }
70
71 @Regex("")
72 public Template home() throws IOException {
73 return new Template("jmx/main.vm", new MapBuilder<String, Object>().set("jmxTree", buildJmxTree()).build());
74 }
75
76 @Regex("/operation/([^/]*)/([^/]*)/(.*)")
77 public String invokeOperation(final String objectNameBase64, final String method, final String[] parameters) {
78 if (!METHOD_INVOCATION_ALLOWED) {
79 throw new MonitoringException("Method invocation not allowed");
80 }
81
82 try {
83 final ObjectName name = new ObjectName(new String(Base64.decodeBase64(objectNameBase64)));
84 final MBeanInfo info = server.getMBeanInfo(name);
85 for (final MBeanOperationInfo op : info.getOperations()) {
86 if (op.getName().equals(method)) {
87 final MBeanParameterInfo[] signature = op.getSignature();
88 final String[] sign = new String[signature.length];
89 for (int i = 0; i < sign.length; i++) {
90 sign[i] = signature[i].getType();
91 }
92 final Object result = server.invoke(name, method, convertParams(signature, parameters), sign);
93 return "<div>Method was invoked and returned:</div>" + value(result);
94 }
95 }
96 } catch (final Exception e) {
97 return "<div class=\"alert alert-error\">\n" +
98 "\n" + e.getMessage() + "\n" +
99 "</div>";
100 }
101
102 return "<div class=\"alert alert-error\">Operation" + method + " not found.</div>";
103 }
104
105 @Regex("/([^/]*)")
106 public Template mbean(final String objectNameBase64) {
107 try {
108 final ObjectName name = new ObjectName(new String(Base64.decodeBase64(objectNameBase64)));
109 final MBeanInfo info = server.getMBeanInfo(name);
110 return new Template("templates/jmx/mbean.vm",
111 new MapBuilder<String, Object>()
112 .set("objectname", name.toString())
113 .set("objectnameHash", Base64.encodeBase64String(name.toString().getBytes()))
114 .set("classname", info.getClassName())
115 .set("description", value(info.getDescription()))
116 .set("attributes", attributes(name, info))
117 .set("operations", operations(info))
118 .build(), false);
119 } catch (final Exception e) {
120 throw new MonitoringException(e);
121 }
122 }
123
124 private JMXNode buildJmxTree() throws IOException {
125 final JMXNode root = new JMXNode("/");
126
127 for (final ObjectInstance instance : server.queryMBeans(null, null)) {
128 final ObjectName objectName = instance.getObjectName();
129 JMXNode.addNode(root, objectName.getDomain(), objectName.getKeyPropertyListString());
130 }
131
132 return root;
133 }
134
135 private Object[] convertParams(final MBeanParameterInfo[] signature, final String[] params) {
136 if (params == null) {
137 return null;
138 }
139
140 final Object[] convertedParams = new Object[signature.length];
141 for (int i = 0; i < signature.length; i++) {
142 if (i < params.length) {
143 convertedParams[i] = convert(signature[i].getType(), params[i]);
144 } else {
145 convertedParams[i] = null;
146 }
147 }
148 return convertedParams;
149 }
150
151 public static Object convert(final String type, final String value) {
152 try {
153 if (WRAPPERS.containsKey(type)) {
154 if (type.equals(Character.TYPE.getName())) {
155 return value.charAt(0);
156 } else {
157 return tryStringConstructor(type, value);
158 }
159 }
160
161 if (type.equals(Character.class.getName())) {
162 return value.charAt(0);
163 }
164
165 if (Number.class.isAssignableFrom(ClassLoaders.current().loadClass(type))) {
166 return toNumber(value);
167 }
168
169 if (value == null || value.equals("null")) {
170 return null;
171 }
172
173 return tryStringConstructor(type, value);
174 } catch (final Exception e) {
175 throw new MonitoringException(e);
176 }
177 }
178
179 private static Number toNumber(final String value) throws NumberFormatException {
180
181 final char lastChar = Character.toLowerCase(value.charAt(value.length() - 1));
182 if (lastChar == 'd') {
183 return Double.valueOf(value.substring(0, value.length() - 1));
184 }
185 if (lastChar == 'l') {
186 return Long.valueOf(value.substring(0, value.length() - 1));
187 }
188 if (lastChar == 'f') {
189 return Float.valueOf(value.substring(0, value.length() - 1));
190 }
191
192
193 for (final Class<?> clazz : new Class<?>[] { Integer.class, Long.class, Double.class }) {
194 try {
195 return Number.class.cast(clazz.getMethod("valueOf").invoke(null, value));
196 } catch (final Exception e) {
197
198 }
199 }
200
201 throw new MonitoringException(value + " is not a number");
202 }
203
204 private static Object tryStringConstructor(String type, final String value) throws Exception {
205 return ClassLoaders.current().loadClass(type).getConstructor(String.class).newInstance(value);
206 }
207
208 private Collection<MBeanOperation> operations(final MBeanInfo info) {
209 final Collection<MBeanOperation> operations = new LinkedList<MBeanOperation>();
210 for (final MBeanOperationInfo operationInfo : info.getOperations()) {
211 final MBeanOperation mBeanOperation = new MBeanOperation(operationInfo.getName(), operationInfo.getReturnType());
212 for (final MBeanParameterInfo param : operationInfo.getSignature()) {
213 mBeanOperation.getParameters().add(new MBeanParameter(param.getName(), param.getType()));
214 }
215 operations.add(mBeanOperation);
216 }
217 return operations;
218 }
219
220 private Collection<MBeanAttribute> attributes(final ObjectName name, final MBeanInfo info) {
221 final Collection<MBeanAttribute> list = new LinkedList<MBeanAttribute>();
222 for (final MBeanAttributeInfo attribute : info.getAttributes()) {
223 Object value;
224 try {
225 value = server.getAttribute(name, attribute.getName());
226 } catch (final Exception e) {
227 value = "<div class=\"alert-error\">" + e.getMessage() + "</div>";
228 }
229 list.add(new MBeanAttribute(attribute.getName(), attribute.getType(), attribute.getDescription(), value(value)));
230 }
231 return list;
232 }
233
234 private static String value(final Object value) {
235 try {
236 if (value == null) {
237 return "";
238 }
239
240 if (value.getClass().isArray()) {
241 final int length = Array.getLength(value);
242 if (length == 0) {
243 return "";
244 }
245
246 final StringBuilder builder = new StringBuilder().append("<ul>");
247 for (int i = 0; i < length; i++) {
248 builder.append("<li>").append(value(Array.get(value, i))).append("</li>");
249 }
250 builder.append("</ul>");
251 return builder.toString();
252 }
253
254 if (Collection.class.isInstance(value)) {
255 final StringBuilder builder = new StringBuilder().append("<ul>");
256 for (final Object o : Collection.class.cast(value)) {
257 builder.append("<li>").append(value(o)).append("</li>");
258 }
259 builder.append("</ul>");
260 return builder.toString();
261 }
262
263 if (TabularData.class.isInstance(value)) {
264 final TabularData td = TabularData.class.cast(value);
265 final StringBuilder builder = new StringBuilder().append("<table class=\"table table-condensed\">");
266 for (final Object type : td.keySet()) {
267 final List<?> values = (List<?>) type;
268 final CompositeData data = td.get(values.toArray(new Object[values.size()]));
269 builder.append("<tr>");
270 final Set<String> dataKeys = data.getCompositeType().keySet();
271 for (final String k : data.getCompositeType().keySet()) {
272 builder.append("<td>").append(value(data.get(k))).append("</td>");
273 }
274 builder.append("</tr>");
275 }
276 builder.append("</table>");
277
278 return builder.toString();
279 }
280
281 if (CompositeData.class.isInstance(value)) {
282 final CompositeData cd = CompositeData.class.cast(value);
283 final Set<String> keys = cd.getCompositeType().keySet();
284
285 final StringBuilder builder = new StringBuilder().append("<table class=\"table table-condensed\">");
286 for (final String type : keys) {
287 builder.append("<tr><td>").append(type).append("</td><td>").append(value(cd.get(type))).append("</td></tr>");
288 }
289 builder.append("</table>");
290
291 return builder.toString();
292
293 }
294
295 if (Map.class.isInstance(value)) {
296 final Map<?, ?> map = Map.class.cast(value);
297
298 final StringBuilder builder = new StringBuilder().append("<table class=\"table table-condensed\">");
299 for (final Map.Entry<?, ?> entry : map.entrySet()) {
300 builder.append("<tr><tr>").append(value(entry.getKey())).append("</td><td>").append(value(entry.getValue())).append("</td></tr>");
301 }
302 builder.append("</table>");
303
304 return builder.toString();
305
306 }
307
308 return value.toString();
309 } catch (final Exception e) {
310 throw new MonitoringException(e);
311 }
312 }
313
314 public static class MBeanAttribute {
315 private final String name;
316 private final String type;
317 private final String description;
318 private final String value;
319
320 public MBeanAttribute(final String name, final String type, final String description, final String value) {
321 this.name = name;
322 this.type = type;
323 this.value = value;
324 if (description != null) {
325 this.description = description;
326 } else {
327 this.description = "No description";
328 }
329 }
330
331 public String getName() {
332 return name;
333 }
334
335 public String getType() {
336 return type;
337 }
338
339 public String getDescription() {
340 return description;
341 }
342
343 public String getValue() {
344 return value;
345 }
346 }
347
348 public static class MBeanOperation {
349 private final String name;
350 private final String returnType;
351 private final Collection<MBeanParameter> parameters = new LinkedList<MBeanParameter>();
352
353 public MBeanOperation(final String name, final String returnType) {
354 this.name = name;
355 this.returnType = returnType;
356 }
357
358 public String getName() {
359 return name;
360 }
361
362 public String getReturnType() {
363 return returnType;
364 }
365
366 public Collection<MBeanParameter> getParameters() {
367 return parameters;
368 }
369 }
370
371 public static class MBeanParameter {
372 private final String name;
373 private final String type;
374
375 public MBeanParameter(final String name, final String type) {
376 this.name = name;
377 this.type = type.replace("java.lang.", "");
378 }
379
380 public String getName() {
381 return name;
382 }
383
384 public String getType() {
385 return type;
386 }
387 }
388 }