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