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 */ 017package org.apache.camel.management; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Proxy; 021import java.util.LinkedHashMap; 022import java.util.LinkedHashSet; 023import java.util.Map; 024import java.util.Set; 025import javax.management.Descriptor; 026import javax.management.IntrospectionException; 027import javax.management.JMException; 028import javax.management.modelmbean.ModelMBeanAttributeInfo; 029import javax.management.modelmbean.ModelMBeanInfo; 030import javax.management.modelmbean.ModelMBeanInfoSupport; 031import javax.management.modelmbean.ModelMBeanNotificationInfo; 032import javax.management.modelmbean.ModelMBeanOperationInfo; 033 034import org.apache.camel.CamelContext; 035import org.apache.camel.Service; 036import org.apache.camel.api.management.ManagedAttribute; 037import org.apache.camel.api.management.ManagedNotification; 038import org.apache.camel.api.management.ManagedNotifications; 039import org.apache.camel.api.management.ManagedOperation; 040import org.apache.camel.api.management.ManagedResource; 041import org.apache.camel.util.IntrospectionSupport; 042import org.apache.camel.util.LRUCache; 043import org.apache.camel.util.LRUWeakCache; 044import org.apache.camel.util.ObjectHelper; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * A Camel specific {@link javax.management.MBeanInfo} assembler that reads the 050 * details from the {@link ManagedResource}, {@link ManagedAttribute}, {@link ManagedOperation}, 051 * {@link ManagedNotification}, and {@link ManagedNotifications} annotations. 052 */ 053public class MBeanInfoAssembler implements Service { 054 055 private static final Logger LOG = LoggerFactory.getLogger(MBeanInfoAssembler.class); 056 057 // use a cache to speedup gathering JMX MBeanInfo for known classes 058 // use a weak cache as we dont want the cache to keep around as it reference classes 059 // which could prevent classloader to unload classes if being referenced from this cache 060 private final LRUCache<Class<?>, MBeanAttributesAndOperations> cache = new LRUWeakCache<Class<?>, MBeanAttributesAndOperations>(1000); 061 062 public MBeanInfoAssembler() { 063 } 064 065 @Deprecated 066 public MBeanInfoAssembler(CamelContext camelContext) { 067 } 068 069 @Override 070 public void start() throws Exception { 071 // noop 072 } 073 074 @Override 075 public void stop() throws Exception { 076 if (LOG.isDebugEnabled()) { 077 LOG.debug("Clearing cache[size={}, hits={}, misses={}, evicted={}]", new Object[]{cache.size(), cache.getHits(), cache.getMisses(), cache.getEvicted()}); 078 } 079 cache.clear(); 080 } 081 082 /** 083 * Structure to hold cached mbean attributes and operations for a given class. 084 */ 085 private static final class MBeanAttributesAndOperations { 086 private Map<String, ManagedAttributeInfo> attributes; 087 private Set<ManagedOperationInfo> operations; 088 } 089 090 /** 091 * Gets the {@link ModelMBeanInfo} for the given managed bean 092 * 093 * @param defaultManagedBean the default managed bean 094 * @param customManagedBean an optional custom managed bean 095 * @param objectName the object name 096 * @return the model info, or <tt>null</tt> if not possible to create, for example due the managed bean is a proxy class 097 * @throws JMException is thrown if error creating the model info 098 */ 099 public ModelMBeanInfo getMBeanInfo(Object defaultManagedBean, Object customManagedBean, String objectName) throws JMException { 100 // skip proxy classes 101 if (defaultManagedBean != null && Proxy.isProxyClass(defaultManagedBean.getClass())) { 102 LOG.trace("Skip creating ModelMBeanInfo due proxy class {}", defaultManagedBean.getClass()); 103 return null; 104 } 105 106 // maps and lists to contain information about attributes and operations 107 Map<String, ManagedAttributeInfo> attributes = new LinkedHashMap<String, ManagedAttributeInfo>(); 108 Set<ManagedOperationInfo> operations = new LinkedHashSet<ManagedOperationInfo>(); 109 Set<ModelMBeanAttributeInfo> mBeanAttributes = new LinkedHashSet<ModelMBeanAttributeInfo>(); 110 Set<ModelMBeanOperationInfo> mBeanOperations = new LinkedHashSet<ModelMBeanOperationInfo>(); 111 Set<ModelMBeanNotificationInfo> mBeanNotifications = new LinkedHashSet<ModelMBeanNotificationInfo>(); 112 113 // extract details from default managed bean 114 if (defaultManagedBean != null) { 115 extractAttributesAndOperations(defaultManagedBean.getClass(), attributes, operations); 116 extractMbeanAttributes(defaultManagedBean, attributes, mBeanAttributes, mBeanOperations); 117 extractMbeanOperations(defaultManagedBean, operations, mBeanOperations); 118 extractMbeanNotifications(defaultManagedBean, mBeanNotifications); 119 } 120 121 // extract details from custom managed bean 122 if (customManagedBean != null) { 123 extractAttributesAndOperations(customManagedBean.getClass(), attributes, operations); 124 extractMbeanAttributes(customManagedBean, attributes, mBeanAttributes, mBeanOperations); 125 extractMbeanOperations(customManagedBean, operations, mBeanOperations); 126 extractMbeanNotifications(customManagedBean, mBeanNotifications); 127 } 128 129 // create the ModelMBeanInfo 130 String name = getName(customManagedBean != null ? customManagedBean : defaultManagedBean, objectName); 131 String description = getDescription(customManagedBean != null ? customManagedBean : defaultManagedBean, objectName); 132 ModelMBeanAttributeInfo[] arrayAttributes = mBeanAttributes.toArray(new ModelMBeanAttributeInfo[mBeanAttributes.size()]); 133 ModelMBeanOperationInfo[] arrayOperations = mBeanOperations.toArray(new ModelMBeanOperationInfo[mBeanOperations.size()]); 134 ModelMBeanNotificationInfo[] arrayNotifications = mBeanNotifications.toArray(new ModelMBeanNotificationInfo[mBeanNotifications.size()]); 135 136 ModelMBeanInfo info = new ModelMBeanInfoSupport(name, description, arrayAttributes, null, arrayOperations, arrayNotifications); 137 LOG.trace("Created ModelMBeanInfo {}", info); 138 return info; 139 } 140 141 private void extractAttributesAndOperations(Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) { 142 MBeanAttributesAndOperations cached = cache.get(managedClass); 143 if (cached == null) { 144 doExtractAttributesAndOperations(managedClass, attributes, operations); 145 cached = new MBeanAttributesAndOperations(); 146 cached.attributes = new LinkedHashMap<String, ManagedAttributeInfo>(attributes); 147 cached.operations = new LinkedHashSet<ManagedOperationInfo>(operations); 148 149 // clear before we re-add them 150 attributes.clear(); 151 operations.clear(); 152 153 // add to cache 154 cache.put(managedClass, cached); 155 } 156 157 attributes.putAll(cached.attributes); 158 operations.addAll(cached.operations); 159 } 160 161 private void doExtractAttributesAndOperations(Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) { 162 // extract the class 163 doDoExtractAttributesAndOperations(managedClass, attributes, operations); 164 165 // and then any sub classes 166 if (managedClass.getSuperclass() != null) { 167 Class<?> clazz = managedClass.getSuperclass(); 168 // skip any JDK classes 169 if (!clazz.getName().startsWith("java")) { 170 LOG.trace("Extracting attributes and operations from sub class: {}", clazz); 171 doExtractAttributesAndOperations(clazz, attributes, operations); 172 } 173 } 174 175 // and then any additional interfaces (as interfaces can be annotated as well) 176 if (managedClass.getInterfaces() != null) { 177 for (Class<?> clazz : managedClass.getInterfaces()) { 178 // recursive as there may be multiple interfaces 179 if (clazz.getName().startsWith("java")) { 180 // skip any JDK classes 181 continue; 182 } 183 LOG.trace("Extracting attributes and operations from implemented interface: {}", clazz); 184 doExtractAttributesAndOperations(clazz, attributes, operations); 185 } 186 } 187 } 188 189 private void doDoExtractAttributesAndOperations(Class<?> managedClass, Map<String, ManagedAttributeInfo> attributes, Set<ManagedOperationInfo> operations) { 190 LOG.trace("Extracting attributes and operations from class: {}", managedClass); 191 192 // introspect the class, and leverage the cache to have better performance 193 IntrospectionSupport.ClassInfo cache = IntrospectionSupport.cacheClass(managedClass); 194 195 for (IntrospectionSupport.MethodInfo cacheInfo : cache.methods) { 196 // must be from declaring class 197 if (cacheInfo.method.getDeclaringClass() != managedClass) { 198 continue; 199 } 200 201 LOG.trace("Extracting attributes and operations from method: {}", cacheInfo.method); 202 ManagedAttribute ma = cacheInfo.method.getAnnotation(ManagedAttribute.class); 203 if (ma != null) { 204 String key; 205 String desc = ma.description(); 206 Method getter = null; 207 Method setter = null; 208 boolean mask = ma.mask(); 209 210 if (cacheInfo.isGetter) { 211 key = cacheInfo.getterOrSetterShorthandName; 212 getter = cacheInfo.method; 213 } else if (cacheInfo.isSetter) { 214 key = cacheInfo.getterOrSetterShorthandName; 215 setter = cacheInfo.method; 216 } else { 217 throw new IllegalArgumentException("@ManagedAttribute can only be used on Java bean methods, was: " + cacheInfo.method + " on bean: " + managedClass); 218 } 219 220 // they key must be capitalized 221 key = ObjectHelper.capitalize(key); 222 223 // lookup first 224 ManagedAttributeInfo info = attributes.get(key); 225 if (info == null) { 226 info = new ManagedAttributeInfo(key, desc); 227 } 228 if (getter != null) { 229 info.setGetter(getter); 230 } 231 if (setter != null) { 232 info.setSetter(setter); 233 } 234 info.setMask(mask); 235 236 attributes.put(key, info); 237 } 238 239 // operations 240 ManagedOperation mo = cacheInfo.method.getAnnotation(ManagedOperation.class); 241 if (mo != null) { 242 String desc = mo.description(); 243 Method operation = cacheInfo.method; 244 boolean mask = mo.mask(); 245 operations.add(new ManagedOperationInfo(desc, operation, mask)); 246 } 247 } 248 } 249 250 private void extractMbeanAttributes(Object managedBean, Map<String, ManagedAttributeInfo> attributes, 251 Set<ModelMBeanAttributeInfo> mBeanAttributes, Set<ModelMBeanOperationInfo> mBeanOperations) throws IntrospectionException { 252 253 for (ManagedAttributeInfo info : attributes.values()) { 254 ModelMBeanAttributeInfo mbeanAttribute = new ModelMBeanAttributeInfo(info.getKey(), info.getDescription(), info.getGetter(), info.getSetter()); 255 256 // add missing attribute descriptors, this is needed to have attributes accessible 257 Descriptor desc = mbeanAttribute.getDescriptor(); 258 259 desc.setField("mask", info.isMask() ? "true" : "false"); 260 if (info.getGetter() != null) { 261 desc.setField("getMethod", info.getGetter().getName()); 262 // attribute must also be added as mbean operation 263 ModelMBeanOperationInfo mbeanOperation = new ModelMBeanOperationInfo(info.getKey(), info.getGetter()); 264 Descriptor opDesc = mbeanOperation.getDescriptor(); 265 opDesc.setField("mask", info.isMask() ? "true" : "false"); 266 mbeanOperation.setDescriptor(opDesc); 267 mBeanOperations.add(mbeanOperation); 268 } 269 if (info.getSetter() != null) { 270 desc.setField("setMethod", info.getSetter().getName()); 271 // attribute must also be added as mbean operation 272 ModelMBeanOperationInfo mbeanOperation = new ModelMBeanOperationInfo(info.getKey(), info.getSetter()); 273 mBeanOperations.add(mbeanOperation); 274 } 275 mbeanAttribute.setDescriptor(desc); 276 277 mBeanAttributes.add(mbeanAttribute); 278 LOG.trace("Assembled attribute: {}", mbeanAttribute); 279 } 280 } 281 282 private void extractMbeanOperations(Object managedBean, Set<ManagedOperationInfo> operations, Set<ModelMBeanOperationInfo> mBeanOperations) { 283 for (ManagedOperationInfo info : operations) { 284 ModelMBeanOperationInfo mbean = new ModelMBeanOperationInfo(info.getDescription(), info.getOperation()); 285 Descriptor opDesc = mbean.getDescriptor(); 286 opDesc.setField("mask", info.isMask() ? "true" : "false"); 287 mbean.setDescriptor(opDesc); 288 mBeanOperations.add(mbean); 289 LOG.trace("Assembled operation: {}", mbean); 290 } 291 } 292 293 private void extractMbeanNotifications(Object managedBean, Set<ModelMBeanNotificationInfo> mBeanNotifications) { 294 ManagedNotifications notifications = managedBean.getClass().getAnnotation(ManagedNotifications.class); 295 if (notifications != null) { 296 for (ManagedNotification notification : notifications.value()) { 297 ModelMBeanNotificationInfo info = new ModelMBeanNotificationInfo(notification.notificationTypes(), notification.name(), notification.description()); 298 mBeanNotifications.add(info); 299 LOG.trace("Assembled notification: {}", info); 300 } 301 } 302 } 303 304 private String getDescription(Object managedBean, String objectName) { 305 ManagedResource mr = ObjectHelper.getAnnotation(managedBean, ManagedResource.class); 306 return mr != null ? mr.description() : ""; 307 } 308 309 private String getName(Object managedBean, String objectName) { 310 return managedBean.getClass().getName(); 311 } 312 313 private static final class ManagedAttributeInfo { 314 private String key; 315 private String description; 316 private Method getter; 317 private Method setter; 318 private boolean mask; 319 320 private ManagedAttributeInfo(String key, String description) { 321 this.key = key; 322 this.description = description; 323 } 324 325 public String getKey() { 326 return key; 327 } 328 329 public String getDescription() { 330 return description; 331 } 332 333 public Method getGetter() { 334 return getter; 335 } 336 337 public void setGetter(Method getter) { 338 this.getter = getter; 339 } 340 341 public Method getSetter() { 342 return setter; 343 } 344 345 public void setSetter(Method setter) { 346 this.setter = setter; 347 } 348 349 public boolean isMask() { 350 return mask; 351 } 352 353 public void setMask(boolean mask) { 354 this.mask = mask; 355 } 356 357 @Override 358 public String toString() { 359 return "ManagedAttributeInfo: [" + key + " + getter: " + getter + ", setter: " + setter + "]"; 360 } 361 } 362 363 private static final class ManagedOperationInfo { 364 private final String description; 365 private final Method operation; 366 private final boolean mask; 367 368 private ManagedOperationInfo(String description, Method operation, boolean mask) { 369 this.description = description; 370 this.operation = operation; 371 this.mask = mask; 372 } 373 374 public String getDescription() { 375 return description; 376 } 377 378 public Method getOperation() { 379 return operation; 380 } 381 382 public boolean isMask() { 383 return mask; 384 } 385 386 @Override 387 public String toString() { 388 return "ManagedOperationInfo: [" + operation + "]"; 389 } 390 } 391 392}