001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jmx.export.assembler; 018 019import java.beans.PropertyDescriptor; 020import java.lang.reflect.Method; 021 022import javax.management.Descriptor; 023import javax.management.MBeanParameterInfo; 024import javax.management.modelmbean.ModelMBeanNotificationInfo; 025 026import org.springframework.aop.support.AopUtils; 027import org.springframework.beans.BeanUtils; 028import org.springframework.beans.factory.InitializingBean; 029import org.springframework.jmx.export.metadata.InvalidMetadataException; 030import org.springframework.jmx.export.metadata.JmxAttributeSource; 031import org.springframework.jmx.export.metadata.JmxMetadataUtils; 032import org.springframework.jmx.export.metadata.ManagedAttribute; 033import org.springframework.jmx.export.metadata.ManagedMetric; 034import org.springframework.jmx.export.metadata.ManagedNotification; 035import org.springframework.jmx.export.metadata.ManagedOperation; 036import org.springframework.jmx.export.metadata.ManagedOperationParameter; 037import org.springframework.jmx.export.metadata.ManagedResource; 038import org.springframework.lang.Nullable; 039import org.springframework.util.Assert; 040import org.springframework.util.ObjectUtils; 041import org.springframework.util.StringUtils; 042 043/** 044 * Implementation of the {@link MBeanInfoAssembler} interface that reads 045 * the management interface information from source level metadata. 046 * 047 * <p>Uses the {@link JmxAttributeSource} strategy interface, so that 048 * metadata can be read using any supported implementation. Out of the box, 049 * Spring provides an implementation based on annotations: 050 * {@code AnnotationJmxAttributeSource}. 051 * 052 * @author Rob Harrop 053 * @author Juergen Hoeller 054 * @author Jennifer Hickey 055 * @since 1.2 056 * @see #setAttributeSource 057 * @see org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource 058 */ 059public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssembler 060 implements AutodetectCapableMBeanInfoAssembler, InitializingBean { 061 062 @Nullable 063 private JmxAttributeSource attributeSource; 064 065 066 /** 067 * Create a new {@code MetadataMBeanInfoAssembler} which needs to be 068 * configured through the {@link #setAttributeSource} method. 069 */ 070 public MetadataMBeanInfoAssembler() { 071 } 072 073 /** 074 * Create a new {@code MetadataMBeanInfoAssembler} for the given 075 * {@code JmxAttributeSource}. 076 * @param attributeSource the JmxAttributeSource to use 077 */ 078 public MetadataMBeanInfoAssembler(JmxAttributeSource attributeSource) { 079 Assert.notNull(attributeSource, "JmxAttributeSource must not be null"); 080 this.attributeSource = attributeSource; 081 } 082 083 084 /** 085 * Set the {@code JmxAttributeSource} implementation to use for 086 * reading the metadata from the bean class. 087 * @see org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource 088 */ 089 public void setAttributeSource(JmxAttributeSource attributeSource) { 090 Assert.notNull(attributeSource, "JmxAttributeSource must not be null"); 091 this.attributeSource = attributeSource; 092 } 093 094 @Override 095 public void afterPropertiesSet() { 096 if (this.attributeSource == null) { 097 throw new IllegalArgumentException("Property 'attributeSource' is required"); 098 } 099 } 100 101 private JmxAttributeSource obtainAttributeSource() { 102 Assert.state(this.attributeSource != null, "No JmxAttributeSource set"); 103 return this.attributeSource; 104 } 105 106 107 /** 108 * Throws an IllegalArgumentException if it encounters a JDK dynamic proxy. 109 * Metadata can only be read from target classes and CGLIB proxies! 110 */ 111 @Override 112 protected void checkManagedBean(Object managedBean) throws IllegalArgumentException { 113 if (AopUtils.isJdkDynamicProxy(managedBean)) { 114 throw new IllegalArgumentException( 115 "MetadataMBeanInfoAssembler does not support JDK dynamic proxies - " + 116 "export the target beans directly or use CGLIB proxies instead"); 117 } 118 } 119 120 /** 121 * Used for autodetection of beans. Checks to see if the bean's class has a 122 * {@code ManagedResource} attribute. If so it will add it list of included beans. 123 * @param beanClass the class of the bean 124 * @param beanName the name of the bean in the bean factory 125 */ 126 @Override 127 public boolean includeBean(Class<?> beanClass, String beanName) { 128 return (obtainAttributeSource().getManagedResource(getClassToExpose(beanClass)) != null); 129 } 130 131 /** 132 * Vote on the inclusion of an attribute accessor. 133 * @param method the accessor method 134 * @param beanKey the key associated with the MBean in the beans map 135 * @return whether the method has the appropriate metadata 136 */ 137 @Override 138 protected boolean includeReadAttribute(Method method, String beanKey) { 139 return hasManagedAttribute(method) || hasManagedMetric(method); 140 } 141 142 /** 143 * Votes on the inclusion of an attribute mutator. 144 * @param method the mutator method 145 * @param beanKey the key associated with the MBean in the beans map 146 * @return whether the method has the appropriate metadata 147 */ 148 @Override 149 protected boolean includeWriteAttribute(Method method, String beanKey) { 150 return hasManagedAttribute(method); 151 } 152 153 /** 154 * Votes on the inclusion of an operation. 155 * @param method the operation method 156 * @param beanKey the key associated with the MBean in the beans map 157 * @return whether the method has the appropriate metadata 158 */ 159 @Override 160 protected boolean includeOperation(Method method, String beanKey) { 161 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); 162 return (pd != null && hasManagedAttribute(method)) || hasManagedOperation(method); 163 } 164 165 /** 166 * Checks to see if the given Method has the {@code ManagedAttribute} attribute. 167 */ 168 private boolean hasManagedAttribute(Method method) { 169 return (obtainAttributeSource().getManagedAttribute(method) != null); 170 } 171 172 /** 173 * Checks to see if the given Method has the {@code ManagedMetric} attribute. 174 */ 175 private boolean hasManagedMetric(Method method) { 176 return (obtainAttributeSource().getManagedMetric(method) != null); 177 } 178 179 /** 180 * Checks to see if the given Method has the {@code ManagedOperation} attribute. 181 * @param method the method to check 182 */ 183 private boolean hasManagedOperation(Method method) { 184 return (obtainAttributeSource().getManagedOperation(method) != null); 185 } 186 187 188 /** 189 * Reads managed resource description from the source level metadata. 190 * Returns an empty {@code String} if no description can be found. 191 */ 192 @Override 193 protected String getDescription(Object managedBean, String beanKey) { 194 ManagedResource mr = obtainAttributeSource().getManagedResource(getClassToExpose(managedBean)); 195 return (mr != null ? mr.getDescription() : ""); 196 } 197 198 /** 199 * Creates a description for the attribute corresponding to this property 200 * descriptor. Attempts to create the description using metadata from either 201 * the getter or setter attributes, otherwise uses the property name. 202 */ 203 @Override 204 protected String getAttributeDescription(PropertyDescriptor propertyDescriptor, String beanKey) { 205 Method readMethod = propertyDescriptor.getReadMethod(); 206 Method writeMethod = propertyDescriptor.getWriteMethod(); 207 208 ManagedAttribute getter = 209 (readMethod != null ? obtainAttributeSource().getManagedAttribute(readMethod) : null); 210 ManagedAttribute setter = 211 (writeMethod != null ? obtainAttributeSource().getManagedAttribute(writeMethod) : null); 212 213 if (getter != null && StringUtils.hasText(getter.getDescription())) { 214 return getter.getDescription(); 215 } 216 else if (setter != null && StringUtils.hasText(setter.getDescription())) { 217 return setter.getDescription(); 218 } 219 220 ManagedMetric metric = (readMethod != null ? obtainAttributeSource().getManagedMetric(readMethod) : null); 221 if (metric != null && StringUtils.hasText(metric.getDescription())) { 222 return metric.getDescription(); 223 } 224 225 return propertyDescriptor.getDisplayName(); 226 } 227 228 /** 229 * Retrieves the description for the supplied {@code Method} from the 230 * metadata. Uses the method name is no description is present in the metadata. 231 */ 232 @Override 233 protected String getOperationDescription(Method method, String beanKey) { 234 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); 235 if (pd != null) { 236 ManagedAttribute ma = obtainAttributeSource().getManagedAttribute(method); 237 if (ma != null && StringUtils.hasText(ma.getDescription())) { 238 return ma.getDescription(); 239 } 240 ManagedMetric metric = obtainAttributeSource().getManagedMetric(method); 241 if (metric != null && StringUtils.hasText(metric.getDescription())) { 242 return metric.getDescription(); 243 } 244 return method.getName(); 245 } 246 else { 247 ManagedOperation mo = obtainAttributeSource().getManagedOperation(method); 248 if (mo != null && StringUtils.hasText(mo.getDescription())) { 249 return mo.getDescription(); 250 } 251 return method.getName(); 252 } 253 } 254 255 /** 256 * Reads {@code MBeanParameterInfo} from the {@code ManagedOperationParameter} 257 * attributes attached to a method. Returns an empty array of {@code MBeanParameterInfo} 258 * if no attributes are found. 259 */ 260 @Override 261 protected MBeanParameterInfo[] getOperationParameters(Method method, String beanKey) { 262 ManagedOperationParameter[] params = obtainAttributeSource().getManagedOperationParameters(method); 263 if (ObjectUtils.isEmpty(params)) { 264 return super.getOperationParameters(method, beanKey); 265 } 266 267 MBeanParameterInfo[] parameterInfo = new MBeanParameterInfo[params.length]; 268 Class<?>[] methodParameters = method.getParameterTypes(); 269 for (int i = 0; i < params.length; i++) { 270 ManagedOperationParameter param = params[i]; 271 parameterInfo[i] = 272 new MBeanParameterInfo(param.getName(), methodParameters[i].getName(), param.getDescription()); 273 } 274 return parameterInfo; 275 } 276 277 /** 278 * Reads the {@link ManagedNotification} metadata from the {@code Class} of the managed resource 279 * and generates and returns the corresponding {@link ModelMBeanNotificationInfo} metadata. 280 */ 281 @Override 282 protected ModelMBeanNotificationInfo[] getNotificationInfo(Object managedBean, String beanKey) { 283 ManagedNotification[] notificationAttributes = 284 obtainAttributeSource().getManagedNotifications(getClassToExpose(managedBean)); 285 ModelMBeanNotificationInfo[] notificationInfos = 286 new ModelMBeanNotificationInfo[notificationAttributes.length]; 287 288 for (int i = 0; i < notificationAttributes.length; i++) { 289 ManagedNotification attribute = notificationAttributes[i]; 290 notificationInfos[i] = JmxMetadataUtils.convertToModelMBeanNotificationInfo(attribute); 291 } 292 293 return notificationInfos; 294 } 295 296 /** 297 * Adds descriptor fields from the {@code ManagedResource} attribute 298 * to the MBean descriptor. Specifically, adds the {@code currencyTimeLimit}, 299 * {@code persistPolicy}, {@code persistPeriod}, {@code persistLocation} 300 * and {@code persistName} descriptor fields if they are present in the metadata. 301 */ 302 @Override 303 protected void populateMBeanDescriptor(Descriptor desc, Object managedBean, String beanKey) { 304 ManagedResource mr = obtainAttributeSource().getManagedResource(getClassToExpose(managedBean)); 305 if (mr == null) { 306 throw new InvalidMetadataException( 307 "No ManagedResource attribute found for class: " + getClassToExpose(managedBean)); 308 } 309 310 applyCurrencyTimeLimit(desc, mr.getCurrencyTimeLimit()); 311 312 if (mr.isLog()) { 313 desc.setField(FIELD_LOG, "true"); 314 } 315 if (StringUtils.hasLength(mr.getLogFile())) { 316 desc.setField(FIELD_LOG_FILE, mr.getLogFile()); 317 } 318 319 if (StringUtils.hasLength(mr.getPersistPolicy())) { 320 desc.setField(FIELD_PERSIST_POLICY, mr.getPersistPolicy()); 321 } 322 if (mr.getPersistPeriod() >= 0) { 323 desc.setField(FIELD_PERSIST_PERIOD, Integer.toString(mr.getPersistPeriod())); 324 } 325 if (StringUtils.hasLength(mr.getPersistName())) { 326 desc.setField(FIELD_PERSIST_NAME, mr.getPersistName()); 327 } 328 if (StringUtils.hasLength(mr.getPersistLocation())) { 329 desc.setField(FIELD_PERSIST_LOCATION, mr.getPersistLocation()); 330 } 331 } 332 333 /** 334 * Adds descriptor fields from the {@code ManagedAttribute} attribute or the {@code ManagedMetric} attribute 335 * to the attribute descriptor. 336 */ 337 @Override 338 protected void populateAttributeDescriptor( 339 Descriptor desc, @Nullable Method getter, @Nullable Method setter, String beanKey) { 340 341 if (getter != null) { 342 ManagedMetric metric = obtainAttributeSource().getManagedMetric(getter); 343 if (metric != null) { 344 populateMetricDescriptor(desc, metric); 345 return; 346 } 347 } 348 349 ManagedAttribute gma = (getter != null ? obtainAttributeSource().getManagedAttribute(getter) : null); 350 ManagedAttribute sma = (setter != null ? obtainAttributeSource().getManagedAttribute(setter) : null); 351 populateAttributeDescriptor(desc, 352 (gma != null ? gma : ManagedAttribute.EMPTY), 353 (sma != null ? sma : ManagedAttribute.EMPTY)); 354 } 355 356 private void populateAttributeDescriptor(Descriptor desc, ManagedAttribute gma, ManagedAttribute sma) { 357 applyCurrencyTimeLimit(desc, resolveIntDescriptor(gma.getCurrencyTimeLimit(), sma.getCurrencyTimeLimit())); 358 359 Object defaultValue = resolveObjectDescriptor(gma.getDefaultValue(), sma.getDefaultValue()); 360 desc.setField(FIELD_DEFAULT, defaultValue); 361 362 String persistPolicy = resolveStringDescriptor(gma.getPersistPolicy(), sma.getPersistPolicy()); 363 if (StringUtils.hasLength(persistPolicy)) { 364 desc.setField(FIELD_PERSIST_POLICY, persistPolicy); 365 } 366 int persistPeriod = resolveIntDescriptor(gma.getPersistPeriod(), sma.getPersistPeriod()); 367 if (persistPeriod >= 0) { 368 desc.setField(FIELD_PERSIST_PERIOD, Integer.toString(persistPeriod)); 369 } 370 } 371 372 private void populateMetricDescriptor(Descriptor desc, ManagedMetric metric) { 373 applyCurrencyTimeLimit(desc, metric.getCurrencyTimeLimit()); 374 375 if (StringUtils.hasLength(metric.getPersistPolicy())) { 376 desc.setField(FIELD_PERSIST_POLICY, metric.getPersistPolicy()); 377 } 378 if (metric.getPersistPeriod() >= 0) { 379 desc.setField(FIELD_PERSIST_PERIOD, Integer.toString(metric.getPersistPeriod())); 380 } 381 382 if (StringUtils.hasLength(metric.getDisplayName())) { 383 desc.setField(FIELD_DISPLAY_NAME, metric.getDisplayName()); 384 } 385 386 if (StringUtils.hasLength(metric.getUnit())) { 387 desc.setField(FIELD_UNITS, metric.getUnit()); 388 } 389 390 if (StringUtils.hasLength(metric.getCategory())) { 391 desc.setField(FIELD_METRIC_CATEGORY, metric.getCategory()); 392 } 393 394 desc.setField(FIELD_METRIC_TYPE, metric.getMetricType().toString()); 395 } 396 397 /** 398 * Adds descriptor fields from the {@code ManagedAttribute} attribute 399 * to the attribute descriptor. Specifically, adds the {@code currencyTimeLimit} 400 * descriptor field if it is present in the metadata. 401 */ 402 @Override 403 protected void populateOperationDescriptor(Descriptor desc, Method method, String beanKey) { 404 ManagedOperation mo = obtainAttributeSource().getManagedOperation(method); 405 if (mo != null) { 406 applyCurrencyTimeLimit(desc, mo.getCurrencyTimeLimit()); 407 } 408 } 409 410 /** 411 * Determines which of two {@code int} values should be used as the value 412 * for an attribute descriptor. In general, only the getter or the setter will 413 * be have a non-negative value so we use that value. In the event that both values 414 * are non-negative, we use the greater of the two. This method can be used to 415 * resolve any {@code int} valued descriptor where there are two possible values. 416 * @param getter the int value associated with the getter for this attribute 417 * @param setter the int associated with the setter for this attribute 418 */ 419 private int resolveIntDescriptor(int getter, int setter) { 420 return (getter >= setter ? getter : setter); 421 } 422 423 /** 424 * Locates the value of a descriptor based on values attached 425 * to both the getter and setter methods. If both have values 426 * supplied then the value attached to the getter is preferred. 427 * @param getter the Object value associated with the get method 428 * @param setter the Object value associated with the set method 429 * @return the appropriate Object to use as the value for the descriptor 430 */ 431 @Nullable 432 private Object resolveObjectDescriptor(@Nullable Object getter, @Nullable Object setter) { 433 return (getter != null ? getter : setter); 434 } 435 436 /** 437 * Locates the value of a descriptor based on values attached 438 * to both the getter and setter methods. If both have values 439 * supplied then the value attached to the getter is preferred. 440 * The supplied default value is used to check to see if the value 441 * associated with the getter has changed from the default. 442 * @param getter the String value associated with the get method 443 * @param setter the String value associated with the set method 444 * @return the appropriate String to use as the value for the descriptor 445 */ 446 @Nullable 447 private String resolveStringDescriptor(@Nullable String getter, @Nullable String setter) { 448 return (StringUtils.hasLength(getter) ? getter : setter); 449 } 450 451}