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