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