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