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