001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2015 ForgeRock AS.
016 */
017package org.opends.server.config;
018
019import static org.opends.messages.ConfigMessages.*;
020import static org.opends.server.config.ConfigConstants.*;
021import static org.opends.server.util.CollectionUtils.*;
022
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.LinkedHashSet;
026import java.util.List;
027
028import javax.management.AttributeList;
029import javax.management.MBeanAttributeInfo;
030import javax.management.MBeanParameterInfo;
031
032import org.forgerock.i18n.LocalizableMessage;
033import org.forgerock.i18n.slf4j.LocalizedLogger;
034import org.forgerock.opendj.ldap.ByteString;
035import org.forgerock.opendj.ldap.schema.Syntax;
036import org.opends.server.core.DirectoryServer;
037import org.opends.server.types.Attribute;
038
039/**
040 * This class defines a configuration attribute that stores both an integer
041 * value and an associated unit.  The unit will contain both a string and a
042 * floating-point value.  When a unit is selected, then the associated value
043 * will be used as a multiplier for the integer value to achieve the actual
044 * value for this parameter.  For example, the attribute could be used to
045 * specify a size in bytes, but a value with a unit of "kb" could multiply that
046 * value by 1024, or "mb" by 1048576, or "gb" by 1073741824.  In this case, a
047 * value of "50 gb" would be the logical equivalent of "53687091200 b".  Upper
048 * and lower bounds may be imposed, and in that case they will be imposed on
049 * the actual value not on merely the integer portion.  This attribute may only
050 * hold a single value and it will always be required.
051 */
052@org.opends.server.types.PublicAPI(
053     stability=org.opends.server.types.StabilityLevel.VOLATILE,
054     mayInstantiate=true,
055     mayExtend=false,
056     mayInvoke=true)
057public final class IntegerWithUnitConfigAttribute
058       extends ConfigAttribute
059{
060  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
061
062
063
064
065  /**
066   * Indicates whether this configuration attribute should impose a lower bound
067   * for the calculated value.
068   */
069  private boolean hasLowerBound;
070
071  /**
072   * Indicates whether this configuration attribute should impose an upper bound
073   * for the calculated value.
074   */
075  private boolean hasUpperBound;
076
077  /** The set of unit names and associated multipliers. */
078  private HashMap<String,Double> units;
079
080  /** The active calculated value for this attribute. */
081  private long activeCalculatedValue;
082
083  /** The active value for this attribute. */
084  private long activeIntValue;
085
086  /** The lower bound for the calculated value. */
087  private long lowerBound;
088
089  /** The pending calculated value for this attribute. */
090  private long pendingCalculatedValue;
091
092  /** The the pending value for this attribute. */
093  private long pendingIntValue;
094
095  /** The upper bound for the calculated value. */
096  private long upperBound;
097
098  /** The active unit for this attribute. */
099  private String activeUnit;
100
101  /** The pending unit for this attribute. */
102  private String pendingUnit;
103
104
105
106  /**
107   * Creates a new integer with unit configuration attribute stub with the
108   * provided information but no values.  The values will be set using the
109   * <CODE>setInitialValue</CODE> method.  Mo validation will be performed on
110   * the set of allowed units.
111   *
112   * @param  name                 The name for this configuration attribute.
113   * @param  description          The description for this configuration
114   *                              attribute.
115   * @param  requiresAdminAction  Indicates whether changes to this
116   *                              configuration attribute require administrative
117   *                              action before they will take effect.
118   * @param  units                The set of units and their associated
119   *                              multipliers for this configuration attribute.
120   * @param  hasLowerBound        Indicates whether a lower bound will be
121   *                              enforced for the calculated value.
122   * @param  lowerBound           The lower bound for the calculated value.
123   * @param  hasUpperBound        Indicates whether an upper bound will be
124   *                              enforced for the calculated value.
125   * @param  upperBound           The upper bound for the calculated value.
126   */
127  public IntegerWithUnitConfigAttribute(String name, LocalizableMessage description,
128                                        boolean requiresAdminAction,
129                                        HashMap<String,Double> units,
130                                        boolean hasLowerBound, long lowerBound,
131                                        boolean hasUpperBound, long upperBound)
132  {
133    super(name, description, true, false, requiresAdminAction);
134
135
136    this.units         = units;
137    this.hasLowerBound = hasLowerBound;
138    this.lowerBound    = lowerBound;
139    this.hasUpperBound = hasUpperBound;
140    this.upperBound    = upperBound;
141  }
142
143
144
145  /**
146   * Creates a new integer with unit configuration attribute with the provided
147   * information.  No validation will be performed on the provided value or
148   * unit, or on the set of allowed units.
149   *
150   * @param  name                 The name for this configuration attribute.
151   * @param  description          The description for this configuration
152   *                              attribute.
153   * @param  requiresAdminAction  Indicates whether changes to this
154   *                              configuration attribute require administrative
155   *                              action before they will take effect.
156   * @param  units                The set of units and their associated
157   *                              multipliers for this configuration attribute.
158   * @param  hasLowerBound        Indicates whether a lower bound will be
159   *                              enforced for the calculated value.
160   * @param  lowerBound           The lower bound for the calculated value.
161   * @param  hasUpperBound        Indicates whether an upper bound will be
162   *                              enforced for the calculated value.
163   * @param  upperBound           The upper bound for the calculated value.
164   * @param  intValue             The selected value for this configuration
165   *                              attribute.
166   * @param  selectedUnit         The selected unit for this configuration
167   *                              attribute.
168   */
169  public IntegerWithUnitConfigAttribute(String name, LocalizableMessage description,
170                                        boolean requiresAdminAction,
171                                        HashMap<String,Double> units,
172                                        boolean hasLowerBound, long lowerBound,
173                                        boolean hasUpperBound, long upperBound,
174                                        long intValue, String selectedUnit)
175  {
176    super(name, description, true, false, requiresAdminAction,
177          getValueSet(intValue, selectedUnit));
178
179
180
181    this.units          = units;
182    this.hasLowerBound  = hasLowerBound;
183    this.lowerBound     = lowerBound;
184    this.hasUpperBound  = hasUpperBound;
185    this.upperBound     = upperBound;
186    this.activeIntValue = intValue;
187    this.activeUnit     = selectedUnit;
188
189    pendingIntValue = activeIntValue;
190    pendingUnit     = activeUnit;
191
192    if (units.containsKey(selectedUnit))
193    {
194      activeCalculatedValue = (long) (activeIntValue * units.get(selectedUnit));
195    }
196
197    pendingCalculatedValue = activeCalculatedValue;
198  }
199
200
201
202  /**
203   * Creates a new integer with unit configuration attribute with the provided
204   * information.  No validation will be performed on the provided value or
205   * unit, or on the set of allowed units.
206   *
207   * @param  name                 The name for this configuration attribute.
208   * @param  description          The description for this configuration
209   *                              attribute.
210   * @param  requiresAdminAction  Indicates whether changes to this
211   *                              configuration attribute require administrative
212   *                              action before they will take effect.
213   * @param  units                The set of units and their associated
214   *                              multipliers for this configuration attribute.
215   * @param  hasLowerBound        Indicates whether a lower bound will be
216   *                              enforced for the calculated value.
217   * @param  lowerBound           The lower bound for the calculated value.
218   * @param  hasUpperBound        Indicates whether an upper bound will be
219   *                              enforced for the calculated value.
220   * @param  upperBound           The upper bound for the calculated value.
221   * @param  activeIntValue       The active selected value for this
222   *                              configuration attribute.
223   * @param  activeSelectedUnit   The active selected unit for this
224   *                              configuration attribute.
225   * @param  pendingIntValue      The pending selected value for this
226   *                              configuration attribute.
227   * @param  pendingSelectedUnit  The pending selected unit for this
228   *                              configuration attribute.
229   */
230  public IntegerWithUnitConfigAttribute(String name, LocalizableMessage description,
231                                        boolean requiresAdminAction,
232                                        HashMap<String,Double> units,
233                                        boolean hasLowerBound, long lowerBound,
234                                        boolean hasUpperBound, long upperBound,
235                                        long activeIntValue,
236                                        String activeSelectedUnit,
237                                        long pendingIntValue,
238                                        String pendingSelectedUnit)
239  {
240    super(name, description, true, false, requiresAdminAction,
241          getValueSet(activeIntValue, activeSelectedUnit),
242          (pendingSelectedUnit != null),
243          getValueSet(pendingIntValue,pendingSelectedUnit));
244
245
246
247    this.units          = units;
248    this.hasLowerBound  = hasLowerBound;
249    this.lowerBound     = lowerBound;
250    this.hasUpperBound  = hasUpperBound;
251    this.upperBound     = upperBound;
252    this.activeIntValue = activeIntValue;
253    this.activeUnit     = activeSelectedUnit;
254
255    if (pendingSelectedUnit == null)
256    {
257      this.pendingIntValue = activeIntValue;
258      this.pendingUnit     = activeUnit;
259    }
260    else
261    {
262      this.pendingIntValue = pendingIntValue;
263      this.pendingUnit     = pendingSelectedUnit;
264    }
265
266    if (units.containsKey(activeUnit))
267    {
268      activeCalculatedValue = (long) (activeIntValue*units.get(activeUnit));
269    }
270
271
272    if (units.containsKey(pendingUnit))
273    {
274      pendingCalculatedValue = (long) (pendingIntValue*units.get(pendingUnit));
275    }
276  }
277
278
279
280  /**
281   * Retrieves the name of the data type for this configuration attribute.  This
282   * is for informational purposes (e.g., inclusion in method signatures and
283   * other kinds of descriptions) and does not necessarily need to map to an
284   * actual Java type.
285   *
286   * @return  The name of the data type for this configuration attribute.
287   */
288  @Override
289  public String getDataType()
290  {
291    return "IntegerWithUnit";
292  }
293
294
295
296  /**
297   * Retrieves the attribute syntax for this configuration attribute.
298   *
299   * @return  The attribute syntax for this configuration attribute.
300   */
301  @Override
302  public Syntax getSyntax()
303  {
304    return DirectoryServer.getDefaultStringSyntax();
305  }
306
307
308
309  /**
310   * Retrieves the integer component of the active value for this configuration
311   * attribute.
312   *
313   * @return  The integer component of the active value for this configuration
314   *          attribute.
315   */
316  public long activeIntValue()
317  {
318    return activeIntValue;
319  }
320
321
322
323  /**
324   * Retrieves the name of the active unit for this configuration attribute.
325   *
326   * @return  The name of the active unit for this configuration attribute.
327   */
328  public String activeUnit()
329  {
330    return activeUnit;
331  }
332
333
334
335  /**
336   * Retrieves the calculated active value for this configuration attribute.
337   * This will be the product of the active int value and the multiplier for the
338   * associated active unit.
339   *
340   * @return  The calculated active value for this configuration attribute.
341   */
342  public long activeCalculatedValue()
343  {
344    return activeCalculatedValue;
345  }
346
347
348
349  /**
350   * Retrieves the integer component of the pending value for this configuration
351   * attribute.  If there is no pending value, then the integer component of the
352   * active value will be returned.
353   *
354   * @return  The integer component of the pending value for this configuration
355   *          attribute.
356   */
357  public long pendingIntValue()
358  {
359    if (hasPendingValues())
360    {
361      return pendingIntValue;
362    }
363    else
364    {
365      return activeIntValue;
366    }
367  }
368
369
370
371  /**
372   * Retrieves the name of the pending unit for this configuration attribute.
373   * If there is no pending value, then the unit for the active value will be
374   * returned.
375   *
376   * @return  The name of the pending unit for this configuration attribute.
377   */
378  public String pendingUnit()
379  {
380    if (hasPendingValues())
381    {
382      return pendingUnit;
383    }
384    else
385    {
386      return activeUnit;
387    }
388  }
389
390
391
392  /**
393   * Retrieves the calculated pending value for this configuration attribute.
394   * This will be the product of the pending int value and the multiplier for
395   * the associated pending unit.  If there is no pending value, then the
396   * calculated active value will be returned.
397   *
398   * @return  The calculated pending value for this configuration attribute.
399   */
400  public long pendingCalculatedValue()
401  {
402    if (hasPendingValues())
403    {
404      return pendingCalculatedValue;
405    }
406    else
407    {
408      return activeCalculatedValue;
409    }
410  }
411
412
413
414  /**
415   * Retrieves the mapping between the allowed names for the units and their
416   * multipliers for this configuration attribute.
417   *
418   * @return  The mapping between the allowed names for the units and their
419   *          multipliers for this configuration attribute.
420   */
421  public HashMap<String,Double> getUnits()
422  {
423    return units;
424  }
425
426
427
428  /**
429   * Indicates whether a lower bound will be enforced for the calculated value
430   * of this configuration attribute.
431   *
432   * @return  <CODE>true</CODE> if a lower bound will be enforced for the
433   *          calculated value of this configuration attribute, or
434   *          <CODE>false</CODE> if not.
435   */
436  public boolean hasLowerBound()
437  {
438    return hasLowerBound;
439  }
440
441
442
443  /**
444   * Retrieves the lower bound for the calculated value of this configuration
445   * attribute.
446   *
447   * @return  The lower bound for the calculated value of this configuration
448   *          attribute.
449   */
450  public long getLowerBound()
451  {
452    return lowerBound;
453  }
454
455
456
457  /**
458   * Indicates whether an upper bound will be enforced for the calculated value
459   * of this configuration attribute.
460   *
461   * @return  <CODE>true</CODE> if an upper bound will be enforced for the
462   *          calculated value of this configuration attribute, or
463   *          <CODE>false</CODE> if not.
464   */
465  public boolean hasUpperBound()
466  {
467    return hasUpperBound;
468  }
469
470
471
472  /**
473   * Retrieves the upper bound for the calculated value of this configuration
474   * attribute.
475   *
476   * @return  The upper bound for the calculated value of this configuration
477   *          attribute.
478   */
479  public long getUpperBound()
480  {
481    return upperBound;
482  }
483
484
485
486  /**
487   * Sets the value for this configuration attribute.
488   *
489   * @param  intValue  The integer component for the value of this configuration
490   *                   attribute.
491   * @param  unit      The unit for the value of this configuration attribute.
492   *
493   * @throws  ConfigException  If the provided unit is not recognized, or if the
494   *                           resulting calculated value is outside the
495   *                           acceptable bounds.
496   */
497  public void setValue(long intValue, String unit)
498         throws ConfigException
499  {
500    if (unit == null || !units.containsKey(unit))
501    {
502      throw new ConfigException(ERR_CONFIG_ATTR_INVALID_UNIT.get(unit, getName()));
503    }
504
505
506    long calculatedValue = (long) (intValue * units.get(unit));
507    if (hasLowerBound && calculatedValue < lowerBound)
508    {
509      LocalizableMessage message = ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
510          getName(), calculatedValue, lowerBound);
511      throw new ConfigException(message);
512    }
513    if (hasUpperBound && calculatedValue > upperBound)
514    {
515      LocalizableMessage message = ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
516          getName(), calculatedValue, upperBound);
517      throw new ConfigException(message);
518    }
519
520
521    if (requiresAdminAction())
522    {
523      pendingCalculatedValue = calculatedValue;
524      pendingIntValue        = intValue;
525      pendingUnit            = unit;
526      setPendingValues(getValueSet(intValue, unit));
527    }
528    else
529    {
530      activeCalculatedValue = calculatedValue;
531      activeIntValue        = intValue;
532      activeUnit            = unit;
533      setActiveValues(getValueSet(intValue, unit));
534    }
535  }
536
537
538
539  /**
540   * Sets the value for this configuration attribute.
541   *
542   * @param  value  The string representation of the value to use for this
543   *                configuration attribute.
544   *
545   * @throws  ConfigException  If the provided value is invalid for some reason.
546   */
547  public void setValue(String value)
548         throws ConfigException
549  {
550    int spacePos = value.indexOf(' ');
551    if (spacePos <= 0)
552    {
553      throw new ConfigException(ERR_CONFIG_ATTR_NO_UNIT_DELIMITER.get(value, getName()));
554    }
555
556
557    long longValue;
558    try
559    {
560      longValue = Long.parseLong(value.substring(0, spacePos));
561    }
562    catch (Exception e)
563    {
564      logger.traceException(e);
565
566      LocalizableMessage message = ERR_CONFIG_ATTR_COULD_NOT_PARSE_INT_COMPONENT.get(value, getName(), e);
567      throw new ConfigException(message, e);
568    }
569
570    setValue(longValue, value.substring(spacePos+1));
571  }
572
573
574
575  /**
576   * Creates the appropriate value set with the provided value.
577   *
578   * @param  intValue  The integer component for the value to construct.
579   * @param  unit      The unit name for the value to construct.
580   *
581   * @return  The constructed value set.
582   */
583  private static LinkedHashSet<ByteString> getValueSet(long intValue, String unit)
584  {
585    if (unit != null)
586    {
587      return getValueSet(intValue + " " + unit);
588    }
589    return null;
590  }
591
592
593
594  /**
595   * Applies the set of pending values, making them the active values for this
596   * configuration attribute.  This will not take any action if there are no
597   * pending values.
598   */
599  @Override
600  public void applyPendingValues()
601  {
602    if (! hasPendingValues())
603    {
604      return;
605    }
606
607    super.applyPendingValues();
608    activeCalculatedValue = pendingCalculatedValue;
609    activeIntValue        = pendingIntValue;
610    activeUnit            = pendingUnit;
611  }
612
613
614
615  /**
616   * Indicates whether the provided value is acceptable for use in this
617   * attribute.  If it is not acceptable, then the reason should be written into
618   * the provided buffer.
619   *
620   * @param  value         The value for which to make the determination.
621   * @param  rejectReason  A buffer into which a human-readable reason for the
622   *                       reject may be written.
623   *
624   * @return  <CODE>true</CODE> if the provided value is acceptable for use in
625   *          this attribute, or <CODE>false</CODE> if not.
626   */
627  @Override
628  public boolean valueIsAcceptable(ByteString value,
629                                   StringBuilder rejectReason)
630  {
631    // Get a string representation of the value and convert it to lowercase.
632    String lowerValue = value.toString().toLowerCase();
633    return valueIsAcceptable(lowerValue, rejectReason);
634  }
635
636
637
638  /**
639   * Indicates whether the provided value is acceptable for use in this
640   * attribute.  If it is not acceptable, then the reason should be written into
641   * the provided buffer.
642   *
643   * @param  lowerValue    The lowercase version of the value for which to make
644   *                       the determination.
645   * @param  rejectReason  A buffer into which a human-readable reason for the
646   *                       reject may be written.
647   *
648   * @return  <CODE>true</CODE> if the provided value is acceptable for use in
649   *          this attribute, or <CODE>false</CODE> if not.
650   */
651  public boolean valueIsAcceptable(String lowerValue,
652                                   StringBuilder rejectReason)
653  {
654    // Find the first space in the value, since it should separate the integer
655    // from the unit.
656    int spacePos = lowerValue.indexOf(' ');
657    if (spacePos < 0)
658    {
659      rejectReason.append(ERR_CONFIG_ATTR_NO_UNIT_DELIMITER.get(
660              lowerValue, getName()));
661      return false;
662    }
663
664
665    // The part up to the space should be the integer component.
666    long longValue;
667    try
668    {
669      longValue = Long.parseLong(lowerValue.substring(0, spacePos));
670    }
671    catch (Exception e)
672    {
673      logger.traceException(e);
674
675      rejectReason.append(ERR_CONFIG_ATTR_INT_COULD_NOT_PARSE.get(lowerValue, getName(), e));
676      return false;
677    }
678
679
680    // The rest of the value should be the unit.  See if it is in the set of
681    // available units.
682    String unit = lowerValue.substring(spacePos+1);
683    if (!units.containsKey(unit))
684    {
685      rejectReason.append(ERR_CONFIG_ATTR_INVALID_UNIT.get(unit, getName()));
686      return false;
687    }
688
689
690    // Multiply the int value by the unit multiplier and see if that is within
691    // the specified bounds.
692    double multiplier = units.get(unit);
693    long calculatedValue = (long) (longValue * multiplier);
694    if (hasLowerBound && calculatedValue < lowerBound)
695    {
696      rejectReason.append(ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
697              getName(), calculatedValue, lowerBound));
698      return false;
699    }
700    if (hasUpperBound && calculatedValue > upperBound)
701    {
702      rejectReason.append(ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
703              getName(), calculatedValue, upperBound));
704      return false;
705    }
706
707
708    // If we've gotten here, then the value is OK.
709    return true;
710  }
711
712
713
714  /**
715   * Converts the provided set of strings to a corresponding set of attribute
716   * values.
717   *
718   * @param  valueStrings   The set of strings to be converted into attribute
719   *                        values.
720   * @param  allowFailures  Indicates whether the decoding process should allow
721   *                        any failures in which one or more values could be
722   *                        decoded but at least one could not.  If this is
723   *                        <CODE>true</CODE> and such a condition is acceptable
724   *                        for the underlying attribute type, then the returned
725   *                        set of values should simply not include those
726   *                        undecodable values.
727   *
728   * @return  The set of attribute values converted from the provided strings.
729   *
730   * @throws  ConfigException  If an unrecoverable problem occurs while
731   *                           performing the conversion.
732   */
733  @Override
734  public LinkedHashSet<ByteString>
735              stringsToValues(List<String> valueStrings, boolean allowFailures)
736         throws ConfigException
737  {
738    if (valueStrings == null || valueStrings.isEmpty())
739    {
740      if (isRequired())
741      {
742        throw new ConfigException(ERR_CONFIG_ATTR_IS_REQUIRED.get(getName()));
743      }
744      return new LinkedHashSet<>();
745    }
746
747
748    int numValues = valueStrings.size();
749    if (!isMultiValued() && numValues > 1)
750    {
751      throw new ConfigException(ERR_CONFIG_ATTR_SET_VALUES_IS_SINGLE_VALUED.get(getName()));
752    }
753
754
755    LinkedHashSet<ByteString> valueSet = new LinkedHashSet<>(numValues);
756    for (String valueString : valueStrings)
757    {
758      if (valueString == null || valueString.length() == 0)
759      {
760        reportError(allowFailures, ERR_CONFIG_ATTR_EMPTY_STRING_VALUE.get(getName()));
761        continue;
762      }
763
764      StringBuilder rejectReason = new StringBuilder();
765      if (!valueIsAcceptable(valueString.toLowerCase(), rejectReason))
766      {
767        reportError(allowFailures, ERR_CONFIG_ATTR_INVALID_VALUE_WITH_UNIT.get(valueString, getName(), rejectReason));
768        continue;
769      }
770
771      valueSet.add(ByteString.valueOfUtf8(valueString));
772    }
773
774
775    // If this method was configured to continue on error, then it is possible
776    // that we ended up with an empty list.  Check to see if this is a required
777    // attribute and if so deal with it accordingly.
778    if (isRequired() && valueSet.isEmpty())
779    {
780      throw new ConfigException(ERR_CONFIG_ATTR_IS_REQUIRED.get(getName()));
781    }
782
783
784    return valueSet;
785  }
786
787  private void reportError(boolean allowFailures, LocalizableMessage message) throws ConfigException
788  {
789    if (!allowFailures)
790    {
791      throw new ConfigException(message);
792    }
793    logger.error(message);
794  }
795
796  /**
797   * Converts the set of active values for this configuration attribute into a
798   * set of strings that may be stored in the configuration or represented over
799   * protocol.  The string representation used by this method should be
800   * compatible with the decoding used by the <CODE>stringsToValues</CODE>
801   * method.
802   *
803   * @return  The string representations of the set of active values for this
804   *          configuration attribute.
805   */
806  @Override
807  public List<String> activeValuesToStrings()
808  {
809    return newArrayList(activeIntValue + " " + activeUnit);
810  }
811
812
813
814  /**
815   * Converts the set of pending values for this configuration attribute into a
816   * set of strings that may be stored in the configuration or represented over
817   * protocol.  The string representation used by this method should be
818   * compatible with the decoding used by the <CODE>stringsToValues</CODE>
819   * method.
820   *
821   * @return  The string representations of the set of pending values for this
822   *          configuration attribute, or <CODE>null</CODE> if there are no
823   *          pending values.
824   */
825  @Override
826  public List<String> pendingValuesToStrings()
827  {
828    if (hasPendingValues())
829    {
830      return newArrayList(pendingIntValue + " " + pendingUnit);
831    }
832    return null;
833  }
834
835
836
837  /**
838   * Retrieves a new configuration attribute of this type that will contain the
839   * values from the provided attribute.
840   *
841   * @param  attributeList  The list of attributes to use to create the config
842   *                        attribute.  The list must contain either one or two
843   *                        elements, with both attributes having the same base
844   *                        name and the only option allowed is ";pending" and
845   *                        only if this attribute is one that requires admin
846   *                        action before a change may take effect.
847   *
848   * @return  The generated configuration attribute.
849   *
850   * @throws  ConfigException  If the provided attribute cannot be treated as a
851   *                           configuration attribute of this type (e.g., if
852   *                           one or more of the values of the provided
853   *                           attribute are not suitable for an attribute of
854   *                           this type, or if this configuration attribute is
855   *                           single-valued and the provided attribute has
856   *                           multiple values).
857   */
858  @Override
859  public ConfigAttribute getConfigAttribute(List<Attribute> attributeList)
860         throws ConfigException
861  {
862    long   activeIntValue  = 0;
863    long   pendingIntValue = 0;
864    String activeUnit      = null;
865    String pendingUnit     = null;
866
867    for (Attribute a : attributeList)
868    {
869      if (a.hasOptions())
870      {
871        // This must be the pending value.
872        if (!a.hasOption(OPTION_PENDING_VALUES))
873        {
874          // This is illegal -- only the pending option is allowed for configuration attributes.
875          throw new ConfigException(ERR_CONFIG_ATTR_OPTIONS_NOT_ALLOWED.get(a.getName()));
876        }
877        if (pendingUnit != null)
878        {
879          // We cannot have multiple pending value sets.
880          throw new ConfigException(ERR_CONFIG_ATTR_MULTIPLE_PENDING_VALUE_SETS.get(a.getName()));
881        }
882
883        String valueString = getValue(a);
884        try
885        {
886          int spacePos = valueString.indexOf(' ');
887          pendingIntValue = Long.parseLong(valueString.substring(0, spacePos));
888          pendingUnit = valueString.substring(spacePos + 1).trim();
889        }
890        catch (Exception e)
891        {
892          throw new ConfigException(ERR_CONFIG_ATTR_COULD_NOT_PARSE_INT_COMPONENT.get(valueString, a.getName(), e));
893        }
894
895        pendingCalculatedValue = calculateValue(pendingIntValue, activeUnit, pendingUnit, a);
896      }
897      else
898      {
899        // This must be the active value.
900        if (activeUnit != null)
901        {
902          // We cannot have multiple active value sets.
903          throw new ConfigException(ERR_CONFIG_ATTR_MULTIPLE_ACTIVE_VALUE_SETS.get(a.getName()));
904        }
905
906        String valueString = getValue(a);
907        try
908        {
909          int spacePos = valueString.indexOf(' ');
910          activeIntValue = Long.parseLong(valueString.substring(0, spacePos));
911          activeUnit = valueString.substring(spacePos + 1).trim();
912        }
913        catch (Exception e)
914        {
915          throw new ConfigException(ERR_CONFIG_ATTR_COULD_NOT_PARSE_INT_COMPONENT.get(valueString, a.getName(), e));
916        }
917
918        activeCalculatedValue = calculateValue(activeIntValue, activeUnit, activeUnit, a);
919      }
920    }
921
922    if (activeUnit == null)
923    {
924      // This is not OK.  The value set must contain an active value.
925      throw new ConfigException(ERR_CONFIG_ATTR_NO_ACTIVE_VALUE_SET.get(getName()));
926    }
927
928    if (pendingUnit == null)
929    {
930      // This is OK.  We'll just use the active value set.
931      pendingIntValue = activeIntValue;
932      pendingUnit     = activeUnit;
933    }
934
935
936    return new IntegerWithUnitConfigAttribute(getName(), getDescription(),
937                                              requiresAdminAction(), units,
938                                              hasLowerBound, lowerBound,
939                                              hasUpperBound, upperBound,
940                                              activeIntValue, activeUnit,
941                                              pendingIntValue, pendingUnit);
942  }
943
944  private String getValue(Attribute a) throws ConfigException
945  {
946    if (a.isEmpty())
947    {
948      // This is illegal -- it must have a value.
949      throw new ConfigException(ERR_CONFIG_ATTR_IS_REQUIRED.get(a.getName()));
950    }
951
952    Iterator<ByteString> iterator = a.iterator();
953    String valueString = iterator.next().toString();
954    if (iterator.hasNext())
955    {
956      // This is illegal -- the attribute is single-valued.
957      throw new ConfigException(ERR_CONFIG_ATTR_SET_VALUES_IS_SINGLE_VALUED.get(a.getName()));
958    }
959    return valueString;
960  }
961
962  private long calculateValue(long intValue, String activeUnit, String pendingUnit, Attribute a) throws ConfigException
963  {
964    // Get the unit and use it to determine the corresponding multiplier.
965    if (!units.containsKey(pendingUnit))
966    {
967      throw new ConfigException(ERR_CONFIG_ATTR_INVALID_UNIT.get(pendingUnit, a.getName()));
968    }
969
970    double multiplier = units.get(activeUnit);
971    final long result = (long) (multiplier * intValue);
972
973    // Check the bounds set for this attribute.
974    if (hasLowerBound && result < lowerBound)
975    {
976      throw new ConfigException(ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(a.getName(), result, lowerBound));
977    }
978    if (hasUpperBound && result > upperBound)
979    {
980      throw new ConfigException(ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(a.getName(), result, upperBound));
981    }
982    return result;
983  }
984
985  /**
986   * Retrieves a JMX attribute containing the active value set for this
987   * configuration attribute.
988   *
989   * @return  A JMX attribute containing the active value set for this
990   *          configuration attribute, or <CODE>null</CODE> if it does not have
991   *          any active values.
992   */
993  @Override
994  public javax.management.Attribute toJMXAttribute()
995  {
996    return new javax.management.Attribute(getName(),
997                                          activeIntValue + " " + activeUnit);
998  }
999
1000  /**
1001   * Retrieves a JMX attribute containing the pending value set for this
1002   * configuration attribute.
1003   *
1004   * @return  A JMX attribute containing the pending value set for this
1005   *          configuration attribute, or <CODE>null</CODE> if it does not have
1006   *          any active values.
1007   */
1008  @Override
1009  public javax.management.Attribute toJMXAttributePending()
1010    {
1011        return new javax.management.Attribute(getName() + ";"
1012                + OPTION_PENDING_VALUES, pendingIntValue + " " + pendingUnit);
1013    }
1014
1015
1016  /**
1017     * Adds information about this configuration attribute to the provided
1018     * JMX attribute list. If this configuration attribute requires
1019     * administrative action before changes take effect and it has a set of
1020     * pending values, then two attributes should be added to the list --
1021     * one for the active value and one for the pending value. The pending
1022     * value should be named with the pending option.
1023     *
1024     * @param attributeList
1025     *            The attribute list to which the JMX attribute(s) should
1026     *            be added.
1027     */
1028  @Override
1029  public void toJMXAttribute(AttributeList attributeList)
1030  {
1031    String activeValue = activeIntValue + " " + activeUnit;
1032    attributeList.add(new javax.management.Attribute(getName(), activeValue));
1033
1034    if (requiresAdminAction() && pendingCalculatedValue != activeCalculatedValue)
1035    {
1036      String name         = getName() + ";" + OPTION_PENDING_VALUES;
1037      String pendingValue = pendingIntValue + " " + pendingUnit;
1038      attributeList.add(new javax.management.Attribute(name, pendingValue));
1039    }
1040  }
1041
1042
1043
1044  /**
1045   * Adds information about this configuration attribute to the provided list in
1046   * the form of a JMX <CODE>MBeanAttributeInfo</CODE> object.  If this
1047   * configuration attribute requires administrative action before changes take
1048   * effect and it has a set of pending values, then two attribute info objects
1049   * should be added to the list -- one for the active value (which should be
1050   * read-write) and one for the pending value (which should be read-only).  The
1051   * pending value should be named with the pending option.
1052   *
1053   * @param  attributeInfoList  The list to which the attribute information
1054   *                            should be added.
1055   */
1056  @Override
1057  public void toJMXAttributeInfo(List<MBeanAttributeInfo> attributeInfoList)
1058  {
1059    attributeInfoList.add(new MBeanAttributeInfo(getName(),
1060                                                 String.class.getName(),
1061                                                 String.valueOf(
1062                                                         getDescription()),
1063                                                 true, true, false));
1064
1065    if (requiresAdminAction())
1066    {
1067      String name = getName() + ";" + OPTION_PENDING_VALUES;
1068      attributeInfoList.add(new MBeanAttributeInfo(name,
1069                                                   String.class.getName(),
1070                                                   String.valueOf(
1071                                                           getDescription()),
1072                                                   true, false, false));
1073    }
1074  }
1075
1076
1077
1078  /**
1079   * Retrieves a JMX <CODE>MBeanParameterInfo</CODE> object that describes this
1080   * configuration attribute.
1081   *
1082   * @return  A JMX <CODE>MBeanParameterInfo</CODE> object that describes this
1083   *          configuration attribute.
1084   */
1085  @Override
1086  public MBeanParameterInfo toJMXParameterInfo()
1087  {
1088    return new MBeanParameterInfo(getName(), String.class.getName(),
1089                                  String.valueOf(getDescription()));
1090  }
1091
1092
1093
1094  /**
1095   * Attempts to set the value of this configuration attribute based on the
1096   * information in the provided JMX attribute.
1097   *
1098   * @param  jmxAttribute  The JMX attribute to use to attempt to set the value
1099   *                       of this configuration attribute.
1100   *
1101   * @throws  ConfigException  If the provided JMX attribute does not have an
1102   *                           acceptable value for this configuration
1103   *                           attribute.
1104   */
1105  @Override
1106  public void setValue(javax.management.Attribute jmxAttribute)
1107         throws ConfigException
1108  {
1109    Object value = jmxAttribute.getValue();
1110    if (value instanceof String)
1111    {
1112      setValue((String) value);
1113    }
1114    else
1115    {
1116      throw new ConfigException(ERR_CONFIG_ATTR_INT_WITH_UNIT_INVALID_TYPE.get(
1117          value, getName(), value.getClass().getName()));
1118    }
1119  }
1120
1121
1122
1123  /**
1124   * Creates a duplicate of this configuration attribute.
1125   *
1126   * @return  A duplicate of this configuration attribute.
1127   */
1128  @Override
1129  public ConfigAttribute duplicate()
1130  {
1131    return new IntegerWithUnitConfigAttribute(getName(), getDescription(),
1132                                              requiresAdminAction(), units,
1133                                              hasLowerBound, lowerBound,
1134                                              hasUpperBound, upperBound,
1135                                              activeIntValue, activeUnit,
1136                                              pendingIntValue, pendingUnit);
1137  }
1138}