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