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