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.camel.management;
018    
019    import java.lang.reflect.Method;
020    import java.lang.reflect.Proxy;
021    import java.util.HashMap;
022    import java.util.LinkedHashSet;
023    import java.util.Map;
024    import java.util.Set;
025    import javax.management.Descriptor;
026    import javax.management.IntrospectionException;
027    import javax.management.JMException;
028    import javax.management.modelmbean.ModelMBeanAttributeInfo;
029    import javax.management.modelmbean.ModelMBeanInfo;
030    import javax.management.modelmbean.ModelMBeanInfoSupport;
031    import javax.management.modelmbean.ModelMBeanNotificationInfo;
032    import javax.management.modelmbean.ModelMBeanOperationInfo;
033    
034    import org.apache.camel.api.management.ManagedAttribute;
035    import org.apache.camel.api.management.ManagedNotification;
036    import org.apache.camel.api.management.ManagedNotifications;
037    import org.apache.camel.api.management.ManagedOperation;
038    import org.apache.camel.api.management.ManagedResource;
039    import org.apache.camel.util.IntrospectionSupport;
040    import org.apache.camel.util.ObjectHelper;
041    import org.slf4j.Logger;
042    import org.slf4j.LoggerFactory;
043    
044    /**
045     * A Camel specific {@link javax.management.MBeanInfo} assembler that reads the
046     * details from the {@link ManagedResource}, {@link ManagedAttribute}, {@link ManagedOperation},
047     * {@link ManagedNotification}, and {@link ManagedNotifications} annotations.
048     */
049    public class MBeanInfoAssembler {
050    
051        private static final Logger LOG = LoggerFactory.getLogger(MBeanInfoAssembler.class);
052    
053        /**
054         * Gets the {@link ModelMBeanInfo} for the given managed bean
055         *
056         * @param defaultManagedBean  the default managed bean
057         * @param customManagedBean   an optional custom managed bean
058         * @param objectName   the object name
059         * @return the model info, or <tt>null</tt> if not possible to create, for example due the managed bean is a proxy class
060         * @throws JMException is thrown if error creating the model info
061         */
062        public ModelMBeanInfo getMBeanInfo(Object defaultManagedBean, Object customManagedBean, String objectName) throws JMException {
063            // skip proxy classes
064            if (Proxy.isProxyClass(defaultManagedBean.getClass())) {
065                LOG.trace("Skip creating ModelMBeanInfo due proxy class {}", defaultManagedBean.getClass());
066                return null;
067            }
068    
069            // maps and lists to contain information about attributes and operations
070            Map<String, ManagedAttributeInfo> attributes = new HashMap<String, ManagedAttributeInfo>();
071            Set<ManagedOperationInfo> operations = new LinkedHashSet<ManagedOperationInfo>();
072            Set<ModelMBeanAttributeInfo> mBeanAttributes = new LinkedHashSet<ModelMBeanAttributeInfo>();
073            Set<ModelMBeanOperationInfo> mBeanOperations = new LinkedHashSet<ModelMBeanOperationInfo>();
074            Set<ModelMBeanNotificationInfo> mBeanNotifications = new LinkedHashSet<ModelMBeanNotificationInfo>();
075    
076            // extract details from default managed bean
077            extractAttributesAndOperations(defaultManagedBean.getClass(), attributes, operations);
078            extractMbeanAttributes(defaultManagedBean, attributes, mBeanAttributes, mBeanOperations);
079            extractMbeanOperations(defaultManagedBean, operations, mBeanOperations);
080            extractMbeanNotifications(defaultManagedBean, mBeanNotifications);
081    
082            // extract details from custom managed bean
083            if (customManagedBean != null) {
084                extractAttributesAndOperations(customManagedBean.getClass(), attributes, operations);
085                extractMbeanAttributes(customManagedBean, attributes, mBeanAttributes, mBeanOperations);
086                extractMbeanOperations(customManagedBean, operations, mBeanOperations);
087                extractMbeanNotifications(customManagedBean, mBeanNotifications);
088            }
089    
090            // create the ModelMBeanInfo
091            String name = getName(customManagedBean != null ? customManagedBean : defaultManagedBean, objectName);
092            String description = getDescription(customManagedBean != null ? customManagedBean : defaultManagedBean, objectName);
093            ModelMBeanAttributeInfo[] arrayAttributes = mBeanAttributes.toArray(new ModelMBeanAttributeInfo[mBeanAttributes.size()]);
094            ModelMBeanOperationInfo[] arrayOperations = mBeanOperations.toArray(new ModelMBeanOperationInfo[mBeanOperations.size()]);
095            ModelMBeanNotificationInfo[] arrayNotifications = mBeanNotifications.toArray(new ModelMBeanNotificationInfo[mBeanNotifications.size()]);
096    
097            ModelMBeanInfo info = new ModelMBeanInfoSupport(name, description, arrayAttributes, null, arrayOperations, arrayNotifications);
098            LOG.trace("Created ModelMBeanInfo {}", info);
099            return info;
100        }
101    
102        private void extractAttributesAndOperations(Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) {
103            // extract the class
104            doExtractAttributesAndOperations(managedClass, attributes, operations);
105    
106            // and then any sub classes
107            if (managedClass.getSuperclass() != null) {
108                Class<?> clazz = managedClass.getSuperclass();
109                // skip any JDK classes
110                if (!clazz.getName().startsWith("java")) {
111                    LOG.trace("Extracting attributes and operations from sub class: {}", clazz);
112                    extractAttributesAndOperations(clazz, attributes, operations);
113                }
114            }
115    
116            // and then any additional interfaces (as interfaces can be annotated as well)
117            if (managedClass.getInterfaces() != null) {
118                for (Class<?> clazz : managedClass.getInterfaces()) {
119                    // recursive as there may be multiple interfaces
120                    if (clazz.getName().startsWith("java")) {
121                        // skip any JDK classes
122                        continue;
123                    }
124                    LOG.trace("Extracting attributes and operations from implemented interface: {}", clazz);
125                    extractAttributesAndOperations(clazz, attributes, operations);
126                }
127            }
128        }
129    
130        private void doExtractAttributesAndOperations(Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) {
131            LOG.trace("Extracting attributes and operations from class: {}", managedClass);
132            for (Method method : managedClass.getDeclaredMethods()) {
133                LOG.trace("Extracting attributes and operations from method: {}", method);
134    
135                ManagedAttribute ma = method.getAnnotation(ManagedAttribute.class);
136                if (ma != null) {
137                    String key;
138                    String desc = ma.description();
139                    Method getter = null;
140                    Method setter = null;
141    
142                    if (IntrospectionSupport.isGetter(method)) {
143                        key = IntrospectionSupport.getGetterShorthandName(method);
144                        getter = method;
145                    } else if (IntrospectionSupport.isSetter(method)) {
146                        key = IntrospectionSupport.getSetterShorthandName(method);
147                        setter = method;
148                    } else {
149                        throw new IllegalArgumentException("@ManagedAttribute can only be used on Java bean methods, was: " + method + " on bean: " + managedClass);
150                    }
151    
152                    // they key must be capitalized
153                    key = ObjectHelper.capitalize(key);
154    
155                    // lookup first
156                    ManagedAttributeInfo info = attributes.get(key);
157                    if (info == null) {
158                        info = new ManagedAttributeInfo(key, desc);
159                    }
160                    if (getter != null) {
161                        info.setGetter(getter);
162                    }
163                    if (setter != null) {
164                        info.setSetter(setter);
165                    }
166    
167                    attributes.put(key, info);
168                }
169    
170                // operations
171                ManagedOperation mo = method.getAnnotation(ManagedOperation.class);
172                if (mo != null) {
173                    String desc = mo.description();
174                    Method operation = method;
175                    operations.add(new ManagedOperationInfo(desc, operation));
176                }
177            }
178        }
179    
180        private void extractMbeanAttributes(Object managedBean, Map<String, ManagedAttributeInfo> attributes,
181                                            Set<ModelMBeanAttributeInfo> mBeanAttributes, Set<ModelMBeanOperationInfo> mBeanOperations) throws IntrospectionException {
182    
183            for (ManagedAttributeInfo info : attributes.values()) {
184                ModelMBeanAttributeInfo mbeanAttribute = new ModelMBeanAttributeInfo(info.getKey(), info.getDescription(), info.getGetter(), info.getSetter());
185    
186                // add missing attribute descriptors, this is needed to have attributes accessible
187                Descriptor desc = mbeanAttribute.getDescriptor();
188                if (info.getGetter() != null) {
189                    desc.setField("getMethod", info.getGetter().getName());
190                    // attribute must also be added as mbean operation
191                    ModelMBeanOperationInfo mbeanOperation = new ModelMBeanOperationInfo(info.getKey(), info.getGetter());
192                    mBeanOperations.add(mbeanOperation);
193                }
194                if (info.getSetter() != null) {
195                    desc.setField("setMethod", info.getSetter().getName());
196                    // attribute must also be added as mbean operation
197                    ModelMBeanOperationInfo mbeanOperation = new ModelMBeanOperationInfo(info.getKey(), info.getSetter());
198                    mBeanOperations.add(mbeanOperation);
199                }
200                mbeanAttribute.setDescriptor(desc);
201    
202                mBeanAttributes.add(mbeanAttribute);
203                LOG.trace("Assembled attribute: {}", mbeanAttribute);
204            }
205        }
206    
207        private void extractMbeanOperations(Object managedBean, Set<ManagedOperationInfo> operations, Set<ModelMBeanOperationInfo> mBeanOperations) {
208            for (ManagedOperationInfo info : operations) {
209                ModelMBeanOperationInfo mbean = new ModelMBeanOperationInfo(info.getDescription(), info.getOperation());
210                mBeanOperations.add(mbean);
211                LOG.trace("Assembled operation: {}", mbean);
212            }
213        }
214    
215        private void extractMbeanNotifications(Object managedBean, Set<ModelMBeanNotificationInfo> mBeanNotifications) {
216            ManagedNotifications notifications = managedBean.getClass().getAnnotation(ManagedNotifications.class);
217            if (notifications != null) {
218                for (ManagedNotification notification : notifications.value()) {
219                    ModelMBeanNotificationInfo info = new ModelMBeanNotificationInfo(notification.notificationTypes(), notification.name(), notification.description());
220                    mBeanNotifications.add(info);
221                    LOG.trace("Assembled notification: {}", info);
222                }
223            }
224        }
225    
226        private String getDescription(Object managedBean, String objectName) {
227            ManagedResource mr = ObjectHelper.getAnnotation(managedBean, ManagedResource.class);
228            return mr != null ? mr.description() : "";
229        }
230    
231        private String getName(Object managedBean, String objectName) {
232            return managedBean.getClass().getName();
233        }
234    
235        private static final class ManagedAttributeInfo {
236            private String key;
237            private String description;
238            private Method getter;
239            private Method setter;
240    
241            private ManagedAttributeInfo(String key, String description) {
242                this.key = key;
243                this.description = description;
244            }
245    
246            public String getKey() {
247                return key;
248            }
249    
250            public String getDescription() {
251                return description;
252            }
253    
254            public Method getGetter() {
255                return getter;
256            }
257    
258            public void setGetter(Method getter) {
259                this.getter = getter;
260            }
261    
262            public Method getSetter() {
263                return setter;
264            }
265    
266            public void setSetter(Method setter) {
267                this.setter = setter;
268            }
269    
270            @Override
271            public String toString() {
272                return "ManagedAttributeInfo: [" + key + " + getter: " + getter + ", setter: " + setter + "]";
273            }
274        }
275    
276        private static final class ManagedOperationInfo {
277            private final String description;
278            private final Method operation;
279    
280            private ManagedOperationInfo(String description, Method operation) {
281                this.description = description;
282                this.operation = operation;
283            }
284    
285            public String getDescription() {
286                return description;
287            }
288    
289            public Method getOperation() {
290                return operation;
291            }
292    
293            @Override
294            public String toString() {
295                return "ManagedOperationInfo: [" + operation + "]";
296            }
297        }
298    
299    }