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