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