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