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-2016 ForgeRock AS.
016 */
017package org.opends.server.config;
018
019import java.lang.reflect.Array;
020import java.util.ArrayList;
021import java.util.LinkedHashSet;
022import java.util.List;
023
024import javax.management.AttributeList;
025import javax.management.MBeanAttributeInfo;
026import javax.management.MBeanParameterInfo;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.schema.Syntax;
032import org.opends.server.core.DirectoryServer;
033import org.opends.server.types.Attribute;
034import org.opends.server.util.CollectionUtils;
035
036import static org.opends.server.config.ConfigConstants.*;
037import static org.opends.messages.ConfigMessages.*;
038
039/**
040 * This class defines an integer configuration attribute, which can hold zero or
041 * more integer values.  For scalability, the actual values will be stored as
042 * <CODE>long</CODE> elements, although it will be possible to interact with
043 * them as integers in cases where that scalability is not required.
044 */
045@org.opends.server.types.PublicAPI(
046     stability=org.opends.server.types.StabilityLevel.VOLATILE,
047     mayInstantiate=true,
048     mayExtend=false,
049     mayInvoke=true)
050public final class IntegerConfigAttribute
051       extends ConfigAttribute
052{
053  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
054
055  /** The set of active values for this attribute. */
056  private List<Long> activeValues;
057  /** The set of pending values for this attribute. */
058  private List<Long> pendingValues;
059  /** Indicates whether this attribute will impose a lower bound for its values. */
060  private boolean hasLowerBound;
061  /** Indicates whether this attribute will impose an upper bound for its values. */
062  private boolean hasUpperBound;
063  /** The lower bound for values of this attribute. */
064  private long lowerBound;
065  /** The upper bound for values of this attribute. */
066  private long upperBound;
067
068  /**
069   * Creates a new integer configuration attribute stub with the provided
070   * information but no values.  The values will be set using the
071   * <CODE>setInitialValue</CODE> method.
072   *
073   * @param  name                 The name for this configuration attribute.
074   * @param  description          The description for this configuration
075   *                              attribute.
076   * @param  isRequired           Indicates whether this configuration attribute
077   *                              is required to have at least one value.
078   * @param  isMultiValued        Indicates whether this configuration attribute
079   *                              may have multiple values.
080   * @param  requiresAdminAction  Indicates whether changes to this
081   *                              configuration attribute require administrative
082   *                              action before they will take effect.
083   * @param  hasLowerBound        Indicates whether a lower bound will be
084   *                              enforced for values of this attribute.
085   * @param  lowerBound           The lower bound that will be enforced for
086   *                              values of this attribute.
087   * @param  hasUpperBound        Indicates whether an upper bound will be
088   *                              enforced for values of this attribute.
089   * @param  upperBound           The upper bound that will be enforced for
090   *                              values of this attribute.
091   */
092  public IntegerConfigAttribute(String name, LocalizableMessage description,
093                                boolean isRequired, boolean isMultiValued,
094                                boolean requiresAdminAction,
095                                boolean hasLowerBound, long lowerBound,
096                                boolean hasUpperBound, long upperBound)
097  {
098    super(name, description, isRequired, isMultiValued, requiresAdminAction);
099
100    this.hasLowerBound = hasLowerBound;
101    this.lowerBound    = lowerBound;
102    this.hasUpperBound = hasUpperBound;
103    this.upperBound    = upperBound;
104
105    activeValues  = new ArrayList<>();
106    pendingValues = activeValues;
107  }
108
109  /**
110   * Creates a new integer configuration attribute with the provided
111   * information.  No validation will be performed on the provided value.
112   *
113   * @param  name                 The name for this configuration attribute.
114   * @param  description          The description for this configuration
115   *                              attribute.
116   * @param  isRequired           Indicates whether this configuration attribute
117   *                              is required to have at least one value.
118   * @param  isMultiValued        Indicates whether this configuration attribute
119   *                              may have multiple values.
120   * @param  requiresAdminAction  Indicates whether changes to this
121   *                              configuration attribute require administrative
122   *                              action before they will take effect.
123   * @param  hasLowerBound        Indicates whether a lower bound will be
124   *                              enforced for values of this attribute.
125   * @param  lowerBound           The lower bound that will be enforced for
126   *                              values of this attribute.
127   * @param  hasUpperBound        Indicates whether an upper bound will be
128   *                              enforced for values of this attribute.
129   * @param  upperBound           The upper bound that will be enforced for
130   *                              values of this attribute.
131   * @param  value                The value for this integer configuration
132   *                              attribute.
133   */
134  public IntegerConfigAttribute(String name, LocalizableMessage description,
135                                boolean isRequired, boolean isMultiValued,
136                                boolean requiresAdminAction,
137                                boolean hasLowerBound, long lowerBound,
138                                boolean hasUpperBound, long upperBound,
139                                long value)
140  {
141    super(name, description, isRequired, isMultiValued, requiresAdminAction,
142          getLongValueSet(value));
143
144    this.hasLowerBound = hasLowerBound;
145    this.lowerBound    = lowerBound;
146    this.hasUpperBound = hasUpperBound;
147    this.upperBound    = upperBound;
148
149    activeValues = CollectionUtils.newArrayList(value);
150    pendingValues = activeValues;
151  }
152
153  /**
154   * Creates a new integer configuration attribute with the provided
155   * information.  No validation will be performed on the provided values.
156   *
157   * @param  name                 The name for this configuration attribute.
158   * @param  description          The description for this configuration
159   *                              attribute.
160   * @param  isRequired           Indicates whether this configuration attribute
161   *                              is required to have at least one value.
162   * @param  isMultiValued        Indicates whether this configuration attribute
163   *                              may have multiple values.
164   * @param  requiresAdminAction  Indicates whether changes to this
165   *                              configuration attribute require administrative
166   *                              action before they will take effect.
167   * @param  hasLowerBound        Indicates whether a lower bound will be
168   *                              enforced for values of this attribute.
169   * @param  lowerBound           The lower bound that will be enforced for
170   *                              values of this attribute.
171   * @param  hasUpperBound        Indicates whether an upper bound will be
172   *                              enforced for values of this attribute.
173   * @param  upperBound           The upper bound that will be enforced for
174   *                              values of this attribute.
175   * @param  values               The set of values for this configuration
176   *                              attribute.
177   */
178  public IntegerConfigAttribute(String name, LocalizableMessage description,
179                                boolean isRequired, boolean isMultiValued,
180                                boolean requiresAdminAction,
181                                boolean hasLowerBound, long lowerBound,
182                                boolean hasUpperBound, long upperBound,
183                                List<Long> values)
184  {
185    super(name, description, isRequired, isMultiValued, requiresAdminAction,
186          getLongValueSet(values));
187
188    this.hasLowerBound = hasLowerBound;
189    this.lowerBound    = lowerBound;
190    this.hasUpperBound = hasUpperBound;
191    this.upperBound    = upperBound;
192
193    activeValues  = values != null ? values : new ArrayList<Long>();
194    pendingValues = activeValues;
195  }
196
197  /**
198   * Creates a new integer configuration attribute with the provided
199   * information.  No validation will be performed on the provided values.
200   *
201   * @param  name                 The name for this configuration attribute.
202   * @param  description          The description for this configuration
203   *                              attribute.
204   * @param  isRequired           Indicates whether this configuration attribute
205   *                              is required to have at least one value.
206   * @param  isMultiValued        Indicates whether this configuration attribute
207   *                              may have multiple values.
208   * @param  requiresAdminAction  Indicates whether changes to this
209   *                              configuration attribute require administrative
210   *                              action before they will take effect.
211   * @param  hasLowerBound        Indicates whether a lower bound will be
212   *                              enforced for values of this attribute.
213   * @param  lowerBound           The lower bound that will be enforced for
214   *                              values of this attribute.
215   * @param  hasUpperBound        Indicates whether an upper bound will be
216   *                              enforced for values of this attribute.
217   * @param  upperBound           The upper bound that will be enforced for
218   *                              values of this attribute.
219   * @param  activeValues         The set of active values for this
220   *                              configuration attribute.
221   * @param  pendingValues        The set of pending values for this
222   *                              configuration attribute.
223   */
224  public IntegerConfigAttribute(String name, LocalizableMessage description,
225                                boolean isRequired, boolean isMultiValued,
226                                boolean requiresAdminAction,
227                                boolean hasLowerBound, long lowerBound,
228                                boolean hasUpperBound, long upperBound,
229                                List<Long> activeValues,
230                                List<Long> pendingValues)
231  {
232    super(name, description, isRequired, isMultiValued, requiresAdminAction,
233          getLongValueSet(activeValues), (pendingValues != null),
234          getLongValueSet(pendingValues));
235
236    this.hasLowerBound = hasLowerBound;
237    this.lowerBound    = lowerBound;
238    this.hasUpperBound = hasUpperBound;
239    this.upperBound    = upperBound;
240
241    if (activeValues == null)
242    {
243      this.activeValues = new ArrayList<>();
244    }
245    else
246    {
247      this.activeValues = activeValues;
248    }
249
250    if (pendingValues == null)
251    {
252      this.pendingValues = this.activeValues;
253    }
254    else
255    {
256      this.pendingValues = pendingValues;
257    }
258  }
259
260  /**
261   * Retrieves the name of the data type for this configuration attribute.  This
262   * is for informational purposes (e.g., inclusion in method signatures and
263   * other kinds of descriptions) and does not necessarily need to map to an
264   * actual Java type.
265   *
266   * @return  The name of the data type for this configuration attribute.
267   */
268  @Override
269  public String getDataType()
270  {
271    return "Integer";
272  }
273
274  /**
275   * Retrieves the attribute syntax for this configuration attribute.
276   *
277   * @return  The attribute syntax for this configuration attribute.
278   */
279  @Override
280  public Syntax getSyntax()
281  {
282    return DirectoryServer.getDefaultIntegerSyntax();
283  }
284
285  /**
286   * Retrieves the active value for this configuration attribute as a long.
287   * This is only valid for single-valued attributes that have a value.
288   *
289   * @return  The active value for this configuration attribute as a long.
290   *
291   * @throws  ConfigException  If this attribute does not have exactly one
292   *                           active value.
293   */
294  public long activeValue()
295         throws ConfigException
296  {
297    if (activeValues == null || activeValues.isEmpty())
298    {
299      LocalizableMessage message = ERR_CONFIG_ATTR_NO_INT_VALUE.get(getName());
300      throw new ConfigException(message);
301    }
302
303    if (activeValues.size() > 1)
304    {
305      LocalizableMessage message = ERR_CONFIG_ATTR_MULTIPLE_INT_VALUES.get(getName());
306      throw new ConfigException(message);
307    }
308
309    return activeValues.get(0);
310  }
311
312  /**
313   * Retrieves the active value for this configuration attribute as an integer.
314   * This is only valid for single-valued attributes that have a value within
315   * the integer range.
316   *
317   * @return  The active value for this configuration attribute as an integer.
318   *
319   * @throws  ConfigException  If the active value of this attribute cannot be
320   *                           retrieved as an integer, including if there are
321   *                           no values, if there are multiple values, or if
322   *                           the value is not in the range of an integer.
323   */
324  public int activeIntValue()
325         throws ConfigException
326  {
327    if (activeValues == null || activeValues.isEmpty())
328    {
329      LocalizableMessage message = ERR_CONFIG_ATTR_NO_INT_VALUE.get(getName());
330      throw new ConfigException(message);
331    }
332
333    if (activeValues.size() > 1)
334    {
335      LocalizableMessage message = ERR_CONFIG_ATTR_MULTIPLE_INT_VALUES.get(getName());
336      throw new ConfigException(message);
337    }
338
339    long longValue = activeValues.get(0);
340    int  intValue  = (int) longValue;
341    if (intValue == longValue)
342    {
343      return intValue;
344    }
345    else
346    {
347      LocalizableMessage message = ERR_CONFIG_ATTR_VALUE_OUT_OF_INT_RANGE.get(getName());
348      throw new ConfigException(message);
349    }
350  }
351
352  /**
353   * Retrieves the set of active values for this configuration attribute.
354   *
355   * @return  The set of active values for this configuration attribute.
356   */
357  public List<Long> activeValues()
358  {
359    return activeValues;
360  }
361
362  /**
363   * Retrieves the pending value for this configuration attribute as a long.
364   * This is only valid for single-valued attributes that have a value.  If this
365   * attribute does not have any pending values, then the active value will be
366   * returned.
367   *
368   * @return  The pending value for this configuration attribute as a long.
369   *
370   * @throws  ConfigException  If this attribute does not have exactly one
371   *                           pending value.
372   */
373  public long pendingValue()
374         throws ConfigException
375  {
376    if (! hasPendingValues())
377    {
378      return activeValue();
379    }
380
381    if (pendingValues == null || pendingValues.isEmpty())
382    {
383      LocalizableMessage message = ERR_CONFIG_ATTR_NO_INT_VALUE.get(getName());
384      throw new ConfigException(message);
385    }
386
387    if (pendingValues.size() > 1)
388    {
389      LocalizableMessage message = ERR_CONFIG_ATTR_MULTIPLE_INT_VALUES.get(getName());
390      throw new ConfigException(message);
391    }
392
393    return pendingValues.get(0);
394  }
395
396  /**
397   * Retrieves the pending value for this configuration attribute as an integer.
398   * This is only valid for single-valued attributes that have a value within
399   * the integer range.  If this attribute does not have any pending values,
400   * then t he active value will be returned.
401   *
402   * @return  The pending value for this configuration attribute as an integer.
403   *
404   * @throws  ConfigException  If the pending value of this attribute cannot be
405   *                           retrieved as an integer, including if there are
406   *                           no values, if there are multiple values, or if
407   *                           the value is not in the range of an integer.
408   */
409  public int pendingIntValue()
410         throws ConfigException
411  {
412    if (! hasPendingValues())
413    {
414      return activeIntValue();
415    }
416
417    if (pendingValues == null || pendingValues.isEmpty())
418    {
419      LocalizableMessage message = ERR_CONFIG_ATTR_NO_INT_VALUE.get(getName());
420      throw new ConfigException(message);
421    }
422
423    if (pendingValues.size() > 1)
424    {
425      LocalizableMessage message = ERR_CONFIG_ATTR_MULTIPLE_INT_VALUES.get(getName());
426      throw new ConfigException(message);
427    }
428
429    long longValue = pendingValues.get(0);
430    int  intValue  = (int) longValue;
431    if (intValue == longValue)
432    {
433      return intValue;
434    }
435    else
436    {
437      LocalizableMessage message = ERR_CONFIG_ATTR_VALUE_OUT_OF_INT_RANGE.get(getName());
438      throw new ConfigException(message);
439    }
440  }
441
442  /**
443   * Retrieves the set of pending values for this configuration attribute.  If
444   * there are no pending values, then the set of active values will be
445   * returned.
446   *
447   * @return  The set of pending values for this configuration attribute.
448   */
449  public List<Long> pendingValues()
450  {
451    if (! hasPendingValues())
452    {
453      return activeValues;
454    }
455
456    return pendingValues;
457  }
458
459  /**
460   * Indicates whether a lower bound will be enforced for the value of this
461   * configuration attribute.
462   *
463   * @return  <CODE>true</CODE> if a lower bound will be enforced for the
464   *          value of this configuration attribute, or <CODE>false</CODE> if
465   *          not.
466   */
467  public boolean hasLowerBound()
468  {
469    return hasLowerBound;
470  }
471
472  /**
473   * Retrieves the lower bound for the value of this configuration attribute.
474   *
475   * @return  The lower bound for the value of this configuration attribute.
476   */
477  public long getLowerBound()
478  {
479    return lowerBound;
480  }
481
482  /**
483   * Indicates whether an upper bound will be enforced for the calculated value
484   * of this configuration attribute.
485   *
486   * @return  <CODE>true</CODE> if an upper bound will be enforced for the
487   *          calculated value of this configuration attribute, or
488   *          <CODE>false</CODE> if not.
489   */
490  public boolean hasUpperBound()
491  {
492    return hasUpperBound;
493  }
494
495  /**
496   * Retrieves the upper bound for the calculated value of this configuration
497   * attribute.
498   *
499   * @return  The upper bound for the calculated value of this configuration
500   *          attribute.
501   */
502  public long getUpperBound()
503  {
504    return upperBound;
505  }
506
507  /**
508   * Sets the value for this integer configuration attribute.
509   *
510   * @param  value  The value for this integer configuration attribute.
511   *
512   * @throws  ConfigException  If the provided value is not acceptable.
513   */
514  public void setValue(long value)
515         throws ConfigException
516  {
517    if (hasLowerBound && value < lowerBound)
518    {
519      LocalizableMessage message = ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
520          getName(), value, lowerBound);
521      throw new ConfigException(message);
522    }
523
524    if (hasUpperBound && value > upperBound)
525    {
526      LocalizableMessage message = ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
527          getName(), value, upperBound);
528      throw new ConfigException(message);
529    }
530
531    if (requiresAdminAction())
532    {
533      pendingValues = CollectionUtils.newArrayList(value);
534      setPendingValues(getLongValueSet(value));
535    }
536    else
537    {
538      activeValues.clear();
539      activeValues.add(value);
540      pendingValues = activeValues;
541      setActiveValues(getLongValueSet(value));
542    }
543  }
544
545  /**
546   * Sets the values for this integer configuration attribute.
547   *
548   * @param  values  The set of values for this integer configuration attribute.
549   *
550   * @throws  ConfigException  If the provided value set or any of the
551   *                           individual values are not acceptable.
552   */
553  public void setValues(List<Long> values)
554         throws ConfigException
555  {
556    // First check if the set is empty and if that is allowed.
557    if (values == null || values.isEmpty())
558    {
559      if (isRequired())
560      {
561        throw new ConfigException(ERR_CONFIG_ATTR_IS_REQUIRED.get(getName()));
562      }
563
564      if (requiresAdminAction())
565      {
566        setPendingValues(new LinkedHashSet<ByteString>(0));
567        pendingValues = new ArrayList<>();
568      }
569      else
570      {
571        setActiveValues(new LinkedHashSet<ByteString>(0));
572        activeValues.clear();
573      }
574    }
575
576    // Next check if the set contains multiple values and if that is allowed.
577    int numValues = values.size();
578    if (!isMultiValued() && numValues > 1)
579    {
580      LocalizableMessage message =
581          ERR_CONFIG_ATTR_SET_VALUES_IS_SINGLE_VALUED.get(getName());
582      throw new ConfigException(message);
583    }
584
585    // Iterate through all the provided values, make sure that they are
586    // acceptable, and build the value set.
587    LinkedHashSet<ByteString> valueSet = new LinkedHashSet<>(numValues);
588    for (long value : values)
589    {
590      if (hasLowerBound && value < lowerBound)
591      {
592        LocalizableMessage message = ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
593            getName(), value, lowerBound);
594        throw new ConfigException(message);
595      }
596
597      if (hasUpperBound && value > upperBound)
598      {
599        LocalizableMessage message = ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
600            getName(), value, upperBound);
601        throw new ConfigException(message);
602      }
603
604      String valueString = String.valueOf(value);
605      ByteString attrValue = ByteString.valueOfUtf8(valueString);
606      if (valueSet.contains(attrValue))
607      {
608        LocalizableMessage message = ERR_CONFIG_ATTR_ADD_VALUES_ALREADY_EXISTS.get(
609            getName(), valueString);
610        throw new ConfigException(message);
611      }
612
613      valueSet.add(attrValue);
614    }
615
616    // Apply this value set to the new active or pending value set.
617    if (requiresAdminAction())
618    {
619      pendingValues = values;
620      setPendingValues(valueSet);
621    }
622    else
623    {
624      activeValues  = values;
625      pendingValues = activeValues;
626      setActiveValues(valueSet);
627    }
628  }
629
630  /**
631   * Creates the appropriate value set with the provided value.
632   *
633   * @param  value  The value to use to create the value set.
634   *
635   * @return  The constructed value set.
636   */
637  private static LinkedHashSet<ByteString> getLongValueSet(long value)
638  {
639    return getValueSet(String.valueOf(value));
640  }
641
642  /**
643   * Creates the appropriate value set with the provided values.
644   *
645   * @param  values  The values to use to create the value set.
646   *
647   * @return  The constructed value set.
648   */
649  private static LinkedHashSet<ByteString> getLongValueSet(List<Long> values)
650  {
651    if (values == null)
652    {
653      return null;
654    }
655
656    LinkedHashSet<ByteString> valueSet = new LinkedHashSet<>(values.size());
657    for (long value : values)
658    {
659      valueSet.add(ByteString.valueOfUtf8(String.valueOf(value)));
660    }
661    return valueSet;
662  }
663
664  /**
665   * Applies the set of pending values, making them the active values for this
666   * configuration attribute.  This will not take any action if there are no
667   * pending values.
668   */
669  @Override
670  public void applyPendingValues()
671  {
672    if (! hasPendingValues())
673    {
674      return;
675    }
676
677    super.applyPendingValues();
678    activeValues = pendingValues;
679  }
680
681  /**
682   * Indicates whether the provided value is acceptable for use in this
683   * attribute.  If it is not acceptable, then the reason should be written into
684   * the provided buffer.
685   *
686   * @param  value         The value for which to make the determination.
687   * @param  rejectReason  A buffer into which a human-readable reason for the
688   *                       reject may be written.
689   *
690   * @return  <CODE>true</CODE> if the provided value is acceptable for use in
691   *          this attribute, or <CODE>false</CODE> if not.
692   */
693  @Override
694  public boolean valueIsAcceptable(ByteString value, StringBuilder rejectReason)
695  {
696    // First, make sure we can represent it as a long.
697    String stringValue = value.toString();
698    long longValue;
699    try
700    {
701      longValue = Long.parseLong(stringValue);
702    }
703    catch (Exception e)
704    {
705      logger.traceException(e);
706
707      rejectReason.append(ERR_CONFIG_ATTR_INVALID_INT_VALUE.get(
708              getName(), stringValue, e));
709      return false;
710    }
711
712    // Perform any necessary bounds checking.
713    if (hasLowerBound && longValue < lowerBound)
714    {
715      rejectReason.append(ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
716              getName(), longValue, lowerBound));
717      return false;
718    }
719
720    if (hasUpperBound && longValue > upperBound)
721    {
722      rejectReason.append(ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
723              getName(), longValue, upperBound));
724      return false;
725    }
726
727    // If we've gotten here, then the value must be acceptable.
728    return true;
729  }
730
731  /**
732   * Converts the provided set of strings to a corresponding set of attribute
733   * values.
734   *
735   * @param  valueStrings   The set of strings to be converted into attribute
736   *                        values.
737   * @param  allowFailures  Indicates whether the decoding process should allow
738   *                        any failures in which one or more values could be
739   *                        decoded but at least one could not.  If this is
740   *                        <CODE>true</CODE> and such a condition is acceptable
741   *                        for the underlying attribute type, then the returned
742   *                        set of values should simply not include those
743   *                        undecodable values.
744   *
745   * @return  The set of attribute values converted from the provided strings.
746   *
747   * @throws  ConfigException  If an unrecoverable problem occurs while
748   *                           performing the conversion.
749   */
750  @Override
751  public LinkedHashSet<ByteString>
752              stringsToValues(List<String> valueStrings, boolean allowFailures)
753         throws ConfigException
754  {
755    if (valueStrings == null || valueStrings.isEmpty())
756    {
757      if (isRequired())
758      {
759        throw new ConfigException(ERR_CONFIG_ATTR_IS_REQUIRED.get(getName()));
760      }
761      return new LinkedHashSet<>();
762    }
763
764    int numValues = valueStrings.size();
765    if (!isMultiValued() && numValues > 1)
766    {
767      throw new ConfigException(ERR_CONFIG_ATTR_SET_VALUES_IS_SINGLE_VALUED.get(getName()));
768    }
769
770    LinkedHashSet<ByteString> valueSet = new LinkedHashSet<>(numValues);
771    for (String valueString : valueStrings)
772    {
773      long longValue;
774      try
775      {
776        longValue = Long.parseLong(valueString);
777      }
778      catch (Exception e)
779      {
780        logger.traceException(e);
781
782        reportError(allowFailures, ERR_CONFIG_ATTR_INT_COULD_NOT_PARSE.get(valueString, getName(), e));
783        continue;
784      }
785
786      if (hasLowerBound && longValue < lowerBound)
787      {
788        reportError(allowFailures, ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(getName(), longValue, lowerBound));
789        continue;
790      }
791      if (hasUpperBound && longValue > upperBound)
792      {
793        reportError(allowFailures, ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(getName(), longValue, upperBound));
794        continue;
795      }
796
797      valueSet.add(ByteString.valueOfUtf8(valueString));
798    }
799
800    // If this method was configured to continue on error, then it is possible
801    // that we ended up with an empty list.  Check to see if this is a required
802    // attribute and if so deal with it accordingly.
803    if (isRequired() && valueSet.isEmpty())
804    {
805      throw new ConfigException(ERR_CONFIG_ATTR_IS_REQUIRED.get(getName()));
806    }
807
808    return valueSet;
809  }
810
811  private void reportError(boolean allowFailures, LocalizableMessage message) throws ConfigException
812  {
813    if (!allowFailures)
814    {
815      throw new ConfigException(message);
816    }
817    logger.error(message);
818  }
819
820  /**
821   * Converts the set of active values for this configuration attribute into a
822   * set of strings that may be stored in the configuration or represented over
823   * protocol.  The string representation used by this method should be
824   * compatible with the decoding used by the <CODE>stringsToValues</CODE>
825   * method.
826   *
827   * @return  The string representations of the set of active values for this
828   *          configuration attribute.
829   */
830  @Override
831  public List<String> activeValuesToStrings()
832  {
833    return toListOfString(activeValues);
834  }
835
836  /**
837   * Converts the set of pending values for this configuration attribute into a
838   * set of strings that may be stored in the configuration or represented over
839   * protocol.  The string representation used by this method should be
840   * compatible with the decoding used by the <CODE>stringsToValues</CODE>
841   * method.
842   *
843   * @return  The string representations of the set of pending values for this
844   *          configuration attribute, or <CODE>null</CODE> if there are no
845   *          pending values.
846   */
847  @Override
848  public List<String> pendingValuesToStrings()
849  {
850    if (hasPendingValues())
851    {
852      return toListOfString(pendingValues);
853    }
854    return null;
855  }
856
857  private List<String> toListOfString(List<Long> values)
858  {
859    ArrayList<String> results = new ArrayList<>(values.size());
860    for (long l : values)
861    {
862      results.add(String.valueOf(l));
863    }
864    return results;
865  }
866
867  /**
868   * Retrieves a new configuration attribute of this type that will contain the
869   * values from the provided attribute.
870   *
871   * @param  attributeList  The list of attributes to use to create the config
872   *                        attribute.  The list must contain either one or two
873   *                        elements, with both attributes having the same base
874   *                        name and the only option allowed is ";pending" and
875   *                        only if this attribute is one that requires admin
876   *                        action before a change may take effect.
877   *
878   * @return  The generated configuration attribute.
879   *
880   * @throws  ConfigException  If the provided attribute cannot be treated as a
881   *                           configuration attribute of this type (e.g., if
882   *                           one or more of the values of the provided
883   *                           attribute are not suitable for an attribute of
884   *                           this type, or if this configuration attribute is
885   *                           single-valued and the provided attribute has
886   *                           multiple values).
887   */
888  @Override
889  public ConfigAttribute getConfigAttribute(List<Attribute> attributeList)
890         throws ConfigException
891  {
892    ArrayList<Long> activeValues  = null;
893    ArrayList<Long> pendingValues = null;
894
895    for (Attribute a : attributeList)
896    {
897      if (a.hasOptions())
898      {
899        // This must be the pending value.
900        if (a.hasOption(OPTION_PENDING_VALUES))
901        {
902          if (pendingValues != null)
903          {
904            // We cannot have multiple pending value sets.
905            LocalizableMessage message =
906                ERR_CONFIG_ATTR_MULTIPLE_PENDING_VALUE_SETS.get(a.getName());
907            throw new ConfigException(message);
908          }
909
910          if (a.isEmpty())
911          {
912            if (isRequired())
913            {
914              // This is illegal -- it must have a value.
915              LocalizableMessage message = ERR_CONFIG_ATTR_IS_REQUIRED.get(a.getName());
916              throw new ConfigException(message);
917            }
918            else
919            {
920              // This is fine.  The pending value set can be empty.
921              pendingValues = new ArrayList<>(0);
922            }
923          }
924          else
925          {
926            int numValues = a.size();
927            if (numValues > 1 && !isMultiValued())
928            {
929              // This is illegal -- the attribute is single-valued.
930              LocalizableMessage message =
931                  ERR_CONFIG_ATTR_SET_VALUES_IS_SINGLE_VALUED.get(a.getName());
932              throw new ConfigException(message);
933            }
934
935            pendingValues = new ArrayList<>(numValues);
936            for (ByteString v : a)
937            {
938              long longValue;
939              try
940              {
941                longValue = Long.parseLong(v.toString());
942              }
943              catch (Exception e)
944              {
945                LocalizableMessage message = ERR_CONFIG_ATTR_INT_COULD_NOT_PARSE.get(
946                    v, a.getName(), e);
947                throw new ConfigException(message, e);
948              }
949
950              // Check the bounds set for this attribute.
951              if (hasLowerBound && longValue < lowerBound)
952              {
953                LocalizableMessage message = ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
954                    a.getName(), longValue, lowerBound);
955                throw new ConfigException(message);
956              }
957
958              if (hasUpperBound && longValue > upperBound)
959              {
960                LocalizableMessage message = ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
961                    a.getName(), longValue, upperBound);
962                throw new ConfigException(message);
963              }
964
965              pendingValues.add(longValue);
966            }
967          }
968        }
969        else
970        {
971          // This is illegal -- only the pending option is allowed for
972          // configuration attributes.
973          LocalizableMessage message =
974              ERR_CONFIG_ATTR_OPTIONS_NOT_ALLOWED.get(
975                      a.getName());
976          throw new ConfigException(message);
977        }
978      }
979      else
980      {
981        // This must be the active value.
982        if (activeValues!= null)
983        {
984          // We cannot have multiple active value sets.
985          LocalizableMessage message =
986              ERR_CONFIG_ATTR_MULTIPLE_ACTIVE_VALUE_SETS.get(a.getName());
987          throw new ConfigException(message);
988        }
989
990        if (a.isEmpty())
991        {
992          if (isRequired())
993          {
994            // This is illegal -- it must have a value.
995            LocalizableMessage message = ERR_CONFIG_ATTR_IS_REQUIRED.get(a.getName());
996            throw new ConfigException(message);
997          }
998          else
999          {
1000            // This is fine.  The active value set can be empty.
1001            activeValues = new ArrayList<>(0);
1002          }
1003        }
1004        else
1005        {
1006          int numValues = a.size();
1007          if (numValues > 1 && !isMultiValued())
1008          {
1009            // This is illegal -- the attribute is single-valued.
1010            LocalizableMessage message =
1011                ERR_CONFIG_ATTR_SET_VALUES_IS_SINGLE_VALUED.get(a.getName());
1012            throw new ConfigException(message);
1013          }
1014
1015          activeValues = new ArrayList<>(numValues);
1016          for (ByteString v : a)
1017          {
1018            long longValue;
1019            try
1020            {
1021              longValue = Long.parseLong(v.toString());
1022            }
1023            catch (Exception e)
1024            {
1025              LocalizableMessage message = ERR_CONFIG_ATTR_INT_COULD_NOT_PARSE.get(
1026                  v, a.getName(), e);
1027              throw new ConfigException(message, e);
1028            }
1029
1030            // Check the bounds set for this attribute.
1031            if (hasLowerBound && longValue < lowerBound)
1032            {
1033              LocalizableMessage message = ERR_CONFIG_ATTR_INT_BELOW_LOWER_BOUND.get(
1034                  a.getName(), longValue, lowerBound);
1035              throw new ConfigException(message);
1036            }
1037
1038            if (hasUpperBound && longValue > upperBound)
1039            {
1040              LocalizableMessage message = ERR_CONFIG_ATTR_INT_ABOVE_UPPER_BOUND.get(
1041                  a.getName(), longValue, upperBound);
1042              throw new ConfigException(message);
1043            }
1044
1045            activeValues.add(longValue);
1046          }
1047        }
1048      }
1049    }
1050
1051    if (activeValues == null)
1052    {
1053      // This is not OK.  The value set must contain an active value.
1054      LocalizableMessage message = ERR_CONFIG_ATTR_NO_ACTIVE_VALUE_SET.get(getName());
1055      throw new ConfigException(message);
1056    }
1057
1058    if (pendingValues == null)
1059    {
1060      // This is OK.  We'll just use the active value set.
1061      pendingValues = activeValues;
1062    }
1063
1064    return new IntegerConfigAttribute(getName(), getDescription(), isRequired(),
1065                                      isMultiValued(), requiresAdminAction(),
1066                                      hasLowerBound, lowerBound, hasUpperBound,
1067                                      upperBound, activeValues, pendingValues);
1068  }
1069
1070  /**
1071   * Retrieves a JMX attribute containing the value set for this
1072   * configuration attribute (active or pending).
1073   *
1074   * @param pending indicates if pending or active  values are required.
1075   *
1076   * @return  A JMX attribute containing the active value set for this
1077   *          configuration attribute, or <CODE>null</CODE> if it does not have
1078   *          any active values.
1079   */
1080  private javax.management.Attribute _toJMXAttribute(boolean pending)
1081  {
1082    List<Long> requestedValues ;
1083    String name ;
1084    if (pending)
1085    {
1086        requestedValues = pendingValues ;
1087        name = getName() + ";" + OPTION_PENDING_VALUES ;
1088    }
1089    else
1090    {
1091        requestedValues = activeValues ;
1092        name = getName() ;
1093    }
1094
1095    if (isMultiValued())
1096    {
1097      long[] values = new long[requestedValues.size()];
1098      for (int i=0; i < values.length; i++)
1099      {
1100        values[i] = requestedValues.get(i);
1101      }
1102
1103      return new javax.management.Attribute(name, values);
1104    }
1105    else if (requestedValues.isEmpty())
1106    {
1107      return null;
1108    }
1109    else
1110    {
1111      return new javax.management.Attribute(name, requestedValues.get(0));
1112    }
1113  }
1114
1115  /**
1116   * Retrieves a JMX attribute containing the active value set for this
1117   * configuration attribute.
1118   *
1119   * @return  A JMX attribute containing the active value set for this
1120   *          configuration attribute, or <CODE>null</CODE> if it does not have
1121   *          any active values.
1122   */
1123  @Override
1124  public javax.management.Attribute toJMXAttribute()
1125  {
1126    return _toJMXAttribute(false);
1127  }
1128
1129  /**
1130   * Retrieves a JMX attribute containing the pending value set for this
1131   * configuration attribute.
1132   *
1133   * @return  A JMX attribute containing the pending value set for this
1134   *          configuration attribute.
1135   */
1136  @Override
1137  public  javax.management.Attribute toJMXAttributePending()
1138  {
1139      return _toJMXAttribute(true);
1140  }
1141
1142  /**
1143   * Adds information about this configuration attribute to the provided JMX
1144   * attribute list.  If this configuration attribute requires administrative
1145   * action before changes take effect and it has a set of pending values, then
1146   * two attributes should be added to the list -- one for the active value
1147   * and one for the pending value.  The pending value should be named with
1148   * the pending option.
1149   *
1150   * @param  attributeList  The attribute list to which the JMX attribute(s)
1151   *                        should be added.
1152   */
1153  @Override
1154  public void toJMXAttribute(AttributeList attributeList)
1155  {
1156    if (!activeValues.isEmpty())
1157    {
1158      if (isMultiValued())
1159      {
1160        long[] values = new long[activeValues.size()];
1161        for (int i=0; i < values.length; i++)
1162        {
1163          values[i] = activeValues.get(i);
1164        }
1165
1166        attributeList.add(new javax.management.Attribute(getName(), values));
1167      }
1168      else
1169      {
1170        attributeList.add(new javax.management.Attribute(getName(),
1171                                                         activeValues.get(0)));
1172      }
1173    }
1174    else
1175    {
1176      if (isMultiValued())
1177      {
1178        attributeList.add(new javax.management.Attribute(getName(),
1179                                                         new String[0]));
1180      }
1181      else
1182      {
1183        attributeList.add(new javax.management.Attribute(getName(), null));
1184      }
1185    }
1186
1187    if (requiresAdminAction()
1188        && pendingValues != null
1189        && pendingValues != activeValues)
1190    {
1191      String name = getName() + ";" + OPTION_PENDING_VALUES;
1192
1193      if (isMultiValued())
1194      {
1195        long[] values = new long[pendingValues.size()];
1196        for (int i=0; i < values.length; i++)
1197        {
1198          values[i] = pendingValues.get(i);
1199        }
1200
1201        attributeList.add(new javax.management.Attribute(name, values));
1202      }
1203      else if (! pendingValues.isEmpty())
1204      {
1205        attributeList.add(new javax.management.Attribute(name,
1206                                                         pendingValues.get(0)));
1207      }
1208    }
1209  }
1210
1211  /**
1212   * Adds information about this configuration attribute to the provided list in
1213   * the form of a JMX <CODE>MBeanAttributeInfo</CODE> object.  If this
1214   * configuration attribute requires administrative action before changes take
1215   * effect and it has a set of pending values, then two attribute info objects
1216   * should be added to the list -- one for the active value (which should be
1217   * read-write) and one for the pending value (which should be read-only).  The
1218   * pending value should be named with the pending option.
1219   *
1220   * @param  attributeInfoList  The list to which the attribute information
1221   *                            should be added.
1222   */
1223  @Override
1224  public void toJMXAttributeInfo(List<MBeanAttributeInfo> attributeInfoList)
1225  {
1226    if (isMultiValued())
1227    {
1228      attributeInfoList.add(new MBeanAttributeInfo(getName(),
1229                                                   JMX_TYPE_LONG_ARRAY,
1230                                                   String.valueOf(
1231                                                           getDescription()),
1232                                                   true, true, false));
1233    }
1234    else
1235    {
1236      attributeInfoList.add(new MBeanAttributeInfo(getName(),
1237                                                   Long.class.getName(),
1238                                                   String.valueOf(
1239                                                           getDescription()),
1240                                                   true, true, false));
1241    }
1242
1243    if (requiresAdminAction())
1244    {
1245      String name = getName() + ";" + OPTION_PENDING_VALUES;
1246
1247      if (isMultiValued())
1248      {
1249        attributeInfoList.add(new MBeanAttributeInfo(name, JMX_TYPE_LONG_ARRAY,
1250                                                     String.valueOf(
1251                                                             getDescription()),
1252                                                     true, false, false));
1253      }
1254      else
1255      {
1256        attributeInfoList.add(new MBeanAttributeInfo(name, Long.class.getName(),
1257                                                     String.valueOf(
1258                                                             getDescription()),
1259                                                     true, false, false));
1260      }
1261    }
1262  }
1263
1264  /**
1265   * Retrieves a JMX <CODE>MBeanParameterInfo</CODE> object that describes this
1266   * configuration attribute.
1267   *
1268   * @return  A JMX <CODE>MBeanParameterInfo</CODE> object that describes this
1269   *          configuration attribute.
1270   */
1271  @Override
1272  public MBeanParameterInfo toJMXParameterInfo()
1273  {
1274    if (isMultiValued())
1275    {
1276      return new MBeanParameterInfo(getName(), JMX_TYPE_LONG_ARRAY,
1277                                    String.valueOf(getDescription()));
1278    }
1279    else
1280    {
1281      return new MBeanParameterInfo(getName(), Long.TYPE.getName(),
1282                                    String.valueOf(getDescription()));
1283    }
1284  }
1285
1286  /**
1287   * Attempts to set the value of this configuration attribute based on the
1288   * information in the provided JMX attribute.
1289   *
1290   * @param  jmxAttribute  The JMX attribute to use to attempt to set the value
1291   *                       of this configuration attribute.
1292   *
1293   * @throws  ConfigException  If the provided JMX attribute does not have an
1294   *                           acceptable value for this configuration
1295   *                           attribute.
1296   */
1297  @Override
1298  public void setValue(javax.management.Attribute jmxAttribute)
1299         throws ConfigException
1300  {
1301    Object value = jmxAttribute.getValue();
1302    if (value instanceof Long)
1303    {
1304      setValue(((Long) value).longValue());
1305    }
1306    else if (value instanceof Integer)
1307    {
1308      setValue(((Integer) value).intValue());
1309    }
1310    else if (value instanceof String)
1311    {
1312      try
1313      {
1314        setValue(Long.parseLong((String) value));
1315      }
1316      catch (Exception e)
1317      {
1318        logger.traceException(e);
1319
1320        LocalizableMessage message = ERR_CONFIG_ATTR_INT_COULD_NOT_PARSE.get(value, getName(), e);
1321        throw new ConfigException(message, e);
1322      }
1323    }
1324    else if (value.getClass().isArray())
1325    {
1326      String componentType = value.getClass().getComponentType().getName();
1327      int length = Array.getLength(value);
1328
1329      try
1330      {
1331        if (componentType.equals(Long.class.getName()))
1332        {
1333          ArrayList<Long> values = new ArrayList<>();
1334
1335          for (int i=0; i < length; i++)
1336          {
1337            values.add(Array.getLong(value, i));
1338          }
1339
1340          setValues(values);
1341        }
1342        else if (componentType.equals(Integer.class.getName()))
1343        {
1344          ArrayList<Long> values = new ArrayList<>();
1345
1346          for (int i=0; i < length; i++)
1347          {
1348            values.add((long) Array.getInt(value, i));
1349          }
1350
1351          setValues(values);
1352        }
1353        else if (componentType.equals(String.class.getName()))
1354        {
1355          ArrayList<Long> values = new ArrayList<>();
1356
1357          for (int i=0; i < length; i++)
1358          {
1359            String s = (String) Array.get(value, i);
1360            values.add(Long.parseLong(s));
1361          }
1362
1363          setValues(values);
1364        }
1365        else
1366        {
1367          LocalizableMessage message =
1368              ERR_CONFIG_ATTR_INT_INVALID_ARRAY_TYPE.get(
1369                      jmxAttribute.getName(), componentType);
1370          throw new ConfigException(message);
1371        }
1372      }
1373      catch (ConfigException ce)
1374      {
1375        logger.traceException(ce);
1376
1377        throw ce;
1378      }
1379      catch (Exception e)
1380      {
1381        logger.traceException(e);
1382
1383        LocalizableMessage message = ERR_CONFIG_ATTR_INT_COULD_NOT_PARSE.get(
1384            componentType + "[" + length + "]", getName(), e);
1385        throw new ConfigException(message, e);
1386      }
1387    }
1388    else
1389    {
1390      throw new ConfigException(ERR_CONFIG_ATTR_INT_INVALID_TYPE.get(
1391          value, getName(), value.getClass().getName()));
1392    }
1393  }
1394
1395  /**
1396   * Creates a duplicate of this configuration attribute.
1397   *
1398   * @return  A duplicate of this configuration attribute.
1399   */
1400  @Override
1401  public ConfigAttribute duplicate()
1402  {
1403    return new IntegerConfigAttribute(getName(), getDescription(), isRequired(),
1404                                      isMultiValued(), requiresAdminAction(),
1405                                      hasLowerBound, lowerBound, hasUpperBound,
1406                                      upperBound, activeValues, pendingValues);
1407  }
1408}