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