001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020 021package org.granite.jmx; 022 023import java.beans.BeanInfo; 024import java.beans.Introspector; 025import java.beans.MethodDescriptor; 026import java.beans.PropertyDescriptor; 027import java.lang.annotation.Annotation; 028import java.lang.reflect.Method; 029import java.math.BigDecimal; 030import java.math.BigInteger; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Date; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039 040import javax.management.Attribute; 041import javax.management.AttributeList; 042import javax.management.AttributeNotFoundException; 043import javax.management.DynamicMBean; 044import javax.management.InvalidAttributeValueException; 045import javax.management.MBeanException; 046import javax.management.MBeanInfo; 047import javax.management.MBeanNotificationInfo; 048import javax.management.MBeanOperationInfo; 049import javax.management.ObjectName; 050import javax.management.ReflectionException; 051import javax.management.openmbean.ArrayType; 052import javax.management.openmbean.OpenDataException; 053import javax.management.openmbean.OpenMBeanAttributeInfo; 054import javax.management.openmbean.OpenMBeanAttributeInfoSupport; 055import javax.management.openmbean.OpenMBeanConstructorInfo; 056import javax.management.openmbean.OpenMBeanInfoSupport; 057import javax.management.openmbean.OpenMBeanOperationInfo; 058import javax.management.openmbean.OpenMBeanOperationInfoSupport; 059import javax.management.openmbean.OpenMBeanParameterInfo; 060import javax.management.openmbean.OpenMBeanParameterInfoSupport; 061import javax.management.openmbean.OpenType; 062import javax.management.openmbean.SimpleType; 063 064/** 065 * The OpenMBean class wraps an instance of any bean and introspects its properties and methods 066 * for MBeanAttributes and MBeanOperations. It implements all functionalities required by a 067 * {@link DynamicMBean} and returns an OpenMBeanInfo object. 068 * <br> 069 * <br> 070 * Limitations: 071 * <ul> 072 * <li>only attributes and operations are supported (no contructor and no notification),</li> 073 * <li>only {@link SimpleType} and {@link ArrayType} are supported (no composite type).</li> 074 * </ul> 075 * 076 * @author Franck WOLFF 077 */ 078@SuppressWarnings({ "unchecked", "rawtypes" }) 079public class OpenMBean implements DynamicMBean { 080 081 /////////////////////////////////////////////////////////////////////////// 082 // Fields. 083 084 private static final Map<Class<?>, SimpleType> SIMPLE_TYPES = new HashMap<Class<?>, SimpleType>(); 085 static { 086 SIMPLE_TYPES.put(Void.class, SimpleType.VOID); 087 SIMPLE_TYPES.put(Void.TYPE, SimpleType.VOID); 088 SIMPLE_TYPES.put(Boolean.class, SimpleType.BOOLEAN); 089 SIMPLE_TYPES.put(Boolean.TYPE, SimpleType.BOOLEAN); 090 SIMPLE_TYPES.put(Character.class, SimpleType.CHARACTER); 091 SIMPLE_TYPES.put(Character.TYPE, SimpleType.CHARACTER); 092 SIMPLE_TYPES.put(Byte.class, SimpleType.BYTE); 093 SIMPLE_TYPES.put(Byte.TYPE, SimpleType.BYTE); 094 SIMPLE_TYPES.put(Short.class, SimpleType.SHORT); 095 SIMPLE_TYPES.put(Short.TYPE, SimpleType.SHORT); 096 SIMPLE_TYPES.put(Integer.class, SimpleType.INTEGER); 097 SIMPLE_TYPES.put(Integer.TYPE, SimpleType.INTEGER); 098 SIMPLE_TYPES.put(Long.class, SimpleType.LONG); 099 SIMPLE_TYPES.put(Long.TYPE, SimpleType.LONG); 100 SIMPLE_TYPES.put(Float.class, SimpleType.FLOAT); 101 SIMPLE_TYPES.put(Float.TYPE, SimpleType.FLOAT); 102 SIMPLE_TYPES.put(Double.class, SimpleType.DOUBLE); 103 SIMPLE_TYPES.put(Double.TYPE, SimpleType.DOUBLE); 104 SIMPLE_TYPES.put(String.class, SimpleType.STRING); 105 SIMPLE_TYPES.put(BigDecimal.class, SimpleType.BIGDECIMAL); 106 SIMPLE_TYPES.put(BigInteger.class, SimpleType.BIGINTEGER); 107 SIMPLE_TYPES.put(Date.class, SimpleType.DATE); 108 SIMPLE_TYPES.put(ObjectName.class, SimpleType.OBJECTNAME); 109 } 110 111 private final MBeanInfo info; 112 private final Object instance; 113 114 private final Map<String, PropertyDescriptor> attributesMap = new HashMap<String, PropertyDescriptor>(); 115 private final Map<String, MethodDescriptor> operationsMap = new HashMap<String, MethodDescriptor>(); 116 117 /////////////////////////////////////////////////////////////////////////// 118 // Constructors. 119 120 /** 121 * Creates a new OpenMBean instance and instrospects its child class for attributes 122 * and operations. 123 */ 124 protected OpenMBean() { 125 this.info = init(getClass(), OpenMBean.class, attributesMap, operationsMap); 126 this.instance = this; 127 } 128 129 private OpenMBean(Class<?> beanClass, Class<?> stopClass, Object instance) { 130 this.info = init(beanClass, stopClass, attributesMap, operationsMap); 131 this.instance = instance; 132 } 133 134 /////////////////////////////////////////////////////////////////////////// 135 // Static OpenMBean creators. 136 137 /** 138 * Creates a new OpenMBean by introspecting and wrapping the <tt>instance</tt> parameter. 139 * This method search for an interface named <tt>instance.getClass().getSimpleName() + "MBean"</tt> 140 * and, if it finds it, uses it for introspection. Otherwise, the class of the <tt>instance</tt> 141 * object is used instead. 142 * 143 * @param instance an instance of a bean to introspect. 144 * @return a new OpenMBean instance that wraps the instance bean. 145 */ 146 public static OpenMBean createMBean(Object instance) { 147 148 Class<?> beanClass = null; 149 Class<?> instanceClass = instance.getClass(); 150 while (instanceClass != null) { 151 String interMBeanName = instanceClass.getSimpleName() + "MBean"; 152 for (Class<?> inter : instanceClass.getInterfaces()) { 153 if (interMBeanName.equals(inter.getSimpleName())) { 154 beanClass = inter; 155 break; 156 } 157 } 158 if (beanClass != null) 159 break; 160 instanceClass = instanceClass.getSuperclass(); 161 } 162 163 if (beanClass == null) 164 beanClass = instance.getClass(); 165 166 Class<?> stopClass = null; 167 if (!beanClass.isInterface()) { 168 stopClass = beanClass.getSuperclass(); 169 if (stopClass == null) 170 stopClass = Object.class; 171 } 172 173 return new OpenMBean(beanClass, stopClass, instance); 174 } 175 176 /** 177 * Creates a new OpenMBean by introspecting the <tt>beanClass</tt> parameter and wrapping 178 * the <tt>instance</tt> parameter. 179 * 180 * @param beanClass a class (or interface) used for introspection. 181 * @param instance the bean to encapsulate. 182 * @return a new OpenMBean instance that wraps the instance bean. 183 * @throws IllegalArgumentException if instance is not an instance of beanClass. 184 */ 185 public static OpenMBean createMBean(Class<?> beanClass, Object instance) { 186 if (!beanClass.isAssignableFrom(instance.getClass())) 187 throw new IllegalArgumentException("Instance " + instance + " should be an instance of " + beanClass); 188 189 Class<?> stopClass = null; 190 if (!beanClass.isInterface()) { 191 stopClass = beanClass.getSuperclass(); 192 if (stopClass == null) 193 stopClass = Object.class; 194 } 195 196 return new OpenMBean(beanClass, stopClass, instance); 197 } 198 199 /////////////////////////////////////////////////////////////////////////// 200 // Static initialization methods. 201 202 private static MBeanInfo init( 203 Class<?> beanClass, 204 Class<?> stopClass, 205 Map<String, PropertyDescriptor> attributesMap, 206 Map<String, MethodDescriptor> operationsMap) { 207 208 MBean mb = beanClass.getAnnotation(MBean.class); 209 210 String description = null; 211 if (mb != null) 212 description = mb.description(); 213 if (description == null) 214 description = beanClass.getSimpleName() + " MBean"; 215 216 List<OpenMBeanAttributeInfo> attributes = new ArrayList<OpenMBeanAttributeInfo>(); 217 List<OpenMBeanOperationInfo> operations = new ArrayList<OpenMBeanOperationInfo>(); 218 219 try { 220 BeanInfo beanInfo = ( 221 stopClass == null ? 222 Introspector.getBeanInfo(beanClass) : 223 Introspector.getBeanInfo(beanClass, stopClass) 224 ); 225 226 Set<Method> attributeMethods = new HashSet<Method>(); 227 228 // Attributes. 229 for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) { 230 231 MBeanAttribute mba = null; 232 if (property.getReadMethod() != null) 233 mba = property.getReadMethod().getAnnotation(MBeanAttribute.class); 234 if (mba == null && property.getWriteMethod() != null) 235 mba = property.getWriteMethod().getAnnotation(MBeanAttribute.class); 236 237 if (mba == null) 238 continue; 239 240 String name = property.getName(); 241 242 String desc = mba.description(); 243 if (desc == null) 244 desc = name.substring(0, 1).toUpperCase() + name.substring(1) + " Attribute"; 245 246 OpenType type = getOpenType(property.getPropertyType()); 247 248 attributes.add(new OpenMBeanAttributeInfoSupport( 249 name, 250 desc, 251 type, 252 property.getReadMethod() != null, 253 property.getWriteMethod() != null, 254 property.getReadMethod() != null && property.getReadMethod().getName().startsWith("is") 255 )); 256 attributesMap.put(property.getName(), property); 257 258 if (property.getReadMethod() != null) 259 attributeMethods.add(property.getReadMethod()); 260 if (property.getWriteMethod() != null) 261 attributeMethods.add(property.getWriteMethod()); 262 } 263 264 // Operations 265 for (MethodDescriptor method : beanInfo.getMethodDescriptors()) { 266 267 if (attributeMethods.contains(method.getMethod())) 268 continue; 269 270 MBeanOperation mbo = method.getMethod().getAnnotation(MBeanOperation.class); 271 272 if (mbo == null) 273 continue; 274 275 String name = method.getName(); 276 277 String desc = mbo.description(); 278 if (desc == null) 279 desc = name.substring(0, 1).toUpperCase() + name.substring(1) + " Operation"; 280 281 List<OpenMBeanParameterInfo> parameters = new ArrayList<OpenMBeanParameterInfo>(); 282 Annotation[][] annotations = method.getMethod().getParameterAnnotations(); 283 284 int i = 0; 285 for (Class<?> parameter : method.getMethod().getParameterTypes()) { 286 String paramName = getParameterName(annotations, i); 287 String paramDesc = getParameterDescription(annotations, i); 288 OpenType paramType = getOpenType(parameter); 289 290 parameters.add(new OpenMBeanParameterInfoSupport(paramName, paramDesc, paramType)); 291 292 i++; 293 } 294 295 OpenType returnedType = getOpenType(method.getMethod().getReturnType()); 296 297 int impact = MBeanOperationInfo.UNKNOWN; 298 if (mbo.impact() != null) { 299 switch (mbo.impact()) { 300 case ACTION: impact = MBeanOperationInfo.ACTION; break; 301 case ACTION_INFO: impact = MBeanOperationInfo.ACTION_INFO; break; 302 case INFO: impact = MBeanOperationInfo.INFO; break; 303 } 304 } 305 306 operations.add(new OpenMBeanOperationInfoSupport( 307 name, 308 desc, 309 parameters.toArray(new OpenMBeanParameterInfo[parameters.size()]), 310 returnedType, 311 impact 312 )); 313 314 String[] paramClasses = new String[parameters.size()]; 315 for (i = 0; i < parameters.size(); i++) 316 paramClasses[i] = parameters.get(i).getOpenType().getTypeName().toString(); 317 String qName = name + Arrays.toString(paramClasses); 318 319 operationsMap.put(qName, method); 320 } 321 } 322 catch (Exception e) { 323 throw new RuntimeException("Could not introspect MBean class: " + beanClass, e); 324 } 325 326 return new OpenMBeanInfoSupport( 327 beanClass.getName(), 328 description, 329 attributes.toArray(new OpenMBeanAttributeInfo[attributes.size()]), 330 new OpenMBeanConstructorInfo[0], 331 operations.toArray(new OpenMBeanOperationInfo[operations.size()]), 332 new MBeanNotificationInfo[0] 333 ); 334 } 335 336 private static String getParameterName(Annotation[][] paramAnnotations, int index) { 337 String name = null; 338 if (paramAnnotations != null && paramAnnotations.length > index) { 339 for (Annotation annot : paramAnnotations[index]) { 340 if (MBeanParameter.class.equals(annot.annotationType())) { 341 name = ((MBeanParameter)annot).name(); 342 break; 343 } 344 } 345 } 346 return (name != null ? name : "arg" + (index + 1)); 347 } 348 349 private static String getParameterDescription(Annotation[][] paramAnnotations, int index) { 350 String description = null; 351 if (paramAnnotations != null && paramAnnotations.length > index) { 352 for (Annotation annot : paramAnnotations[index]) { 353 if (MBeanParameter.class.equals(annot.annotationType())) { 354 description = ((MBeanParameter)annot).description(); 355 break; 356 } 357 } 358 } 359 return (description != null ? description : "Operation Parameter " + (index + 1)); 360 } 361 362 private static OpenType getOpenType(Class<?> clazz) throws OpenDataException { 363 364 if (SIMPLE_TYPES.containsKey(clazz)) 365 return SIMPLE_TYPES.get(clazz); 366 367 if (clazz.isArray()) { 368 int dimension = 1; 369 Class<?> componentType = clazz.getComponentType(); 370 while (componentType.isArray()) { 371 dimension++; 372 componentType = componentType.getComponentType(); 373 } 374 return new ArrayType(dimension, getOpenType(componentType)); 375 } 376 377 throw new OpenDataException("Unsupported type: " + clazz); 378 } 379 380 /////////////////////////////////////////////////////////////////////////// 381 // DynamicMBean implementation. 382 383 /** 384 * {@inheritDoc} 385 */ 386 public synchronized Object getAttribute(String attribute) 387 throws AttributeNotFoundException, MBeanException, ReflectionException { 388 389 PropertyDescriptor property = attributesMap.get(attribute); 390 if (property != null && property.getReadMethod() != null) { 391 try { 392 return property.getReadMethod().invoke(instance); 393 } 394 catch (Exception e) { 395 throw new ReflectionException(e, "Could not get attribute value: " + attribute); 396 } 397 } 398 throw new AttributeNotFoundException("Attribute " + attribute + " not found"); 399 } 400 401 /** 402 * {@inheritDoc} 403 */ 404 public synchronized AttributeList getAttributes(String[] names) { 405 AttributeList attributes = new AttributeList(); 406 for (String name : names) { 407 try { 408 attributes.add(new Attribute(name, getAttribute(name))); 409 } 410 catch (Exception e) { 411 // ignore... 412 } 413 } 414 return attributes; 415 } 416 417 /** 418 * {@inheritDoc} 419 */ 420 public synchronized MBeanInfo getMBeanInfo() { 421 return info; 422 } 423 424 /** 425 * {@inheritDoc} 426 */ 427 public synchronized Object invoke(String actionName, Object[] params, String[] signature) 428 throws MBeanException, ReflectionException { 429 430 if (signature == null) 431 signature = new String[0]; 432 433 String qName = actionName + Arrays.toString(signature); 434 435 MethodDescriptor method = operationsMap.get(qName); 436 if (method == null) 437 throw new RuntimeException("Method not found: " + qName); 438 439 try { 440 return method.getMethod().invoke(instance, params); 441 } 442 catch (Exception e) { 443 throw new ReflectionException(e, "Could not invoke operation: " + qName); 444 } 445 } 446 447 /** 448 * {@inheritDoc} 449 */ 450 public synchronized void setAttribute(Attribute attribute) 451 throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { 452 453 PropertyDescriptor property = attributesMap.get(attribute.getName()); 454 if (property != null && property.getWriteMethod() != null) { 455 try { 456 property.getWriteMethod().invoke(instance, attribute.getValue()); 457 } 458 catch (Exception e) { 459 throw new ReflectionException(e, "Could not set attribute value: " + attribute.getName() + "=" + attribute.getValue()); 460 } 461 } 462 else 463 throw new AttributeNotFoundException("Attribute " + attribute.getName() + " not found"); 464 } 465 466 /** 467 * {@inheritDoc} 468 */ 469 public synchronized AttributeList setAttributes(AttributeList attributes) { 470 AttributeList returnedAttributes = new AttributeList(); 471 for (Object a : attributes) { 472 Attribute attribute = (Attribute)a; 473 try { 474 setAttribute(attribute); 475 returnedAttributes.add(new Attribute(attribute.getName(), getAttribute(attribute.getName()))); 476 } 477 catch (Exception e) { 478 // Ignore... 479 } 480 } 481 return returnedAttributes; 482 } 483}