001: /*
002: * Copyright 2002-2006 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: import java.util.ArrayList;
022: import java.util.List;
023:
024: import javax.management.Descriptor;
025: import javax.management.JMException;
026: import javax.management.MBeanOperationInfo;
027: import javax.management.MBeanParameterInfo;
028: import javax.management.modelmbean.ModelMBeanAttributeInfo;
029: import javax.management.modelmbean.ModelMBeanOperationInfo;
030:
031: import org.springframework.aop.framework.AopProxyUtils;
032: import org.springframework.aop.support.AopUtils;
033: import org.springframework.beans.BeanUtils;
034: import org.springframework.core.JdkVersion;
035: import org.springframework.jmx.support.JmxUtils;
036:
037: /**
038: * Builds on the {@link AbstractMBeanInfoAssembler} superclass to
039: * add a basic algorithm for building metadata based on the
040: * reflective metadata of the MBean class.
041: *
042: * <p>The logic for creating MBean metadata from the reflective metadata
043: * is contained in this class, but this class makes no decisions as to
044: * which methods and properties are to be exposed. Instead it gives
045: * subclasses a chance to 'vote' on each property or method through
046: * the <code>includeXXX</code> methods.
047: *
048: * <p>Subclasses are also given the opportunity to populate attribute
049: * and operation metadata with additional descriptors once the metadata
050: * is assembled through the <code>populateXXXDescriptor</code> methods.
051: *
052: * @author Rob Harrop
053: * @author Juergen Hoeller
054: * @since 1.2
055: * @see #includeOperation
056: * @see #includeReadAttribute
057: * @see #includeWriteAttribute
058: * @see #populateAttributeDescriptor
059: * @see #populateOperationDescriptor
060: */
061: public abstract class AbstractReflectiveMBeanInfoAssembler extends
062: AbstractMBeanInfoAssembler {
063:
064: /**
065: * Identifies a getter method in a JMX {@link Descriptor}.
066: */
067: protected static final String FIELD_GET_METHOD = "getMethod";
068:
069: /**
070: * Identifies a setter method in a JMX {@link Descriptor}.
071: */
072: protected static final String FIELD_SET_METHOD = "setMethod";
073:
074: /**
075: * Constant identifier for the role field in a JMX {@link Descriptor}.
076: */
077: protected static final String FIELD_ROLE = "role";
078:
079: /**
080: * Constant identifier for the getter role field value in a JMX {@link Descriptor}.
081: */
082: protected static final String ROLE_GETTER = "getter";
083:
084: /**
085: * Constant identifier for the setter role field value in a JMX {@link Descriptor}.
086: */
087: protected static final String ROLE_SETTER = "setter";
088:
089: /**
090: * Identifies an operation (method) in a JMX {@link Descriptor}.
091: */
092: protected static final String ROLE_OPERATION = "operation";
093:
094: /**
095: * Constant identifier for the visibility field in a JMX {@link Descriptor}.
096: */
097: protected static final String FIELD_VISIBILITY = "visibility";
098:
099: /**
100: * Lowest visibility, used for operations that correspond to
101: * accessors or mutators for attributes.
102: * @see #FIELD_VISIBILITY
103: */
104: protected static final Integer ATTRIBUTE_OPERATION_VISIBILITY = new Integer(
105: 4);
106:
107: /**
108: * Constant identifier for the class field in a JMX {@link Descriptor}.
109: */
110: protected static final String FIELD_CLASS = "class";
111: /**
112: * Constant identifier for the log field in a JMX {@link Descriptor}.
113: */
114: protected static final String FIELD_LOG = "log";
115:
116: /**
117: * Constant identifier for the logfile field in a JMX {@link Descriptor}.
118: */
119: protected static final String FIELD_LOG_FILE = "logFile";
120:
121: /**
122: * Constant identifier for the currency time limit field in a JMX {@link Descriptor}.
123: */
124: protected static final String FIELD_CURRENCY_TIME_LIMIT = "currencyTimeLimit";
125:
126: /**
127: * Constant identifier for the default field in a JMX {@link Descriptor}.
128: */
129: protected static final String FIELD_DEFAULT = "default";
130:
131: /**
132: * Constant identifier for the persistPolicy field in a JMX {@link Descriptor}.
133: */
134: protected static final String FIELD_PERSIST_POLICY = "persistPolicy";
135:
136: /**
137: * Constant identifier for the persistPeriod field in a JMX {@link Descriptor}.
138: */
139: protected static final String FIELD_PERSIST_PERIOD = "persistPeriod";
140:
141: /**
142: * Constant identifier for the persistLocation field in a JMX {@link Descriptor}.
143: */
144: protected static final String FIELD_PERSIST_LOCATION = "persistLocation";
145:
146: /**
147: * Constant identifier for the persistName field in a JMX {@link Descriptor}.
148: */
149: protected static final String FIELD_PERSIST_NAME = "persistName";
150:
151: /**
152: * Default value for the JMX field "currencyTimeLimit".
153: */
154: private Integer defaultCurrencyTimeLimit;
155:
156: /**
157: * Indicates whether or not strict casing is being used for attributes.
158: */
159: private boolean useStrictCasing = true;
160:
161: private boolean exposeClassDescriptor = false;
162:
163: /**
164: * Set the default for the JMX field "currencyTimeLimit".
165: * The default will usually indicate to never cache attribute values.
166: * <p>Default is none, not explicitly setting that field, as recommended by the
167: * JMX 1.2 specification. This should result in "never cache" behavior, always
168: * reading attribute values freshly (which corresponds to a "currencyTimeLimit"
169: * of <code>-1</code> in JMX 1.2).
170: * <p>However, some JMX implementations (that do not follow the JMX 1.2 spec
171: * in that respect) might require an explicit value to be set here to get
172: * "never cache" behavior: for example, JBoss 3.2.x.
173: * <p>Note that the "currencyTimeLimit" value can also be specified on a
174: * managed attribute or operation. The default value will apply if not
175: * overridden with a "currencyTimeLimit" value <code>>= 0</code> there:
176: * a metadata "currencyTimeLimit" value of <code>-1</code> indicates
177: * to use the default; a value of <code>0</code> indicates to "always cache"
178: * and will be translated to <code>Integer.MAX_VALUE</code>; a positive
179: * value indicates the number of cache seconds.
180: * @see org.springframework.jmx.export.metadata.AbstractJmxAttribute#setCurrencyTimeLimit
181: * @see #applyCurrencyTimeLimit(javax.management.Descriptor, int)
182: */
183: public void setDefaultCurrencyTimeLimit(
184: Integer defaultCurrencyTimeLimit) {
185: this .defaultCurrencyTimeLimit = defaultCurrencyTimeLimit;
186: }
187:
188: /**
189: * Return default value for the JMX field "currencyTimeLimit", if any.
190: */
191: protected Integer getDefaultCurrencyTimeLimit() {
192: return this .defaultCurrencyTimeLimit;
193: }
194:
195: /**
196: * Set whether to use strict casing for attributes. Enabled by default.
197: * <p>When using strict casing, a JavaBean property with a getter such as
198: * <code>getFoo()</code> translates to an attribute called <code>Foo</code>.
199: * With strict casing disabled, <code>getFoo()</code> would translate to just
200: * <code>foo</code>.
201: */
202: public void setUseStrictCasing(boolean useStrictCasing) {
203: this .useStrictCasing = useStrictCasing;
204: }
205:
206: /**
207: * Return whether strict casing for attributes is enabled.
208: */
209: protected boolean isUseStrictCasing() {
210: return useStrictCasing;
211: }
212:
213: /**
214: * Set whether to expose the JMX descriptor field "class" for managed operations.
215: * Default is "false", letting the JMX implementation determine the actual class
216: * through reflection.
217: * <p>Set this property to <code>true</code> for JMX implementations that
218: * require the "class" field to be specified, for example WebLogic's.
219: * In that case, Spring will expose the target class name there, in case of
220: * a plain bean instance or a CGLIB proxy. When encountering a JDK dynamic
221: * proxy, the <b>first</b> interface implemented by the proxy will be specified.
222: * <p><b>WARNING:</b> Review your proxy definitions when exposing a JDK dynamic
223: * proxy through JMX, in particular with this property turned to <code>true</code>:
224: * the specified interface list should start with your management interface in
225: * this case, with all other interfaces following. In general, consider exposing
226: * your target bean directly or a CGLIB proxy for it instead.
227: * @see #getClassForDescriptor(Object)
228: */
229: public void setExposeClassDescriptor(boolean exposeClassDescriptor) {
230: this .exposeClassDescriptor = exposeClassDescriptor;
231: }
232:
233: /**
234: * Return whether to expose the JMX descriptor field "class" for managed operations.
235: */
236: protected boolean isExposeClassDescriptor() {
237: return exposeClassDescriptor;
238: }
239:
240: /**
241: * Iterate through all properties on the MBean class and gives subclasses
242: * the chance to vote on the inclusion of both the accessor and mutator.
243: * If a particular accessor or mutator is voted for inclusion, the appropriate
244: * metadata is assembled and passed to the subclass for descriptor population.
245: * @param managedBean the bean instance (might be an AOP proxy)
246: * @param beanKey the key associated with the MBean in the beans map
247: * of the <code>MBeanExporter</code>
248: * @return the attribute metadata
249: * @throws JMException in case of errors
250: * @see #populateAttributeDescriptor
251: */
252: protected ModelMBeanAttributeInfo[] getAttributeInfo(
253: Object managedBean, String beanKey) throws JMException {
254: PropertyDescriptor[] props = BeanUtils
255: .getPropertyDescriptors(getClassToExpose(managedBean));
256: List infos = new ArrayList();
257:
258: for (int i = 0; i < props.length; i++) {
259: Method getter = props[i].getReadMethod();
260: if (getter != null
261: && getter.getDeclaringClass() == Object.class) {
262: continue;
263: }
264: if (getter != null
265: && !includeReadAttribute(getter, beanKey)) {
266: getter = null;
267: }
268:
269: Method setter = props[i].getWriteMethod();
270: if (setter != null
271: && !includeWriteAttribute(setter, beanKey)) {
272: setter = null;
273: }
274:
275: if (getter != null || setter != null) {
276: // If both getter and setter are null, then this does not need exposing.
277: String attrName = JmxUtils.getAttributeName(props[i],
278: isUseStrictCasing());
279: String description = getAttributeDescription(props[i],
280: beanKey);
281: ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(
282: attrName, description, getter, setter);
283:
284: Descriptor desc = info.getDescriptor();
285: if (getter != null) {
286: desc.setField(FIELD_GET_METHOD, getter.getName());
287: }
288: if (setter != null) {
289: desc.setField(FIELD_SET_METHOD, setter.getName());
290: }
291:
292: populateAttributeDescriptor(desc, getter, setter,
293: beanKey);
294: info.setDescriptor(desc);
295: infos.add(info);
296: }
297: }
298:
299: return (ModelMBeanAttributeInfo[]) infos
300: .toArray(new ModelMBeanAttributeInfo[infos.size()]);
301: }
302:
303: /**
304: * Iterate through all methods on the MBean class and gives subclasses the chance
305: * to vote on their inclusion. If a particular method corresponds to the accessor
306: * or mutator of an attribute that is inclued in the managment interface, then
307: * the corresponding operation is exposed with the "role" descriptor
308: * field set to the appropriate value.
309: * @param managedBean the bean instance (might be an AOP proxy)
310: * @param beanKey the key associated with the MBean in the beans map
311: * of the <code>MBeanExporter</code>
312: * @return the operation metadata
313: * @see #populateOperationDescriptor
314: */
315: protected ModelMBeanOperationInfo[] getOperationInfo(
316: Object managedBean, String beanKey) {
317: Method[] methods = getClassToExpose(managedBean).getMethods();
318: List infos = new ArrayList();
319:
320: for (int i = 0; i < methods.length; i++) {
321: Method method = methods[i];
322: if (JdkVersion.isAtLeastJava15() && method.isSynthetic()) {
323: continue;
324: }
325: if (method.getDeclaringClass().equals(Object.class)) {
326: continue;
327: }
328:
329: ModelMBeanOperationInfo info = null;
330: PropertyDescriptor pd = BeanUtils
331: .findPropertyForMethod(method);
332: if (pd != null) {
333: if ((method.equals(pd.getReadMethod()) && includeReadAttribute(
334: method, beanKey))
335: || (method.equals(pd.getWriteMethod()) && includeWriteAttribute(
336: method, beanKey))) {
337: // Attributes need to have their methods exposed as
338: // operations to the JMX server as well.
339: info = createModelMBeanOperationInfo(method, pd
340: .getName(), beanKey);
341: Descriptor desc = info.getDescriptor();
342: if (method.equals(pd.getReadMethod())) {
343: desc.setField(FIELD_ROLE, ROLE_GETTER);
344: } else {
345: desc.setField(FIELD_ROLE, ROLE_SETTER);
346: }
347: desc.setField(FIELD_VISIBILITY,
348: ATTRIBUTE_OPERATION_VISIBILITY);
349: if (isExposeClassDescriptor()) {
350: desc.setField(FIELD_CLASS,
351: getClassForDescriptor(managedBean)
352: .getName());
353: }
354: info.setDescriptor(desc);
355: }
356: } else if (includeOperation(method, beanKey)) {
357: info = createModelMBeanOperationInfo(method, method
358: .getName(), beanKey);
359: Descriptor desc = info.getDescriptor();
360: desc.setField(FIELD_ROLE, ROLE_OPERATION);
361: if (isExposeClassDescriptor()) {
362: desc.setField(FIELD_CLASS, getClassForDescriptor(
363: managedBean).getName());
364: }
365: populateOperationDescriptor(desc, method, beanKey);
366: info.setDescriptor(desc);
367: }
368:
369: if (info != null) {
370: infos.add(info);
371: }
372: }
373:
374: return (ModelMBeanOperationInfo[]) infos
375: .toArray(new ModelMBeanOperationInfo[infos.size()]);
376: }
377:
378: /**
379: * Creates an instance of <code>ModelMBeanOperationInfo</code> for the
380: * given method. Populates the parameter info for the operation.
381: * @param method the <code>Method</code> to create a <code>ModelMBeanOperationInfo</code> for
382: * @param name the name for the operation info
383: * @param beanKey the key associated with the MBean in the beans map
384: * of the <code>MBeanExporter</code>
385: * @return the <code>ModelMBeanOperationInfo</code>
386: */
387: protected ModelMBeanOperationInfo createModelMBeanOperationInfo(
388: Method method, String name, String beanKey) {
389: MBeanParameterInfo[] params = getOperationParameters(method,
390: beanKey);
391: if (params.length == 0) {
392: return new ModelMBeanOperationInfo(getOperationDescription(
393: method, beanKey), method);
394: } else {
395: return new ModelMBeanOperationInfo(name,
396: getOperationDescription(method, beanKey),
397: getOperationParameters(method, beanKey), method
398: .getReturnType().getName(),
399: MBeanOperationInfo.UNKNOWN);
400: }
401: }
402:
403: /**
404: * Return the class to be used for the JMX descriptor field "class".
405: * Only applied when the "exposeClassDescriptor" property is "true".
406: * <p>Default implementation returns the first implemented interface
407: * for a JDK proxy, and the target class else.
408: * @param managedBean the bean instance (might be an AOP proxy)
409: * @return the class to expose in the descriptor field "class"
410: * @see #setExposeClassDescriptor
411: * @see #getClassToExpose(Class)
412: * @see org.springframework.aop.framework.AopProxyUtils#proxiedUserInterfaces(Object)
413: */
414: protected Class getClassForDescriptor(Object managedBean) {
415: if (AopUtils.isJdkDynamicProxy(managedBean)) {
416: return AopProxyUtils.proxiedUserInterfaces(managedBean)[0];
417: }
418: return getClassToExpose(managedBean);
419: }
420:
421: /**
422: * Allows subclasses to vote on the inclusion of a particular attribute accessor.
423: * @param method the accessor <code>Method</code>
424: * @param beanKey the key associated with the MBean in the beans map
425: * of the <code>MBeanExporter</code>
426: * @return <code>true</code> if the accessor should be included in the management interface,
427: * otherwise <code>false<code>
428: */
429: protected abstract boolean includeReadAttribute(Method method,
430: String beanKey);
431:
432: /**
433: * Allows subclasses to vote on the inclusion of a particular attribute mutator.
434: * @param method the mutator <code>Method</code>.
435: * @param beanKey the key associated with the MBean in the beans map
436: * of the <code>MBeanExporter</code>
437: * @return <code>true</code> if the mutator should be included in the management interface,
438: * otherwise <code>false<code>
439: */
440: protected abstract boolean includeWriteAttribute(Method method,
441: String beanKey);
442:
443: /**
444: * Allows subclasses to vote on the inclusion of a particular operation.
445: * @param method the operation method
446: * @param beanKey the key associated with the MBean in the beans map
447: * of the <code>MBeanExporter</code>
448: * @return whether the operation should be included in the management interface
449: */
450: protected abstract boolean includeOperation(Method method,
451: String beanKey);
452:
453: /**
454: * Get the description for a particular attribute.
455: * <p>Default implementation returns a description for the operation
456: * that is the name of corresponding <code>Method</code>.
457: * @param propertyDescriptor the PropertyDescriptor for the attribute
458: * @param beanKey the key associated with the MBean in the beans map
459: * of the <code>MBeanExporter</code>
460: * @return the description for the attribute
461: */
462: protected String getAttributeDescription(
463: PropertyDescriptor propertyDescriptor, String beanKey) {
464: return propertyDescriptor.getDisplayName();
465: }
466:
467: /**
468: * Get the description for a particular operation.
469: * <p>Default implementation returns a description for the operation
470: * that is the name of corresponding <code>Method</code>.
471: * @param method the operation method
472: * @param beanKey the key associated with the MBean in the beans map
473: * of the <code>MBeanExporter</code>
474: * @return the description for the operation
475: */
476: protected String getOperationDescription(Method method,
477: String beanKey) {
478: return method.getName();
479: }
480:
481: /**
482: * Create parameter info for the given method. Default implementation
483: * returns an empty arry of <code>MBeanParameterInfo</code>.
484: * @param method the <code>Method</code> to get the parameter information for
485: * @param beanKey the key associated with the MBean in the beans map
486: * of the <code>MBeanExporter</code>
487: * @return the <code>MBeanParameterInfo</code> array
488: */
489: protected MBeanParameterInfo[] getOperationParameters(
490: Method method, String beanKey) {
491: return new MBeanParameterInfo[0];
492: }
493:
494: /**
495: * Allows subclasses to add extra fields to the <code>Descriptor</code> for an
496: * MBean. Default implementation sets the <code>currencyTimeLimit</code> field to
497: * the specified "defaultCurrencyTimeLimit", if any (by default none).
498: * @param descriptor the <code>Descriptor</code> for the MBean resource.
499: * @param managedBean the bean instance (might be an AOP proxy)
500: * @param beanKey the key associated with the MBean in the beans map
501: * of the <code>MBeanExporter</code>
502: * @see #setDefaultCurrencyTimeLimit(Integer)
503: * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
504: */
505: protected void populateMBeanDescriptor(Descriptor descriptor,
506: Object managedBean, String beanKey) {
507: applyDefaultCurrencyTimeLimit(descriptor);
508: }
509:
510: /**
511: * Allows subclasses to add extra fields to the <code>Descriptor</code> for a particular
512: * attribute. Default implementation sets the <code>currencyTimeLimit</code> field to
513: * the specified "defaultCurrencyTimeLimit", if any (by default none).
514: * @param desc the attribute descriptor
515: * @param getter the accessor method for the attribute
516: * @param setter the mutator method for the attribute
517: * @param beanKey the key associated with the MBean in the beans map
518: * of the <code>MBeanExporter</code>
519: * @see #setDefaultCurrencyTimeLimit(Integer)
520: * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
521: */
522: protected void populateAttributeDescriptor(Descriptor desc,
523: Method getter, Method setter, String beanKey) {
524: applyDefaultCurrencyTimeLimit(desc);
525: }
526:
527: /**
528: * Allows subclasses to add extra fields to the <code>Descriptor</code> for a particular
529: * operation. Default implementation sets the <code>currencyTimeLimit</code> field to
530: * the specified "defaultCurrencyTimeLimit", if any (by default none).
531: * @param desc the operation descriptor
532: * @param method the method corresponding to the operation
533: * @param beanKey the key associated with the MBean in the beans map
534: * of the <code>MBeanExporter</code>
535: * @see #setDefaultCurrencyTimeLimit(Integer)
536: * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
537: */
538: protected void populateOperationDescriptor(Descriptor desc,
539: Method method, String beanKey) {
540: applyDefaultCurrencyTimeLimit(desc);
541: }
542:
543: /**
544: * Set the <code>currencyTimeLimit</code> field to the specified
545: * "defaultCurrencyTimeLimit", if any (by default none).
546: * @param desc the JMX attribute or operation descriptor
547: * @see #setDefaultCurrencyTimeLimit(Integer)
548: */
549: protected final void applyDefaultCurrencyTimeLimit(Descriptor desc) {
550: if (getDefaultCurrencyTimeLimit() != null) {
551: desc.setField(FIELD_CURRENCY_TIME_LIMIT,
552: getDefaultCurrencyTimeLimit().toString());
553: }
554: }
555:
556: /**
557: * Apply the given JMX "currencyTimeLimit" value to the given descriptor.
558: * <p>Default implementation sets a value <code>>0</code> as-is (as number of cache seconds),
559: * turns a value of <code>0</code> into <code>Integer.MAX_VALUE</code> ("always cache")
560: * and sets the "defaultCurrencyTimeLimit" (if any, indicating "never cache") in case of
561: * a value <code><0</code>. This follows the recommendation in the JMX 1.2 specification.
562: * @param desc the JMX attribute or operation descriptor
563: * @param currencyTimeLimit the "currencyTimeLimit" value to apply
564: * @see #setDefaultCurrencyTimeLimit(Integer)
565: * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
566: */
567: protected void applyCurrencyTimeLimit(Descriptor desc,
568: int currencyTimeLimit) {
569: if (currencyTimeLimit > 0) {
570: // number of cache seconds
571: desc.setField(FIELD_CURRENCY_TIME_LIMIT, Integer
572: .toString(currencyTimeLimit));
573: } else if (currencyTimeLimit == 0) {
574: // "always cache"
575: desc.setField(FIELD_CURRENCY_TIME_LIMIT, Integer
576: .toString(Integer.MAX_VALUE));
577: } else {
578: // "never cache"
579: applyDefaultCurrencyTimeLimit(desc);
580: }
581: }
582:
583: }
|