001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.monitoring.reporting.web.plugin.jmx; 018 019 import org.apache.commons.codec.binary.Base64; 020 import org.apache.commons.monitoring.MonitoringException; 021 import org.apache.commons.monitoring.configuration.Configuration; 022 import org.apache.commons.monitoring.reporting.web.handler.api.Regex; 023 import org.apache.commons.monitoring.reporting.web.handler.api.Template; 024 import org.apache.commons.monitoring.reporting.web.template.MapBuilder; 025 import org.apache.commons.monitoring.util.ClassLoaders; 026 027 import javax.management.MBeanAttributeInfo; 028 import javax.management.MBeanInfo; 029 import javax.management.MBeanOperationInfo; 030 import javax.management.MBeanParameterInfo; 031 import javax.management.MBeanServerConnection; 032 import javax.management.ObjectInstance; 033 import javax.management.ObjectName; 034 import javax.management.openmbean.CompositeData; 035 import javax.management.openmbean.TabularData; 036 import java.io.IOException; 037 import java.lang.management.ManagementFactory; 038 import java.lang.reflect.Array; 039 import java.lang.reflect.Field; 040 import java.util.Arrays; 041 import java.util.Collection; 042 import java.util.HashMap; 043 import java.util.LinkedList; 044 import java.util.List; 045 import java.util.Map; 046 import java.util.Set; 047 048 public class JMXEndpoints { 049 private static final boolean METHOD_INVOCATION_ALLOWED = Configuration.is(Configuration.COMMONS_MONITORING_PREFIX + "jmx.method.allowed", true); 050 051 private static final Map<String, Class<?>> WRAPPERS = new HashMap<String, Class<?>>(); 052 053 static { 054 for (final Class<?> c : Arrays.<Class<?>>asList(Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Character.class, Boolean.class )) { 055 try { 056 final Field f = c.getField("TYPE"); 057 Class<?> p = (Class<?>) f.get(null); 058 WRAPPERS.put(p.getName(), c); 059 } catch (Exception e) { 060 throw new AssertionError(e); 061 } 062 } 063 } 064 065 protected final MBeanServerConnection server; 066 067 public JMXEndpoints() { 068 this.server = ManagementFactory.getPlatformMBeanServer(); 069 } 070 071 @Regex("") 072 public Template home() throws IOException { 073 return new Template("jmx/main.vm", new MapBuilder<String, Object>().set("jmxTree", buildJmxTree()).build()); 074 } 075 076 @Regex("/operation/([^/]*)/([^/]*)/(.*)") 077 public String invokeOperation(final String objectNameBase64, final String method, final String[] parameters) { 078 if (!METHOD_INVOCATION_ALLOWED) { 079 throw new MonitoringException("Method invocation not allowed"); 080 } 081 082 try { 083 final ObjectName name = new ObjectName(new String(Base64.decodeBase64(objectNameBase64))); 084 final MBeanInfo info = server.getMBeanInfo(name); 085 for (final MBeanOperationInfo op : info.getOperations()) { 086 if (op.getName().equals(method)) { 087 final MBeanParameterInfo[] signature = op.getSignature(); 088 final String[] sign = new String[signature.length]; 089 for (int i = 0; i < sign.length; i++) { 090 sign[i] = signature[i].getType(); 091 } 092 final Object result = server.invoke(name, method, convertParams(signature, parameters), sign); 093 return "<div>Method was invoked and returned:</div>" + value(result); 094 } 095 } 096 } catch (final Exception e) { 097 return "<div class=\"alert alert-error\">\n" + 098 "\n" + e.getMessage() + "\n" + 099 "</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 // first the user can force the conversion 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 // try all conversions in cascade until it works 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 // no-op 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 }