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