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