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