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