• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.21
today-context/src/main/java/infra/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.jmx.export.assembler;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.beans.PropertyDescriptor;
23
import java.lang.reflect.Method;
24
import java.util.ArrayList;
25
import java.util.List;
26

27
import javax.management.Descriptor;
28
import javax.management.JMException;
29
import javax.management.MBeanOperationInfo;
30
import javax.management.MBeanParameterInfo;
31
import javax.management.modelmbean.ModelMBeanAttributeInfo;
32
import javax.management.modelmbean.ModelMBeanOperationInfo;
33

34
import infra.aop.framework.AopProxyUtils;
35
import infra.aop.support.AopUtils;
36
import infra.beans.BeanUtils;
37
import infra.core.DefaultParameterNameDiscoverer;
38
import infra.core.ParameterNameDiscoverer;
39
import infra.jmx.export.metadata.AbstractJmxAttribute;
40
import infra.jmx.support.JmxUtils;
41

42
/**
43
 * Builds on the {@link AbstractMBeanInfoAssembler} superclass to
44
 * add a basic algorithm for building metadata based on the
45
 * reflective metadata of the MBean class.
46
 *
47
 * <p>The logic for creating MBean metadata from the reflective metadata
48
 * is contained in this class, but this class makes no decisions as to
49
 * which methods and properties are to be exposed. Instead it gives
50
 * subclasses a chance to 'vote' on each property or method through
51
 * the {@code includeXXX} methods.
52
 *
53
 * <p>Subclasses are also given the opportunity to populate attribute
54
 * and operation metadata with additional descriptors once the metadata
55
 * is assembled through the {@code populateXXXDescriptor} methods.
56
 *
57
 * @author Rob Harrop
58
 * @author Juergen Hoeller
59
 * @author David Boden
60
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
61
 * @see #includeOperation
62
 * @see #includeReadAttribute
63
 * @see #includeWriteAttribute
64
 * @see #populateAttributeDescriptor
65
 * @see #populateOperationDescriptor
66
 * @since 4.0
67
 */
68
public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBeanInfoAssembler {
2✔
69

70
  /**
71
   * Identifies a getter method in a JMX {@link Descriptor}.
72
   */
73
  protected static final String FIELD_GET_METHOD = "getMethod";
74

75
  /**
76
   * Identifies a setter method in a JMX {@link Descriptor}.
77
   */
78
  protected static final String FIELD_SET_METHOD = "setMethod";
79

80
  /**
81
   * Constant identifier for the role field in a JMX {@link Descriptor}.
82
   */
83
  protected static final String FIELD_ROLE = "role";
84

85
  /**
86
   * Constant identifier for the getter role field value in a JMX {@link Descriptor}.
87
   */
88
  protected static final String ROLE_GETTER = "getter";
89

90
  /**
91
   * Constant identifier for the setter role field value in a JMX {@link Descriptor}.
92
   */
93
  protected static final String ROLE_SETTER = "setter";
94

95
  /**
96
   * Identifies an operation (method) in a JMX {@link Descriptor}.
97
   */
98
  protected static final String ROLE_OPERATION = "operation";
99

100
  /**
101
   * Constant identifier for the visibility field in a JMX {@link Descriptor}.
102
   */
103
  protected static final String FIELD_VISIBILITY = "visibility";
104

105
  /**
106
   * Lowest visibility, used for operations that correspond to
107
   * accessors or mutators for attributes.
108
   *
109
   * @see #FIELD_VISIBILITY
110
   */
111
  protected static final int ATTRIBUTE_OPERATION_VISIBILITY = 4;
112

113
  /**
114
   * Constant identifier for the class field in a JMX {@link Descriptor}.
115
   */
116
  protected static final String FIELD_CLASS = "class";
117
  /**
118
   * Constant identifier for the log field in a JMX {@link Descriptor}.
119
   */
120
  protected static final String FIELD_LOG = "log";
121

122
  /**
123
   * Constant identifier for the logfile field in a JMX {@link Descriptor}.
124
   */
125
  protected static final String FIELD_LOG_FILE = "logFile";
126

127
  /**
128
   * Constant identifier for the currency time limit field in a JMX {@link Descriptor}.
129
   */
130
  protected static final String FIELD_CURRENCY_TIME_LIMIT = "currencyTimeLimit";
131

132
  /**
133
   * Constant identifier for the default field in a JMX {@link Descriptor}.
134
   */
135
  protected static final String FIELD_DEFAULT = "default";
136

137
  /**
138
   * Constant identifier for the persistPolicy field in a JMX {@link Descriptor}.
139
   */
140
  protected static final String FIELD_PERSIST_POLICY = "persistPolicy";
141

142
  /**
143
   * Constant identifier for the persistPeriod field in a JMX {@link Descriptor}.
144
   */
145
  protected static final String FIELD_PERSIST_PERIOD = "persistPeriod";
146

147
  /**
148
   * Constant identifier for the persistLocation field in a JMX {@link Descriptor}.
149
   */
150
  protected static final String FIELD_PERSIST_LOCATION = "persistLocation";
151

152
  /**
153
   * Constant identifier for the persistName field in a JMX {@link Descriptor}.
154
   */
155
  protected static final String FIELD_PERSIST_NAME = "persistName";
156

157
  /**
158
   * Constant identifier for the displayName field in a JMX {@link Descriptor}.
159
   */
160
  protected static final String FIELD_DISPLAY_NAME = "displayName";
161

162
  /**
163
   * Constant identifier for the units field in a JMX {@link Descriptor}.
164
   */
165
  protected static final String FIELD_UNITS = "units";
166

167
  /**
168
   * Constant identifier for the metricType field in a JMX {@link Descriptor}.
169
   */
170
  protected static final String FIELD_METRIC_TYPE = "metricType";
171

172
  /**
173
   * Constant identifier for the custom metricCategory field in a JMX {@link Descriptor}.
174
   */
175
  protected static final String FIELD_METRIC_CATEGORY = "metricCategory";
176

177
  /**
178
   * Default value for the JMX field "currencyTimeLimit".
179
   */
180
  @Nullable
181
  private Integer defaultCurrencyTimeLimit;
182

183
  /**
184
   * Indicates whether strict casing is being used for attributes.
185
   */
186
  private boolean useStrictCasing = true;
3✔
187

188
  private boolean exposeClassDescriptor = false;
3✔
189

190
  @Nullable
2✔
191
  private ParameterNameDiscoverer parameterNameDiscoverer = ParameterNameDiscoverer.getSharedInstance();
2✔
192

193
  /**
194
   * Set the default for the JMX field "currencyTimeLimit".
195
   * The default will usually indicate to never cache attribute values.
196
   * <p>Default is none, not explicitly setting that field, as recommended by the
197
   * JMX 1.2 specification. This should result in "never cache" behavior, always
198
   * reading attribute values freshly (which corresponds to a "currencyTimeLimit"
199
   * of {@code -1} in JMX 1.2).
200
   * <p>However, some JMX implementations (that do not follow the JMX 1.2 spec
201
   * in that respect) might require an explicit value to be set here to get
202
   * "never cache" behavior: for example, JBoss 3.2.x.
203
   * <p>Note that the "currencyTimeLimit" value can also be specified on a
204
   * managed attribute or operation. The default value will apply if not
205
   * overridden with a "currencyTimeLimit" value {@code >= 0} there:
206
   * a metadata "currencyTimeLimit" value of {@code -1} indicates
207
   * to use the default; a value of {@code 0} indicates to "always cache"
208
   * and will be translated to {@code Integer.MAX_VALUE}; a positive
209
   * value indicates the number of cache seconds.
210
   *
211
   * @see AbstractJmxAttribute#setCurrencyTimeLimit
212
   * @see #applyCurrencyTimeLimit(Descriptor, int)
213
   */
214
  public void setDefaultCurrencyTimeLimit(@Nullable Integer defaultCurrencyTimeLimit) {
215
    this.defaultCurrencyTimeLimit = defaultCurrencyTimeLimit;
×
216
  }
×
217

218
  /**
219
   * Return default value for the JMX field "currencyTimeLimit", if any.
220
   */
221
  @Nullable
222
  protected Integer getDefaultCurrencyTimeLimit() {
223
    return this.defaultCurrencyTimeLimit;
3✔
224
  }
225

226
  /**
227
   * Set whether to use strict casing for attributes. Enabled by default.
228
   * <p>When using strict casing, a JavaBean property with a getter such as
229
   * {@code getFoo()} translates to an attribute called {@code Foo}.
230
   * With strict casing disabled, {@code getFoo()} would translate to just
231
   * {@code foo}.
232
   */
233
  public void setUseStrictCasing(boolean useStrictCasing) {
234
    this.useStrictCasing = useStrictCasing;
×
235
  }
×
236

237
  /**
238
   * Return whether strict casing for attributes is enabled.
239
   */
240
  protected boolean isUseStrictCasing() {
241
    return this.useStrictCasing;
3✔
242
  }
243

244
  /**
245
   * Set whether to expose the JMX descriptor field "class" for managed operations.
246
   * Default is "false", letting the JMX implementation determine the actual class
247
   * through reflection.
248
   * <p>Set this property to {@code true} for JMX implementations that
249
   * require the "class" field to be specified, for example WebLogic's.
250
   * In that case, Infra will expose the target class name there, in case of
251
   * a plain bean instance or a CGLIB proxy. When encountering a JDK dynamic
252
   * proxy, the <b>first</b> interface implemented by the proxy will be specified.
253
   * <p><b>WARNING:</b> Review your proxy definitions when exposing a JDK dynamic
254
   * proxy through JMX, in particular with this property turned to {@code true}:
255
   * the specified interface list should start with your management interface in
256
   * this case, with all other interfaces following. In general, consider exposing
257
   * your target bean directly or a CGLIB proxy for it instead.
258
   *
259
   * @see #getClassForDescriptor(Object)
260
   */
261
  public void setExposeClassDescriptor(boolean exposeClassDescriptor) {
262
    this.exposeClassDescriptor = exposeClassDescriptor;
×
263
  }
×
264

265
  /**
266
   * Return whether to expose the JMX descriptor field "class" for managed operations.
267
   */
268
  protected boolean isExposeClassDescriptor() {
269
    return this.exposeClassDescriptor;
3✔
270
  }
271

272
  /**
273
   * Set the ParameterNameDiscoverer to use for resolving method parameter
274
   * names if needed (e.g. for parameter names of MBean operation methods).
275
   * <p>Default is a {@link DefaultParameterNameDiscoverer}.
276
   */
277
  public void setParameterNameDiscoverer(@Nullable ParameterNameDiscoverer parameterNameDiscoverer) {
278
    this.parameterNameDiscoverer = parameterNameDiscoverer;
×
279
  }
×
280

281
  /**
282
   * Return the ParameterNameDiscoverer to use for resolving method parameter
283
   * names if needed (may be {@code null} in order to skip parameter detection).
284
   */
285
  @Nullable
286
  protected ParameterNameDiscoverer getParameterNameDiscoverer() {
287
    return this.parameterNameDiscoverer;
3✔
288
  }
289

290
  /**
291
   * Iterate through all properties on the MBean class and gives subclasses
292
   * the chance to vote on the inclusion of both the accessor and mutator.
293
   * If a particular accessor or mutator is voted for inclusion, the appropriate
294
   * metadata is assembled and passed to the subclass for descriptor population.
295
   *
296
   * @param managedBean the bean instance (might be an AOP proxy)
297
   * @param beanKey the key associated with the MBean in the beans map
298
   * of the {@code MBeanExporter}
299
   * @return the attribute metadata
300
   * @throws JMException in case of errors
301
   * @see #populateAttributeDescriptor
302
   */
303
  @Override
304
  protected ModelMBeanAttributeInfo[] getAttributeInfo(Object managedBean, String beanKey) throws JMException {
305
    PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(getClassToExpose(managedBean));
5✔
306
    List<ModelMBeanAttributeInfo> infos = new ArrayList<>();
4✔
307

308
    for (PropertyDescriptor prop : props) {
16✔
309
      Method getter = prop.getReadMethod();
3✔
310
      if (getter != null && getter.getDeclaringClass() == Object.class) {
6✔
311
        continue;
1✔
312
      }
313
      if (getter != null && !includeReadAttribute(getter, beanKey)) {
7✔
314
        getter = null;
2✔
315
      }
316

317
      Method setter = prop.getWriteMethod();
3✔
318
      if (setter != null && !includeWriteAttribute(setter, beanKey)) {
7✔
319
        setter = null;
2✔
320
      }
321

322
      if (getter != null || setter != null) {
4✔
323
        // If both getter and setter are null, then this does not need exposing.
324
        String attrName = JmxUtils.getAttributeName(prop, isUseStrictCasing());
5✔
325
        String description = getAttributeDescription(prop, beanKey);
5✔
326
        ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(attrName, description, getter, setter);
8✔
327

328
        Descriptor desc = info.getDescriptor();
3✔
329
        if (getter != null) {
2✔
330
          desc.setField(FIELD_GET_METHOD, getter.getName());
5✔
331
        }
332
        if (setter != null) {
2✔
333
          desc.setField(FIELD_SET_METHOD, setter.getName());
5✔
334
        }
335

336
        populateAttributeDescriptor(desc, getter, setter, beanKey);
6✔
337
        info.setDescriptor(desc);
3✔
338
        infos.add(info);
4✔
339
      }
340
    }
341

342
    return infos.toArray(new ModelMBeanAttributeInfo[0]);
6✔
343
  }
344

345
  /**
346
   * Iterate through all methods on the MBean class and gives subclasses the chance
347
   * to vote on their inclusion. If a particular method corresponds to the accessor
348
   * or mutator of an attribute that is included in the management interface, then
349
   * the corresponding operation is exposed with the &quot;role&quot; descriptor
350
   * field set to the appropriate value.
351
   *
352
   * @param managedBean the bean instance (might be an AOP proxy)
353
   * @param beanKey the key associated with the MBean in the beans map
354
   * of the {@code MBeanExporter}
355
   * @return the operation metadata
356
   * @see #populateOperationDescriptor
357
   */
358
  @Override
359
  protected ModelMBeanOperationInfo[] getOperationInfo(Object managedBean, String beanKey) {
360
    Method[] methods = getClassToExpose(managedBean).getMethods();
5✔
361
    List<ModelMBeanOperationInfo> infos = new ArrayList<>();
4✔
362

363
    for (Method method : methods) {
16✔
364
      if (method.isSynthetic()) {
3!
365
        continue;
×
366
      }
367
      if (Object.class == method.getDeclaringClass()) {
4✔
368
        continue;
1✔
369
      }
370

371
      ModelMBeanOperationInfo info = null;
2✔
372
      PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method);
3✔
373
      if (pd != null && ((method.equals(pd.getReadMethod()) && includeReadAttribute(method, beanKey)) ||
14✔
374
              (method.equals(pd.getWriteMethod()) && includeWriteAttribute(method, beanKey)))) {
8✔
375
        // Attributes need to have their methods exposed as
376
        // operations to the JMX server as well.
377
        info = createModelMBeanOperationInfo(method, pd.getName(), beanKey);
7✔
378
        Descriptor desc = info.getDescriptor();
3✔
379
        if (method.equals(pd.getReadMethod())) {
5✔
380
          desc.setField(FIELD_ROLE, ROLE_GETTER);
5✔
381
        }
382
        else {
383
          desc.setField(FIELD_ROLE, ROLE_SETTER);
4✔
384
        }
385
        desc.setField(FIELD_VISIBILITY, ATTRIBUTE_OPERATION_VISIBILITY);
5✔
386
        if (isExposeClassDescriptor()) {
3!
387
          desc.setField(FIELD_CLASS, getClassForDescriptor(managedBean).getName());
×
388
        }
389
        info.setDescriptor(desc);
3✔
390
      }
391

392
      // allow getters and setters to be marked as operations directly
393
      if (info == null && includeOperation(method, beanKey)) {
7✔
394
        info = createModelMBeanOperationInfo(method, method.getName(), beanKey);
7✔
395
        Descriptor desc = info.getDescriptor();
3✔
396
        desc.setField(FIELD_ROLE, ROLE_OPERATION);
4✔
397
        if (isExposeClassDescriptor()) {
3!
398
          desc.setField(FIELD_CLASS, getClassForDescriptor(managedBean).getName());
×
399
        }
400
        populateOperationDescriptor(desc, method, beanKey);
5✔
401
        info.setDescriptor(desc);
3✔
402
      }
403

404
      if (info != null) {
2✔
405
        infos.add(info);
4✔
406
      }
407
    }
408

409
    return infos.toArray(new ModelMBeanOperationInfo[0]);
6✔
410
  }
411

412
  /**
413
   * Creates an instance of {@code ModelMBeanOperationInfo} for the
414
   * given method. Populates the parameter info for the operation.
415
   *
416
   * @param method the {@code Method} to create a {@code ModelMBeanOperationInfo} for
417
   * @param name the logical name for the operation (method name or property name);
418
   * not used by the default implementation but possibly by subclasses
419
   * @param beanKey the key associated with the MBean in the beans map
420
   * of the {@code MBeanExporter}
421
   * @return the {@code ModelMBeanOperationInfo}
422
   */
423
  protected ModelMBeanOperationInfo createModelMBeanOperationInfo(Method method, String name, String beanKey) {
424
    MBeanParameterInfo[] params = getOperationParameters(method, beanKey);
5✔
425
    if (params.length == 0) {
3✔
426
      return new ModelMBeanOperationInfo(getOperationDescription(method, beanKey), method);
9✔
427
    }
428
    else {
429
      return new ModelMBeanOperationInfo(method.getName(),
8✔
430
              getOperationDescription(method, beanKey),
4✔
431
              getOperationParameters(method, beanKey),
2✔
432
              method.getReturnType().getName(),
4✔
433
              MBeanOperationInfo.UNKNOWN);
434
    }
435
  }
436

437
  /**
438
   * Return the class to be used for the JMX descriptor field "class".
439
   * Only applied when the "exposeClassDescriptor" property is "true".
440
   * <p>The default implementation returns the first implemented interface
441
   * for a JDK proxy, and the target class else.
442
   *
443
   * @param managedBean the bean instance (might be an AOP proxy)
444
   * @return the class to expose in the descriptor field "class"
445
   * @see #setExposeClassDescriptor
446
   * @see #getClassToExpose(Class)
447
   * @see AopProxyUtils#proxiedUserInterfaces(Object)
448
   */
449
  protected Class<?> getClassForDescriptor(Object managedBean) {
450
    if (AopUtils.isJdkDynamicProxy(managedBean)) {
×
451
      return AopProxyUtils.proxiedUserInterfaces(managedBean)[0];
×
452
    }
453
    return getClassToExpose(managedBean);
×
454
  }
455

456
  /**
457
   * Allows subclasses to vote on the inclusion of a particular attribute accessor.
458
   *
459
   * @param method the accessor {@code Method}
460
   * @param beanKey the key associated with the MBean in the beans map
461
   * of the {@code MBeanExporter}
462
   * @return {@code true} if the accessor should be included in the management interface,
463
   * otherwise {@code false}
464
   */
465
  protected abstract boolean includeReadAttribute(Method method, String beanKey);
466

467
  /**
468
   * Allows subclasses to vote on the inclusion of a particular attribute mutator.
469
   *
470
   * @param method the mutator {@code Method}.
471
   * @param beanKey the key associated with the MBean in the beans map
472
   * of the {@code MBeanExporter}
473
   * @return {@code true} if the mutator should be included in the management interface,
474
   * otherwise {@code false}
475
   */
476
  protected abstract boolean includeWriteAttribute(Method method, String beanKey);
477

478
  /**
479
   * Allows subclasses to vote on the inclusion of a particular operation.
480
   *
481
   * @param method the operation method
482
   * @param beanKey the key associated with the MBean in the beans map
483
   * of the {@code MBeanExporter}
484
   * @return whether the operation should be included in the management interface
485
   */
486
  protected abstract boolean includeOperation(Method method, String beanKey);
487

488
  /**
489
   * Get the description for a particular attribute.
490
   * <p>The default implementation returns a description for the operation
491
   * that is the name of corresponding {@code Method}.
492
   *
493
   * @param propertyDescriptor the PropertyDescriptor for the attribute
494
   * @param beanKey the key associated with the MBean in the beans map
495
   * of the {@code MBeanExporter}
496
   * @return the description for the attribute
497
   */
498
  protected String getAttributeDescription(PropertyDescriptor propertyDescriptor, String beanKey) {
499
    return propertyDescriptor.getDisplayName();
3✔
500
  }
501

502
  /**
503
   * Get the description for a particular operation.
504
   * <p>The default implementation returns a description for the operation
505
   * that is the name of corresponding {@code Method}.
506
   *
507
   * @param method the operation method
508
   * @param beanKey the key associated with the MBean in the beans map
509
   * of the {@code MBeanExporter}
510
   * @return the description for the operation
511
   */
512
  protected String getOperationDescription(Method method, String beanKey) {
513
    return method.getName();
3✔
514
  }
515

516
  /**
517
   * Create parameter info for the given method.
518
   * <p>The default implementation returns an empty array of {@code MBeanParameterInfo}.
519
   *
520
   * @param method the {@code Method} to get the parameter information for
521
   * @param beanKey the key associated with the MBean in the beans map
522
   * of the {@code MBeanExporter}
523
   * @return the {@code MBeanParameterInfo} array
524
   */
525
  protected MBeanParameterInfo[] getOperationParameters(Method method, String beanKey) {
526
    ParameterNameDiscoverer paramNameDiscoverer = getParameterNameDiscoverer();
3✔
527
    @Nullable String[] paramNames = (paramNameDiscoverer != null ? paramNameDiscoverer.getParameterNames(method) : null);
7!
528
    if (paramNames == null) {
2✔
529
      return new MBeanParameterInfo[0];
3✔
530
    }
531

532
    MBeanParameterInfo[] info = new MBeanParameterInfo[paramNames.length];
4✔
533
    Class<?>[] typeParameters = method.getParameterTypes();
3✔
534
    for (int i = 0; i < info.length; i++) {
8✔
535
      info[i] = new MBeanParameterInfo(paramNames[i], typeParameters[i].getName(), paramNames[i]);
16✔
536
    }
537

538
    return info;
2✔
539
  }
540

541
  /**
542
   * Allows subclasses to add extra fields to the {@code Descriptor} for an MBean.
543
   * <p>The default implementation sets the {@code currencyTimeLimit} field to
544
   * the specified "defaultCurrencyTimeLimit", if any (by default none).
545
   *
546
   * @param descriptor the {@code Descriptor} for the MBean resource.
547
   * @param managedBean the bean instance (might be an AOP proxy)
548
   * @param beanKey the key associated with the MBean in the beans map
549
   * of the {@code MBeanExporter}
550
   * @see #setDefaultCurrencyTimeLimit(Integer)
551
   * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
552
   */
553
  @Override
554
  protected void populateMBeanDescriptor(Descriptor descriptor, Object managedBean, String beanKey) {
555
    applyDefaultCurrencyTimeLimit(descriptor);
3✔
556
  }
1✔
557

558
  /**
559
   * Allows subclasses to add extra fields to the {@code Descriptor} for a
560
   * particular attribute.
561
   * <p>The default implementation sets the {@code currencyTimeLimit} field to
562
   * the specified "defaultCurrencyTimeLimit", if any (by default none).
563
   *
564
   * @param desc the attribute descriptor
565
   * @param getter the accessor method for the attribute
566
   * @param setter the mutator method for the attribute
567
   * @param beanKey the key associated with the MBean in the beans map
568
   * of the {@code MBeanExporter}
569
   * @see #setDefaultCurrencyTimeLimit(Integer)
570
   * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
571
   */
572
  protected void populateAttributeDescriptor(
573
          Descriptor desc, @Nullable Method getter, @Nullable Method setter, String beanKey) {
574

575
    applyDefaultCurrencyTimeLimit(desc);
3✔
576
  }
1✔
577

578
  /**
579
   * Allows subclasses to add extra fields to the {@code Descriptor} for a
580
   * particular operation.
581
   * <p>The default implementation sets the {@code currencyTimeLimit} field to
582
   * the specified "defaultCurrencyTimeLimit", if any (by default none).
583
   *
584
   * @param desc the operation descriptor
585
   * @param method the method corresponding to the operation
586
   * @param beanKey the key associated with the MBean in the beans map
587
   * of the {@code MBeanExporter}
588
   * @see #setDefaultCurrencyTimeLimit(Integer)
589
   * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
590
   */
591
  protected void populateOperationDescriptor(Descriptor desc, Method method, String beanKey) {
592
    applyDefaultCurrencyTimeLimit(desc);
3✔
593
  }
1✔
594

595
  /**
596
   * Set the {@code currencyTimeLimit} field to the specified
597
   * "defaultCurrencyTimeLimit", if any (by default none).
598
   *
599
   * @param desc the JMX attribute or operation descriptor
600
   * @see #setDefaultCurrencyTimeLimit(Integer)
601
   */
602
  protected final void applyDefaultCurrencyTimeLimit(Descriptor desc) {
603
    if (getDefaultCurrencyTimeLimit() != null) {
3!
604
      desc.setField(FIELD_CURRENCY_TIME_LIMIT, getDefaultCurrencyTimeLimit().toString());
×
605
    }
606
  }
1✔
607

608
  /**
609
   * Apply the given JMX "currencyTimeLimit" value to the given descriptor.
610
   * <p>The default implementation sets a value {@code >0} as-is (as number of cache seconds),
611
   * turns a value of {@code 0} into {@code Integer.MAX_VALUE} ("always cache")
612
   * and sets the "defaultCurrencyTimeLimit" (if any, indicating "never cache") in case of
613
   * a value {@code <0}. This follows the recommendation in the JMX 1.2 specification.
614
   *
615
   * @param desc the JMX attribute or operation descriptor
616
   * @param currencyTimeLimit the "currencyTimeLimit" value to apply
617
   * @see #setDefaultCurrencyTimeLimit(Integer)
618
   * @see #applyDefaultCurrencyTimeLimit(javax.management.Descriptor)
619
   */
620
  protected void applyCurrencyTimeLimit(Descriptor desc, int currencyTimeLimit) {
621
    if (currencyTimeLimit > 0) {
2✔
622
      // number of cache seconds
623
      desc.setField(FIELD_CURRENCY_TIME_LIMIT, Integer.toString(currencyTimeLimit));
6✔
624
    }
625
    else if (currencyTimeLimit == 0) {
2!
626
      // "always cache"
627
      desc.setField(FIELD_CURRENCY_TIME_LIMIT, Integer.toString(Integer.MAX_VALUE));
×
628
    }
629
    else {
630
      // "never cache"
631
      applyDefaultCurrencyTimeLimit(desc);
3✔
632
    }
633
  }
1✔
634

635
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc