| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.camel.support.management; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.management.Descriptor; |
| import javax.management.IntrospectionException; |
| import javax.management.JMException; |
| import javax.management.modelmbean.ModelMBeanAttributeInfo; |
| import javax.management.modelmbean.ModelMBeanInfo; |
| import javax.management.modelmbean.ModelMBeanInfoSupport; |
| import javax.management.modelmbean.ModelMBeanNotificationInfo; |
| import javax.management.modelmbean.ModelMBeanOperationInfo; |
| |
| import org.apache.camel.CamelContext; |
| import org.apache.camel.ExtendedCamelContext; |
| import org.apache.camel.Service; |
| import org.apache.camel.api.management.ManagedAttribute; |
| import org.apache.camel.api.management.ManagedNotification; |
| import org.apache.camel.api.management.ManagedNotifications; |
| import org.apache.camel.api.management.ManagedOperation; |
| import org.apache.camel.api.management.ManagedResource; |
| import org.apache.camel.spi.BeanIntrospection; |
| import org.apache.camel.support.LRUCache; |
| import org.apache.camel.support.LRUCacheFactory; |
| import org.apache.camel.util.ObjectHelper; |
| import org.apache.camel.util.StringHelper; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A Camel specific {@link javax.management.MBeanInfo} assembler that reads the |
| * details from the {@link ManagedResource}, {@link ManagedAttribute}, {@link ManagedOperation}, |
| * {@link ManagedNotification}, and {@link ManagedNotifications} annotations. |
| */ |
| public class MBeanInfoAssembler implements Service { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(MBeanInfoAssembler.class); |
| |
| // use a cache to speedup gathering JMX MBeanInfo for known classes |
| // use a weak cache as we dont want the cache to keep around as it reference classes |
| // which could prevent classloader to unload classes if being referenced from this cache |
| private Map<Class<?>, MBeanAttributesAndOperations> cache; |
| |
| public MBeanInfoAssembler() { |
| } |
| |
| @Override |
| public void start() { |
| cache = LRUCacheFactory.newLRUWeakCache(1000); |
| } |
| |
| @Override |
| public void stop() { |
| if (cache != null) { |
| if (LOG.isDebugEnabled() && cache instanceof LRUCache) { |
| LRUCache cache = (LRUCache) this.cache; |
| LOG.debug("Clearing cache[size={}, hits={}, misses={}, evicted={}]", cache.size(), cache.getHits(), cache.getMisses(), cache.getEvicted()); |
| } |
| cache.clear(); |
| } |
| } |
| |
| /** |
| * Structure to hold cached mbean attributes and operations for a given class. |
| */ |
| private static final class MBeanAttributesAndOperations { |
| private Map<String, ManagedAttributeInfo> attributes; |
| private Set<ManagedOperationInfo> operations; |
| } |
| |
| /** |
| * Gets the {@link ModelMBeanInfo} for the given managed bean |
| * |
| * @param defaultManagedBean the default managed bean |
| * @param customManagedBean an optional custom managed bean |
| * @param objectName the object name |
| * @return the model info, or <tt>null</tt> if not possible to create, for example due the managed bean is a proxy class |
| * @throws JMException is thrown if error creating the model info |
| */ |
| public ModelMBeanInfo getMBeanInfo(CamelContext camelContext, Object defaultManagedBean, Object customManagedBean, String objectName) throws JMException { |
| |
| // skip proxy classes |
| if (defaultManagedBean != null && Proxy.isProxyClass(defaultManagedBean.getClass())) { |
| LOG.trace("Skip creating ModelMBeanInfo due proxy class {}", defaultManagedBean.getClass()); |
| return null; |
| } |
| |
| // maps and lists to contain information about attributes and operations |
| Map<String, ManagedAttributeInfo> attributes = new LinkedHashMap<>(); |
| Set<ManagedOperationInfo> operations = new LinkedHashSet<>(); |
| Set<ModelMBeanAttributeInfo> mBeanAttributes = new LinkedHashSet<>(); |
| Set<ModelMBeanOperationInfo> mBeanOperations = new LinkedHashSet<>(); |
| Set<ModelMBeanNotificationInfo> mBeanNotifications = new LinkedHashSet<>(); |
| |
| // extract details from default managed bean |
| if (defaultManagedBean != null) { |
| extractAttributesAndOperations(camelContext, defaultManagedBean.getClass(), attributes, operations); |
| extractMbeanAttributes(defaultManagedBean, attributes, mBeanAttributes, mBeanOperations); |
| extractMbeanOperations(defaultManagedBean, operations, mBeanOperations); |
| extractMbeanNotifications(defaultManagedBean, mBeanNotifications); |
| } |
| |
| // extract details from custom managed bean |
| if (customManagedBean != null) { |
| extractAttributesAndOperations(camelContext, customManagedBean.getClass(), attributes, operations); |
| extractMbeanAttributes(customManagedBean, attributes, mBeanAttributes, mBeanOperations); |
| extractMbeanOperations(customManagedBean, operations, mBeanOperations); |
| extractMbeanNotifications(customManagedBean, mBeanNotifications); |
| } |
| |
| // create the ModelMBeanInfo |
| String name = getName(customManagedBean != null ? customManagedBean : defaultManagedBean, objectName); |
| String description = getDescription(customManagedBean != null ? customManagedBean : defaultManagedBean, objectName); |
| ModelMBeanAttributeInfo[] arrayAttributes = mBeanAttributes.toArray(new ModelMBeanAttributeInfo[mBeanAttributes.size()]); |
| ModelMBeanOperationInfo[] arrayOperations = mBeanOperations.toArray(new ModelMBeanOperationInfo[mBeanOperations.size()]); |
| ModelMBeanNotificationInfo[] arrayNotifications = mBeanNotifications.toArray(new ModelMBeanNotificationInfo[mBeanNotifications.size()]); |
| |
| ModelMBeanInfo info = new ModelMBeanInfoSupport(name, description, arrayAttributes, null, arrayOperations, arrayNotifications); |
| LOG.trace("Created ModelMBeanInfo {}", info); |
| return info; |
| } |
| |
| private void extractAttributesAndOperations(CamelContext camelContext, Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) { |
| MBeanAttributesAndOperations cached = cache.get(managedClass); |
| if (cached == null) { |
| doExtractAttributesAndOperations(camelContext, managedClass, attributes, operations); |
| cached = new MBeanAttributesAndOperations(); |
| cached.attributes = new LinkedHashMap<>(attributes); |
| cached.operations = new LinkedHashSet<>(operations); |
| |
| // clear before we re-add them |
| attributes.clear(); |
| operations.clear(); |
| |
| // add to cache |
| cache.put(managedClass, cached); |
| } |
| |
| attributes.putAll(cached.attributes); |
| operations.addAll(cached.operations); |
| } |
| |
| private void doExtractAttributesAndOperations(CamelContext camelContext, Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) { |
| // extract the class |
| doDoExtractAttributesAndOperations(camelContext, managedClass, attributes, operations); |
| |
| // and then any sub classes |
| if (managedClass.getSuperclass() != null) { |
| Class<?> clazz = managedClass.getSuperclass(); |
| // skip any JDK classes |
| if (!clazz.getName().startsWith("java")) { |
| LOG.trace("Extracting attributes and operations from sub class: {}", clazz); |
| doExtractAttributesAndOperations(camelContext, clazz, attributes, operations); |
| } |
| } |
| |
| // and then any additional interfaces (as interfaces can be annotated as well) |
| if (managedClass.getInterfaces() != null) { |
| for (Class<?> clazz : managedClass.getInterfaces()) { |
| // recursive as there may be multiple interfaces |
| if (clazz.getName().startsWith("java")) { |
| // skip any JDK classes |
| continue; |
| } |
| LOG.trace("Extracting attributes and operations from implemented interface: {}", clazz); |
| doExtractAttributesAndOperations(camelContext, clazz, attributes, operations); |
| } |
| } |
| } |
| |
| private void doDoExtractAttributesAndOperations(CamelContext camelContext, Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) { |
| LOG.trace("Extracting attributes and operations from class: {}", managedClass); |
| |
| // introspect the class, and leverage the cache to have better performance |
| BeanIntrospection.ClassInfo cache = camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().cacheClass(managedClass); |
| |
| for (BeanIntrospection.MethodInfo cacheInfo : cache.methods) { |
| // must be from declaring class |
| if (cacheInfo.method.getDeclaringClass() != managedClass) { |
| continue; |
| } |
| |
| LOG.trace("Extracting attributes and operations from method: {}", cacheInfo.method); |
| ManagedAttribute ma = cacheInfo.method.getAnnotation(ManagedAttribute.class); |
| if (ma != null) { |
| String key; |
| String desc = ma.description(); |
| Method getter = null; |
| Method setter = null; |
| boolean mask = ma.mask(); |
| |
| if (cacheInfo.isGetter) { |
| key = cacheInfo.getterOrSetterShorthandName; |
| getter = cacheInfo.method; |
| } else if (cacheInfo.isSetter) { |
| key = cacheInfo.getterOrSetterShorthandName; |
| setter = cacheInfo.method; |
| } else { |
| throw new IllegalArgumentException("@ManagedAttribute can only be used on Java bean methods, was: " + cacheInfo.method + " on bean: " + managedClass); |
| } |
| |
| // they key must be capitalized |
| key = StringHelper.capitalize(key); |
| |
| // lookup first |
| ManagedAttributeInfo info = attributes.get(key); |
| if (info == null) { |
| info = new ManagedAttributeInfo(key, desc); |
| } |
| if (getter != null) { |
| info.setGetter(getter); |
| } |
| if (setter != null) { |
| info.setSetter(setter); |
| } |
| info.setMask(mask); |
| |
| attributes.put(key, info); |
| } |
| |
| // operations |
| ManagedOperation mo = cacheInfo.method.getAnnotation(ManagedOperation.class); |
| if (mo != null) { |
| String desc = mo.description(); |
| Method operation = cacheInfo.method; |
| boolean mask = mo.mask(); |
| operations.add(new ManagedOperationInfo(desc, operation, mask)); |
| } |
| } |
| } |
| |
| private void extractMbeanAttributes(Object managedBean, Map<String, ManagedAttributeInfo> attributes, |
| Set<ModelMBeanAttributeInfo> mBeanAttributes, Set<ModelMBeanOperationInfo> mBeanOperations) throws IntrospectionException { |
| |
| for (ManagedAttributeInfo info : attributes.values()) { |
| ModelMBeanAttributeInfo mbeanAttribute = new ModelMBeanAttributeInfo(info.getKey(), info.getDescription(), info.getGetter(), info.getSetter()); |
| |
| // add missing attribute descriptors, this is needed to have attributes accessible |
| Descriptor desc = mbeanAttribute.getDescriptor(); |
| |
| desc.setField("mask", info.isMask() ? "true" : "false"); |
| if (info.getGetter() != null) { |
| desc.setField("getMethod", info.getGetter().getName()); |
| // attribute must also be added as mbean operation |
| ModelMBeanOperationInfo mbeanOperation = new ModelMBeanOperationInfo(info.getKey(), info.getGetter()); |
| Descriptor opDesc = mbeanOperation.getDescriptor(); |
| opDesc.setField("mask", info.isMask() ? "true" : "false"); |
| mbeanOperation.setDescriptor(opDesc); |
| mBeanOperations.add(mbeanOperation); |
| } |
| if (info.getSetter() != null) { |
| desc.setField("setMethod", info.getSetter().getName()); |
| // attribute must also be added as mbean operation |
| ModelMBeanOperationInfo mbeanOperation = new ModelMBeanOperationInfo(info.getKey(), info.getSetter()); |
| mBeanOperations.add(mbeanOperation); |
| } |
| mbeanAttribute.setDescriptor(desc); |
| |
| mBeanAttributes.add(mbeanAttribute); |
| LOG.trace("Assembled attribute: {}", mbeanAttribute); |
| } |
| } |
| |
| private void extractMbeanOperations(Object managedBean, Set<ManagedOperationInfo> operations, Set<ModelMBeanOperationInfo> mBeanOperations) { |
| for (ManagedOperationInfo info : operations) { |
| ModelMBeanOperationInfo mbean = new ModelMBeanOperationInfo(info.getDescription(), info.getOperation()); |
| Descriptor opDesc = mbean.getDescriptor(); |
| opDesc.setField("mask", info.isMask() ? "true" : "false"); |
| mbean.setDescriptor(opDesc); |
| mBeanOperations.add(mbean); |
| LOG.trace("Assembled operation: {}", mbean); |
| } |
| } |
| |
| private void extractMbeanNotifications(Object managedBean, Set<ModelMBeanNotificationInfo> mBeanNotifications) { |
| ManagedNotifications notifications = managedBean.getClass().getAnnotation(ManagedNotifications.class); |
| if (notifications != null) { |
| for (ManagedNotification notification : notifications.value()) { |
| ModelMBeanNotificationInfo info = new ModelMBeanNotificationInfo(notification.notificationTypes(), notification.name(), notification.description()); |
| mBeanNotifications.add(info); |
| LOG.trace("Assembled notification: {}", info); |
| } |
| } |
| } |
| |
| private String getDescription(Object managedBean, String objectName) { |
| ManagedResource mr = ObjectHelper.getAnnotation(managedBean, ManagedResource.class); |
| return mr != null ? mr.description() : ""; |
| } |
| |
| private String getName(Object managedBean, String objectName) { |
| return managedBean.getClass().getName(); |
| } |
| |
| private static final class ManagedAttributeInfo { |
| private String key; |
| private String description; |
| private Method getter; |
| private Method setter; |
| private boolean mask; |
| |
| private ManagedAttributeInfo(String key, String description) { |
| this.key = key; |
| this.description = description; |
| } |
| |
| public String getKey() { |
| return key; |
| } |
| |
| public String getDescription() { |
| return description; |
| } |
| |
| public Method getGetter() { |
| return getter; |
| } |
| |
| public void setGetter(Method getter) { |
| this.getter = getter; |
| } |
| |
| public Method getSetter() { |
| return setter; |
| } |
| |
| public void setSetter(Method setter) { |
| this.setter = setter; |
| } |
| |
| public boolean isMask() { |
| return mask; |
| } |
| |
| public void setMask(boolean mask) { |
| this.mask = mask; |
| } |
| |
| @Override |
| public String toString() { |
| return "ManagedAttributeInfo: [" + key + " + getter: " + getter + ", setter: " + setter + "]"; |
| } |
| } |
| |
| private static final class ManagedOperationInfo { |
| private final String description; |
| private final Method operation; |
| private final boolean mask; |
| |
| private ManagedOperationInfo(String description, Method operation, boolean mask) { |
| this.description = description; |
| this.operation = operation; |
| this.mask = mask; |
| } |
| |
| public String getDescription() { |
| return description; |
| } |
| |
| public Method getOperation() { |
| return operation; |
| } |
| |
| public boolean isMask() { |
| return mask; |
| } |
| |
| @Override |
| public String toString() { |
| return "ManagedOperationInfo: [" + operation + "]"; |
| } |
| } |
| |
| } |