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    }