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.servicemix.jbi.management;
018
019 import java.beans.PropertyChangeEvent;
020 import java.beans.PropertyChangeListener;
021 import java.beans.PropertyDescriptor;
022 import java.lang.reflect.InvocationTargetException;
023 import java.util.Date;
024 import java.util.Hashtable;
025 import java.util.LinkedHashMap;
026 import java.util.Map;
027 import java.util.concurrent.ExecutorService;
028
029 import javax.management.Attribute;
030 import javax.management.AttributeChangeNotification;
031 import javax.management.AttributeChangeNotificationFilter;
032 import javax.management.AttributeList;
033 import javax.management.AttributeNotFoundException;
034 import javax.management.Descriptor;
035 import javax.management.InvalidAttributeValueException;
036 import javax.management.ListenerNotFoundException;
037 import javax.management.MBeanAttributeInfo;
038 import javax.management.MBeanException;
039 import javax.management.MBeanInfo;
040 import javax.management.MBeanNotificationInfo;
041 import javax.management.MBeanOperationInfo;
042 import javax.management.MBeanRegistration;
043 import javax.management.MBeanServer;
044 import javax.management.NotCompliantMBeanException;
045 import javax.management.Notification;
046 import javax.management.NotificationBroadcasterSupport;
047 import javax.management.NotificationFilter;
048 import javax.management.NotificationListener;
049 import javax.management.ObjectName;
050 import javax.management.ReflectionException;
051 import javax.management.RuntimeOperationsException;
052 import javax.management.StandardMBean;
053 import javax.management.modelmbean.DescriptorSupport;
054 import javax.management.modelmbean.ModelMBeanNotificationBroadcaster;
055 import javax.management.modelmbean.ModelMBeanNotificationInfo;
056
057 import org.apache.commons.beanutils.MethodUtils;
058 import org.apache.commons.beanutils.PropertyUtilsBean;
059 import org.apache.commons.logging.Log;
060 import org.apache.commons.logging.LogFactory;
061
062 /**
063 * An MBean wrapper for an Existing Object
064 *
065 * @version $Revision: 570136 $
066 */
067 public class BaseStandardMBean extends StandardMBean implements ModelMBeanNotificationBroadcaster, MBeanRegistration,
068 PropertyChangeListener {
069
070 private static final Log LOG = LogFactory.getLog(BaseStandardMBean.class);
071
072 private static final Map<String, Class<?>> PRIMITIVE_CLASSES = new Hashtable<String, Class<?>>(8);
073 {
074 PRIMITIVE_CLASSES.put(Boolean.TYPE.toString(), Boolean.TYPE);
075 PRIMITIVE_CLASSES.put(Character.TYPE.toString(), Character.TYPE);
076 PRIMITIVE_CLASSES.put(Byte.TYPE.toString(), Byte.TYPE);
077 PRIMITIVE_CLASSES.put(Short.TYPE.toString(), Short.TYPE);
078 PRIMITIVE_CLASSES.put(Integer.TYPE.toString(), Integer.TYPE);
079 PRIMITIVE_CLASSES.put(Long.TYPE.toString(), Long.TYPE);
080 PRIMITIVE_CLASSES.put(Float.TYPE.toString(), Float.TYPE);
081 PRIMITIVE_CLASSES.put(Double.TYPE.toString(), Double.TYPE);
082 }
083
084 protected ExecutorService executorService;
085
086 private Map<String, CachedAttribute> cachedAttributes = new LinkedHashMap<String, CachedAttribute>();
087
088 // used to maintain insertion ordering
089 private PropertyUtilsBean beanUtil = new PropertyUtilsBean();
090
091 private NotificationBroadcasterSupport broadcasterSupport = new NotificationBroadcasterSupport();
092
093 private MBeanAttributeInfo[] attributeInfos;
094
095 private MBeanInfo beanInfo;
096
097 // this values are set after registering with the MBeanServer//
098 private ObjectName objectName;
099
100 private MBeanServer beanServer;
101
102 /**
103 * Constructor
104 *
105 * @param object
106 * @param interfaceMBean
107 * @param description
108 * @param attrs
109 * @param ops
110 * @param executorService2
111 * @throws ReflectionException
112 * @throws NotCompliantMBeanException
113 */
114 public BaseStandardMBean(Object object, Class interfaceMBean, String description,
115 MBeanAttributeInfo[] attrs, MBeanOperationInfo[] ops,
116 ExecutorService executorService) throws ReflectionException, NotCompliantMBeanException {
117 super(object, interfaceMBean);
118 this.attributeInfos = attrs;
119 buildAttributes(object, this.attributeInfos);
120 this.beanInfo = new MBeanInfo(object.getClass().getName(), description, attrs, null, ops, getNotificationInfo());
121 this.executorService = executorService;
122 }
123
124 /**
125 * @return the MBeanINfo for this MBean
126 */
127 public MBeanInfo getMBeanInfo() {
128 return beanInfo;
129 }
130
131 /**
132 * Retrieve ObjectName of the MBean - set after registration
133 *
134 * @return the ObjectName of the MBean
135 */
136 public ObjectName getObjectName() {
137 return objectName;
138 }
139
140 /**
141 * Retrieve the MBeanServer - set after registration
142 *
143 * @return the beanServer
144 */
145 public MBeanServer getBeanServer() {
146 return beanServer;
147 }
148
149 /**
150 * Get the Value of an Attribute
151 *
152 * @param name
153 * @return the value of the Attribute
154 * @throws AttributeNotFoundException
155 * @throws MBeanException
156 * @throws ReflectionException
157 */
158 public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException {
159 Object result = null;
160 CachedAttribute ca = cachedAttributes.get(name);
161 if (ca == null) {
162 // the use of proxies in MX4J has a bug - in which the caps can be
163 // changed on
164 // an attribute
165 for (Map.Entry<String, CachedAttribute> entry : cachedAttributes.entrySet()) {
166 String key = entry.getKey();
167 if (key.equalsIgnoreCase(name)) {
168 ca = entry.getValue();
169 break;
170 }
171 }
172 }
173 if (ca != null) {
174 result = getCurrentValue(ca);
175 } else {
176 throw new AttributeNotFoundException("Could not locate " + name);
177 }
178 return result;
179 }
180
181 /**
182 * Set the Attribute
183 *
184 * @param attr
185 * @throws AttributeNotFoundException
186 * @throws InvalidAttributeValueException
187 * @throws MBeanException
188 * @throws ReflectionException
189 */
190 public void setAttribute(Attribute attr) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException,
191 ReflectionException {
192 String name = attr.getName();
193 CachedAttribute ca = cachedAttributes.get(name);
194 if (ca != null) {
195 Attribute old = ca.getAttribute();
196 try {
197 ca.updateAttribute(beanUtil, attr);
198 sendAttributeChangeNotification(old, attr);
199 } catch (NoSuchMethodException e) {
200 throw new ReflectionException(e);
201 } catch (IllegalAccessException e) {
202 throw new ReflectionException(e);
203 } catch (InvocationTargetException e) {
204 Throwable t = e.getTargetException();
205 if (t instanceof Exception) {
206 throw new MBeanException(e);
207 }
208 throw new MBeanException(e);
209 }
210 } else {
211 throw new AttributeNotFoundException("Could not locate " + name);
212 }
213 }
214
215 /**
216 * Update a named attribute
217 *
218 * @param name
219 * @param value
220 */
221 public void updateAttribute(String name, Object value) {
222 CachedAttribute ca = cachedAttributes.get(name);
223 if (ca != null) {
224 Attribute old = ca.getAttribute();
225 ca.updateAttributeValue(value);
226 try {
227 sendAttributeChangeNotification(old, ca.getAttribute());
228 } catch (RuntimeOperationsException e) {
229 LOG.error("Failed to update attribute: " + name + " to new value: " + value, e);
230 } catch (MBeanException e) {
231 LOG.error("Failed to update attribute: " + name + " to new value: " + value, e);
232 }
233 }
234 }
235
236 /**
237 * Attribute change - fire notification
238 *
239 * @param event
240 */
241 public void propertyChange(PropertyChangeEvent event) {
242 updateAttribute(event.getPropertyName(), event.getNewValue());
243 }
244
245 /**
246 * @param attributes -
247 * array of Attribute names
248 * @return AttributeList of matching Attributes
249 */
250 public AttributeList getAttributes(String[] attributes) {
251 AttributeList result = null;
252 try {
253 if (attributes != null) {
254 result = new AttributeList();
255 for (int i = 0; i < attributes.length; i++) {
256 CachedAttribute ca = cachedAttributes.get(attributes[i]);
257 ca.updateValue(beanUtil);
258 result.add(ca.getAttribute());
259 }
260 } else {
261 // Do this to maintain insertion ordering
262 for (Map.Entry<String, CachedAttribute> entry : cachedAttributes.entrySet()) {
263 CachedAttribute ca = entry.getValue();
264 ca.updateValue(beanUtil);
265 result.add(ca.getAttribute());
266 }
267 }
268 } catch (MBeanException e) {
269 LOG.error("Caught excdeption building attributes", e);
270 }
271 return result;
272 }
273
274 /**
275 * Set values of Attributes
276 *
277 * @param attributes
278 * @return the list of Attributes set with their new values
279 */
280 public AttributeList setAttributes(AttributeList attributes) {
281 AttributeList result = new AttributeList();
282 if (attributes != null) {
283 for (int i = 0; i < attributes.size(); i++) {
284 Attribute attribute = (Attribute) attributes.get(i);
285 try {
286 setAttribute(attribute);
287 } catch (AttributeNotFoundException e) {
288 LOG.warn("Failed to setAttribute(" + attribute + ")", e);
289 } catch (InvalidAttributeValueException e) {
290 LOG.warn("Failed to setAttribute(" + attribute + ")", e);
291 } catch (MBeanException e) {
292 LOG.warn("Failed to setAttribute(" + attribute + ")", e);
293 } catch (ReflectionException e) {
294 LOG.warn("Failed to setAttribute(" + attribute + ")", e);
295 }
296 result.add(attribute);
297 }
298 }
299 return result;
300 }
301
302 /**
303 * Invoke an operation
304 *
305 * @param name
306 * @param params
307 * @param signature
308 * @return result of invoking an operation
309 * @throws MBeanException
310 * @throws ReflectionException
311 */
312 public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException {
313 ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
314 try {
315 Class<?>[] parameterTypes = new Class<?>[signature.length];
316 for (int i = 0; i < parameterTypes.length; i++) {
317 parameterTypes[i] = PRIMITIVE_CLASSES.get(signature[i]);
318 if (parameterTypes[i] == null) {
319 parameterTypes[i] = Class.forName(signature[i]);
320 }
321 }
322 Thread.currentThread().setContextClassLoader(getImplementation().getClass().getClassLoader());
323 return MethodUtils.invokeMethod(getImplementation(), name, params, parameterTypes);
324 } catch (ClassNotFoundException e) {
325 throw new ReflectionException(e);
326 } catch (NoSuchMethodException e) {
327 throw new ReflectionException(e);
328 } catch (IllegalAccessException e) {
329 throw new ReflectionException(e);
330 } catch (InvocationTargetException e) {
331 Throwable t = e.getTargetException();
332 if (t instanceof Exception) {
333 throw new MBeanException((Exception) t);
334 } else {
335 throw new MBeanException(e);
336 }
337 } finally {
338 Thread.currentThread().setContextClassLoader(oldCl);
339 }
340 }
341
342 /**
343 * Called at registration
344 *
345 * @param mbs
346 * @param on
347 * @return the ObjectName
348 * @throws Exception
349 */
350 public ObjectName preRegister(MBeanServer mbs, ObjectName on) throws Exception {
351 if (mbs != null) {
352 this.beanServer = mbs;
353 }
354 if (on != null) {
355 this.objectName = on;
356 }
357 return on;
358 }
359
360 /**
361 * Caled post registration
362 *
363 * @param done
364 */
365 public void postRegister(Boolean done) {
366 }
367
368 /**
369 * Called before removal from the MBeanServer
370 *
371 * @throws Exception
372 */
373 public void preDeregister() throws Exception {
374 }
375
376 /**
377 * Called after removal from the MBeanServer
378 */
379 public void postDeregister() {
380 }
381
382 /**
383 * @param notification
384 * @throws MBeanException
385 * @throws RuntimeOperationsException
386 */
387 public void sendNotification(final Notification notification) throws MBeanException, RuntimeOperationsException {
388 if (notification != null && !executorService.isShutdown()) {
389 executorService.execute(new Runnable() {
390 public void run() {
391 broadcasterSupport.sendNotification(notification);
392 }
393 });
394 }
395 }
396
397 /**
398 * @param text
399 * @throws MBeanException
400 * @throws RuntimeOperationsException
401 */
402 public void sendNotification(String text) throws MBeanException, RuntimeOperationsException {
403 if (text != null) {
404 Notification myNtfyObj = new Notification("jmx.modelmbean.generic", this, 1, text);
405 sendNotification(myNtfyObj);
406 }
407 }
408
409 /**
410 * @param l
411 * @param attrName
412 * @param handback
413 * @throws MBeanException
414 * @throws RuntimeOperationsException
415 * @throws IllegalArgumentException
416 */
417 public void addAttributeChangeNotificationListener(NotificationListener l, String attrName, Object handback) throws MBeanException,
418 RuntimeOperationsException, IllegalArgumentException {
419 AttributeChangeNotificationFilter currFilter = new AttributeChangeNotificationFilter();
420 currFilter.enableAttribute(attrName);
421 broadcasterSupport.addNotificationListener(l, currFilter, handback);
422 }
423
424 /**
425 * @param l
426 * @param attrName
427 * @throws MBeanException
428 * @throws RuntimeOperationsException
429 * @throws ListenerNotFoundException
430 */
431 public void removeAttributeChangeNotificationListener(NotificationListener l, String attrName) throws MBeanException,
432 RuntimeOperationsException, ListenerNotFoundException {
433 broadcasterSupport.removeNotificationListener(l);
434 }
435
436 /**
437 * @param notification
438 * @throws MBeanException
439 * @throws RuntimeOperationsException
440 */
441 public void sendAttributeChangeNotification(AttributeChangeNotification notification) throws MBeanException,
442 RuntimeOperationsException {
443 sendNotification(notification);
444 }
445
446 /**
447 * @param oldAttr
448 * @param newAttr
449 * @throws MBeanException
450 * @throws RuntimeOperationsException
451 */
452 public void sendAttributeChangeNotification(Attribute oldAttr, Attribute newAttr) throws MBeanException, RuntimeOperationsException {
453 if (!oldAttr.equals(newAttr)) {
454 AttributeChangeNotification notification = new AttributeChangeNotification(objectName, 1, (new Date()).getTime(),
455 "AttributeChange", oldAttr.getName(), newAttr.getValue().getClass().toString(), oldAttr.getValue(),
456 newAttr.getValue());
457 sendAttributeChangeNotification(notification);
458 }
459 }
460
461 /**
462 * @return notificationInfo
463 */
464 public final MBeanNotificationInfo[] getNotificationInfo() {
465 MBeanNotificationInfo[] result = new MBeanNotificationInfo[2];
466 Descriptor genericDescriptor = new DescriptorSupport(new String[] {
467 "name=GENERIC", "descriptorType=notification", "log=T",
468 "severity=5", "displayName=jmx.modelmbean.generic" });
469 result[0] = new ModelMBeanNotificationInfo(new String[] {"jmx.modelmbean.generic" }, "GENERIC",
470 "A text notification has been issued by the managed resource", genericDescriptor);
471 Descriptor attributeDescriptor = new DescriptorSupport(new String[] {"name=ATTRIBUTE_CHANGE", "descriptorType=notification",
472 "log=T", "severity=5", "displayName=jmx.attribute.change" });
473 result[1] = new ModelMBeanNotificationInfo(new String[] {"jmx.attribute.change" }, "ATTRIBUTE_CHANGE",
474 "Signifies that an observed MBean attribute value has changed", attributeDescriptor);
475 return result;
476 }
477
478 /**
479 * @param l
480 * @param filter
481 * @param handle
482 * @throws IllegalArgumentException
483 */
484 public void addNotificationListener(NotificationListener l, NotificationFilter filter,
485 Object handle) throws IllegalArgumentException {
486 broadcasterSupport.addNotificationListener(l, filter, handle);
487 }
488
489 /**
490 * @param l
491 * @throws ListenerNotFoundException
492 */
493 public void removeNotificationListener(NotificationListener l) throws ListenerNotFoundException {
494 broadcasterSupport.removeNotificationListener(l);
495 }
496
497 private Object getCurrentValue(CachedAttribute ca) throws MBeanException {
498 Object result = null;
499 if (ca != null) {
500 try {
501 result = beanUtil.getProperty(ca.getBean(), ca.getName());
502 } catch (IllegalAccessException e) {
503 throw new MBeanException(e);
504 } catch (InvocationTargetException e) {
505 throw new MBeanException(e);
506 } catch (NoSuchMethodException e) {
507 throw new MBeanException(e);
508 }
509 }
510 return result;
511 }
512
513 /**
514 * build internal Map of CachedAttributes
515 *
516 * @param obj
517 * @param attrs
518 * @throws ReflectionException
519 */
520 private void buildAttributes(Object obj, MBeanAttributeInfo[] attrs) throws ReflectionException {
521 if (attrs != null) {
522 for (int i = 0; i < attrs.length; i++) {
523 try {
524 String name = attrs[i].getName();
525 PropertyDescriptor pd = beanUtil.getPropertyDescriptor(obj, name);
526 Object value = beanUtil.getProperty(obj, name);
527 Attribute attribute = new Attribute(name, value);
528 CachedAttribute ca = new CachedAttribute(attribute);
529 ca.setBean(obj);
530 ca.setPropertyDescriptor(pd);
531 cachedAttributes.put(name, ca);
532 } catch (NoSuchMethodException e) {
533 throw new ReflectionException(e);
534 } catch (IllegalAccessException e) {
535 throw new ReflectionException(e);
536 } catch (InvocationTargetException e) {
537 throw new ReflectionException(e);
538 }
539 }
540 }
541 }
542 }