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
021 package org.granite.jmx;
022
023 import java.beans.BeanInfo;
024 import java.beans.Introspector;
025 import java.beans.MethodDescriptor;
026 import java.beans.PropertyDescriptor;
027 import java.lang.annotation.Annotation;
028 import java.lang.reflect.Method;
029 import java.math.BigDecimal;
030 import java.math.BigInteger;
031 import java.util.ArrayList;
032 import java.util.Arrays;
033 import java.util.Date;
034 import java.util.HashMap;
035 import java.util.HashSet;
036 import java.util.List;
037 import java.util.Map;
038 import java.util.Set;
039
040 import javax.management.Attribute;
041 import javax.management.AttributeList;
042 import javax.management.AttributeNotFoundException;
043 import javax.management.DynamicMBean;
044 import javax.management.InvalidAttributeValueException;
045 import javax.management.MBeanException;
046 import javax.management.MBeanInfo;
047 import javax.management.MBeanNotificationInfo;
048 import javax.management.MBeanOperationInfo;
049 import javax.management.ObjectName;
050 import javax.management.ReflectionException;
051 import javax.management.openmbean.ArrayType;
052 import javax.management.openmbean.OpenDataException;
053 import javax.management.openmbean.OpenMBeanAttributeInfo;
054 import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
055 import javax.management.openmbean.OpenMBeanConstructorInfo;
056 import javax.management.openmbean.OpenMBeanInfoSupport;
057 import javax.management.openmbean.OpenMBeanOperationInfo;
058 import javax.management.openmbean.OpenMBeanOperationInfoSupport;
059 import javax.management.openmbean.OpenMBeanParameterInfo;
060 import javax.management.openmbean.OpenMBeanParameterInfoSupport;
061 import javax.management.openmbean.OpenType;
062 import 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" })
079 public 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 }