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