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