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