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