001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS.
026 */
027
028package org.forgerock.opendj.config;
029
030import org.forgerock.util.Reject;
031
032import java.util.Comparator;
033import java.util.EnumSet;
034import java.util.Locale;
035import java.util.MissingResourceException;
036import java.util.Set;
037
038import org.forgerock.i18n.LocalizableMessage;
039
040/**
041 * An interface for querying generic property definition features.
042 * <p>
043 * Property definitions are analogous to ConfigAttributes in the current model
044 * and will play a similar role. Eventually these will replace them.
045 * <p>
046 * Implementations <b>must</b> take care to implement the various comparison
047 * methods.
048 *
049 * @param <T>
050 *            The data-type of values of the property.
051 */
052public abstract class PropertyDefinition<T> implements Comparator<T>, Comparable<PropertyDefinition<?>> {
053
054    /**
055     * An interface for incrementally constructing property definitions.
056     *
057     * @param <T>
058     *            The data-type of values of the property.
059     * @param <D>
060     *            The type of property definition constructed by this builder.
061     */
062    protected static abstract class AbstractBuilder<T, D extends PropertyDefinition<T>> {
063
064        /** The administrator action. */
065        private AdministratorAction adminAction;
066
067        /** The default behavior provider. */
068        private DefaultBehaviorProvider<T> defaultBehavior;
069
070        /** The abstract managed object. */
071        private final AbstractManagedObjectDefinition<?, ?> definition;
072
073        /** The options applicable to this definition. */
074        private final EnumSet<PropertyOption> options;
075
076        /** The name of this property definition. */
077        private final String propertyName;
078
079        /**
080         * Create a property definition builder.
081         *
082         * @param d
083         *            The managed object definition associated with this
084         *            property definition.
085         * @param propertyName
086         *            The property name.
087         */
088        protected AbstractBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
089            this.definition = d;
090            this.propertyName = propertyName;
091            this.options = EnumSet.noneOf(PropertyOption.class);
092            this.adminAction = new AdministratorAction(AdministratorAction.Type.NONE, d, propertyName);
093            this.defaultBehavior = new UndefinedDefaultBehaviorProvider<>();
094        }
095
096        /**
097         * Construct a property definition based on the properties of this
098         * builder.
099         *
100         * @return The new property definition.
101         */
102        public final D getInstance() {
103            return buildInstance(definition, propertyName, options, adminAction, defaultBehavior);
104        }
105
106        /**
107         * Set the administrator action.
108         *
109         * @param adminAction
110         *            The administrator action.
111         */
112        public final void setAdministratorAction(AdministratorAction adminAction) {
113            Reject.ifNull(adminAction);
114            this.adminAction = adminAction;
115        }
116
117        /**
118         * Set the default behavior provider.
119         *
120         * @param defaultBehavior
121         *            The default behavior provider.
122         */
123        public final void setDefaultBehaviorProvider(DefaultBehaviorProvider<T> defaultBehavior) {
124            Reject.ifNull(defaultBehavior);
125            this.defaultBehavior = defaultBehavior;
126        }
127
128        /**
129         * Add a property definition option.
130         *
131         * @param option
132         *            The property option.
133         */
134        public final void setOption(PropertyOption option) {
135            Reject.ifNull(option);
136            options.add(option);
137        }
138
139        /**
140         * Build a property definition based on the properties of this builder.
141         *
142         * @param d
143         *            The managed object definition associated with this
144         *            property definition.
145         * @param propertyName
146         *            The property name.
147         * @param options
148         *            Options applicable to this definition.
149         * @param adminAction
150         *            The administrator action.
151         * @param defaultBehavior
152         *            The default behavior provider.
153         * @return The new property definition.
154         */
155        protected abstract D buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
156            EnumSet<PropertyOption> options, AdministratorAction adminAction,
157            DefaultBehaviorProvider<T> defaultBehavior);
158    }
159
160    /** The administrator action. */
161    private final AdministratorAction adminAction;
162
163    /** The default behavior provider. */
164    private final DefaultBehaviorProvider<T> defaultBehavior;
165
166    /** The abstract managed object. */
167    private final AbstractManagedObjectDefinition<?, ?> definition;
168
169    /** Options applicable to this definition. */
170    private final Set<PropertyOption> options;
171
172    /** The property name. */
173    private final String propertyName;
174
175    /** The property value class. */
176    private final Class<T> theClass;
177
178    /**
179     * Create a property definition.
180     *
181     * @param d
182     *            The managed object definition associated with this property
183     *            definition.
184     * @param theClass
185     *            The property value class.
186     * @param propertyName
187     *            The property name.
188     * @param options
189     *            Options applicable to this definition.
190     * @param adminAction
191     *            The administrator action.
192     * @param defaultBehavior
193     *            The default behavior provider.
194     */
195    protected PropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, Class<T> theClass, String propertyName,
196        EnumSet<PropertyOption> options, AdministratorAction adminAction, DefaultBehaviorProvider<T> defaultBehavior) {
197        Reject.ifNull(d, theClass, propertyName, options, adminAction, defaultBehavior);
198
199        this.definition = d;
200        this.theClass = theClass;
201        this.propertyName = propertyName;
202        this.options = EnumSet.copyOf(options);
203        this.adminAction = adminAction;
204        this.defaultBehavior = defaultBehavior;
205    }
206
207    /**
208     * Apply a visitor to this property definition.
209     *
210     * @param <R>
211     *            The return type of the visitor's methods.
212     * @param <P>
213     *            The type of the additional parameters to the visitor's
214     *            methods.
215     * @param v
216     *            The property definition visitor.
217     * @param p
218     *            Optional additional visitor parameter.
219     * @return Returns a result as specified by the visitor.
220     */
221    public abstract <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p);
222
223    /**
224     * Apply a visitor to a property value associated with this property
225     * definition.
226     *
227     * @param <R>
228     *            The return type of the visitor's methods.
229     * @param <P>
230     *            The type of the additional parameters to the visitor's
231     *            methods.
232     * @param v
233     *            The property value visitor.
234     * @param value
235     *            The property value.
236     * @param p
237     *            Optional additional visitor parameter.
238     * @return Returns a result as specified by the visitor.
239     */
240    public abstract <R, P> R accept(PropertyValueVisitor<R, P> v, T value, P p);
241
242    /**
243     * Cast the provided value to the type associated with this property
244     * definition.
245     * <p>
246     * This method only casts the object to the required type; it does not
247     * validate the value once it has been cast. Subsequent validation should be
248     * performed using the method {@link #validateValue(Object)}.
249     * <p>
250     * This method guarantees the following expression is always
251     * <code>true</code>:
252     *
253     * <pre>
254     *  PropertyDefinition d;
255     *  x == d.cast(x);
256     * </pre>
257     *
258     * @param object
259     *            The property value to be cast (can be <code>null</code>).
260     * @return Returns the property value cast to the correct type.
261     * @throws ClassCastException
262     *             If the provided property value did not have the correct type.
263     */
264    public final T castValue(Object object) {
265        return theClass.cast(object);
266    }
267
268    /**
269     * Compares two property values for order. Returns a negative integer, zero,
270     * or a positive integer as the first argument is less than, equal to, or
271     * greater than the second.
272     * <p>
273     * This default implementation normalizes both values using
274     * {@link #normalizeValue(Object)} and then performs a case-sensitive string
275     * comparison.
276     *
277     * @param o1
278     *            the first object to be compared.
279     * @param o2
280     *            the second object to be compared.
281     * @return a negative integer, zero, or a positive integer as the first
282     *         argument is less than, equal to, or greater than the second.
283     */
284    public int compare(T o1, T o2) {
285        Reject.ifNull(o1);
286        Reject.ifNull(o2);
287
288        String s1 = normalizeValue(o1);
289        String s2 = normalizeValue(o2);
290
291        return s1.compareTo(s2);
292    }
293
294    /**
295     * Compares this property definition with the specified property definition
296     * for order. Returns a negative integer, zero, or a positive integer if
297     * this property definition is less than, equal to, or greater than the
298     * specified property definition.
299     * <p>
300     * The ordering must be determined first from the property name and then
301     * base on the underlying value type.
302     *
303     * @param o
304     *            The reference property definition with which to compare.
305     * @return Returns a negative integer, zero, or a positive integer if this
306     *         property definition is less than, equal to, or greater than the
307     *         specified property definition.
308     */
309    public final int compareTo(PropertyDefinition<?> o) {
310        int rc = propertyName.compareTo(o.propertyName);
311        if (rc == 0) {
312            rc = theClass.getName().compareTo(o.theClass.getName());
313        }
314        return rc;
315    }
316
317    /**
318     * Parse and validate a string representation of a property value.
319     *
320     * @param value
321     *            The property string value (must not be <code>null</code>).
322     * @return Returns the decoded property value.
323     * @throws PropertyException
324     *             If the property value string is invalid.
325     */
326    public abstract T decodeValue(String value);
327
328    /**
329     * Encode the provided property value into its string representation.
330     * <p>
331     * This default implementation simply returns invokes the
332     * {@link Object#toString()} method on the provided value.
333     *
334     * @param value
335     *            The property value (must not be <code>null</code>).
336     * @return Returns the encoded property string value.
337     * @throws PropertyException
338     *             If the property value is invalid.
339     */
340    public String encodeValue(T value) {
341        Reject.ifNull(value);
342
343        return value.toString();
344    }
345
346    /**
347     * Indicates whether some other object is &quot;equal to&quot; this property
348     * definition. This method must obey the general contract of
349     * <tt>Object.equals(Object)</tt>. Additionally, this method can return
350     * <tt>true</tt> <i>only</i> if the specified Object is also a property
351     * definition and it has the same name, as returned by {@link #getName()},
352     * and also is deemed to be &quot;compatible&quot; with this property
353     * definition. Compatibility means that the two property definitions share
354     * the same underlying value type and provide similar comparator
355     * implementations.
356     *
357     * @param o
358     *            The reference object with which to compare.
359     * @return Returns <code>true</code> only if the specified object is also a
360     *         property definition and it has the same name and is compatible
361     *         with this property definition.
362     * @see java.lang.Object#equals(java.lang.Object)
363     * @see java.lang.Object#hashCode()
364     */
365    @Override
366    public final boolean equals(Object o) {
367        if (this == o) {
368            return true;
369        } else if (o instanceof PropertyDefinition) {
370            PropertyDefinition<?> other = (PropertyDefinition<?>) o;
371            return propertyName.equals(other.propertyName)
372                    && theClass.equals(other.theClass);
373        } else {
374            return false;
375        }
376    }
377
378    /**
379     * Get the administrator action associated with this property definition.
380     * The administrator action describes any action which the administrator
381     * must perform in order for changes to this property to take effect.
382     *
383     * @return Returns the administrator action associated with this property
384     *         definition.
385     */
386    public final AdministratorAction getAdministratorAction() {
387        return adminAction;
388    }
389
390    /**
391     * Get the default behavior provider associated with this property
392     * definition.
393     *
394     * @return Returns the default behavior provider associated with this
395     *         property definition.
396     */
397    public final DefaultBehaviorProvider<T> getDefaultBehaviorProvider() {
398        return defaultBehavior;
399    }
400
401    /**
402     * Gets the optional description of this property definition in the default
403     * locale.
404     *
405     * @return Returns the description of this property definition in the
406     *         default locale, or <code>null</code> if there is no description.
407     */
408    public final LocalizableMessage getDescription() {
409        return getDescription(Locale.getDefault());
410    }
411
412    /**
413     * Gets the optional description of this property definition in the
414     * specified locale.
415     *
416     * @param locale
417     *            The locale.
418     * @return Returns the description of this property definition in the
419     *         specified locale, or <code>null</code> if there is no
420     *         description.
421     */
422    public final LocalizableMessage getDescription(Locale locale) {
423        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
424        String property = "property." + propertyName + ".description";
425        try {
426            return resource.getMessage(definition, property, locale);
427        } catch (MissingResourceException e) {
428            return null;
429        }
430    }
431
432    /**
433     * Gets the managed object definition associated with this property
434     * definition.
435     *
436     * @return Returns the managed object definition associated with this
437     *         property definition.
438     */
439    public final AbstractManagedObjectDefinition<?, ?> getManagedObjectDefinition() {
440        return definition;
441    }
442
443    /**
444     * Get the name of the property.
445     *
446     * @return Returns the name of the property.
447     */
448    public final String getName() {
449        return propertyName;
450    }
451
452    /**
453     * Gets the synopsis of this property definition in the default locale.
454     *
455     * @return Returns the synopsis of this property definition in the default
456     *         locale.
457     */
458    public final LocalizableMessage getSynopsis() {
459        return getSynopsis(Locale.getDefault());
460    }
461
462    /**
463     * Gets the synopsis of this property definition in the specified locale.
464     *
465     * @param locale
466     *            The locale.
467     * @return Returns the synopsis of this property definition in the specified
468     *         locale.
469     */
470    public final LocalizableMessage getSynopsis(Locale locale) {
471        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
472        String property = "property." + propertyName + ".synopsis";
473        return resource.getMessage(definition, property, locale);
474    }
475
476    /**
477     * Returns a hash code value for this property definition. The hash code
478     * should be derived from the property name and the type of values handled
479     * by this property definition.
480     *
481     * @return Returns the hash code value for this property definition.
482     */
483    @Override
484    public final int hashCode() {
485        int rc = 17 + propertyName.hashCode();
486        return 37 * rc + theClass.hashCode();
487    }
488
489    /**
490     * Check if the specified option is set for this property definition.
491     *
492     * @param option
493     *            The option to test.
494     * @return Returns <code>true</code> if the option is set, or
495     *         <code>false</code> otherwise.
496     */
497    public final boolean hasOption(PropertyOption option) {
498        return options.contains(option);
499    }
500
501    /**
502     * Get a normalized string representation of a property value. This can then
503     * be used for comparisons and for generating hash-codes.
504     * <p>
505     * This method may throw an exception if the provided value is invalid.
506     * However, applications should not assume that implementations of this
507     * method will always validate a value. This task is the responsibility of
508     * {@link #validateValue(Object)}.
509     * <p>
510     * This default implementation simply returns the string representation of
511     * the provided value. Sub-classes might want to override this method if
512     * this behavior is insufficient (for example, a string property definition
513     * might strip white-space and convert characters to lower-case).
514     *
515     * @param value
516     *            The property value to be normalized.
517     * @return Returns the normalized property value.
518     * @throws PropertyException
519     *             If the property value is invalid.
520     */
521    public String normalizeValue(T value) {
522        Reject.ifNull(value);
523
524        return encodeValue(value);
525    }
526
527    /**
528     * Returns a string representation of this property definition.
529     *
530     * @return Returns a string representation of this property definition.
531     * @see Object#toString()
532     */
533    @Override
534    public final String toString() {
535        StringBuilder builder = new StringBuilder();
536        toString(builder);
537        return builder.toString();
538    }
539
540    /**
541     * Append a string representation of the property definition to the provided
542     * string builder.
543     * <p>
544     * This simple implementation just outputs the propertyName of the property
545     * definition. Sub-classes should override this method to provide more
546     * complete string representations.
547     *
548     * @param builder
549     *            The string builder where the string representation should be
550     *            appended.
551     */
552    public void toString(StringBuilder builder) {
553        builder.append(propertyName);
554    }
555
556    /**
557     * Determine if the provided property value is valid according to this
558     * property definition.
559     *
560     * @param value
561     *            The property value (must not be <code>null</code>).
562     * @throws PropertyException
563     *             If the property value is invalid.
564     */
565    public abstract void validateValue(T value);
566
567    /**
568     * Performs any run-time initialization required by this property
569     * definition. This may include resolving managed object paths and property
570     * names.
571     *
572     * @throws Exception
573     *             If this property definition could not be initialized.
574     */
575    protected void initialize() throws Exception {
576        // No implementation required.
577    }
578}