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