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 */
026
027package org.forgerock.opendj.config;
028
029import org.forgerock.util.Reject;
030
031import java.util.EnumSet;
032
033/**
034 * Duration property definition.
035 * <p>
036 * A duration property definition comprises of:
037 * <ul>
038 * <li>a <i>base unit</i> - specifies the minimum granularity which can be used
039 * to specify duration property values. For example, if the base unit is in
040 * seconds then values represented in milliseconds will not be permitted. The
041 * default base unit is seconds
042 * <li>an optional <i>maximum unit</i> - specifies the biggest duration unit
043 * which can be used to specify duration property values. Values presented in
044 * units greater than this unit will not be permitted. There is no default
045 * maximum unit
046 * <li><i>lower limit</i> - specifies the smallest duration permitted by the
047 * property. The default lower limit is 0 and can never be less than 0
048 * <li>an optional <i>upper limit</i> - specifies the biggest duration permitted
049 * by the property. By default, there is no upper limit
050 * <li>support for <i>unlimited</i> durations - when permitted users can specify
051 * "unlimited" durations. These are represented using the decoded value, -1, or
052 * the encoded string value "unlimited". By default, unlimited durations are not
053 * permitted. In addition, it is not possible to define an upper limit and
054 * support unlimited values.
055 * </ul>
056 * Decoded values are represented using <code>long</code> values in the base
057 * unit defined for the duration property definition.
058 */
059public final class DurationPropertyDefinition extends PropertyDefinition<Long> {
060
061    /** String used to represent unlimited durations. */
062    private static final String UNLIMITED = "unlimited";
063
064    /** The base unit for this property definition. */
065    private final DurationUnit baseUnit;
066
067    /** The optional maximum unit for this property definition. */
068    private final DurationUnit maximumUnit;
069
070    /** The lower limit of the property value in milli-seconds. */
071    private final long lowerLimit;
072
073    /** The optional upper limit of the property value in milli-seconds. */
074    private final Long upperLimit;
075
076    /**
077     * Indicates whether this property allows the use of the "unlimited"
078     * duration value (represented using a -1L or the string
079     * "unlimited").
080     */
081    private final boolean allowUnlimited;
082
083    /**
084     * An interface for incrementally constructing duration property
085     * definitions.
086     */
087    public static final class Builder extends AbstractBuilder<Long, DurationPropertyDefinition> {
088
089        /** The base unit for this property definition. */
090        private DurationUnit baseUnit = DurationUnit.SECONDS;
091
092        /** The optional maximum unit for this property definition. */
093        private DurationUnit maximumUnit;
094
095        /** The lower limit of the property value in milli-seconds. */
096        private long lowerLimit;
097
098        /** The optional upper limit of the property value in milli-seconds. */
099        private Long upperLimit;
100
101        /**
102         * Indicates whether this property allows the use of the
103         * "unlimited" duration value (represented using a -1L or the
104         * string "unlimited").
105         */
106        private boolean allowUnlimited;
107
108        /** Private constructor. */
109        private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
110            super(d, propertyName);
111        }
112
113        /**
114         * Set the base unit for this property definition (values including
115         * limits are specified in this unit). By default a duration property
116         * definition uses seconds.
117         *
118         * @param unit
119         *            The string representation of the base unit (must not be
120         *            <code>null</code>).
121         * @throws IllegalArgumentException
122         *             If the provided unit name did not correspond to a known
123         *             duration unit, or if the base unit is bigger than the
124         *             maximum unit.
125         */
126        public final void setBaseUnit(String unit) {
127            Reject.ifNull(unit);
128
129            setBaseUnit(DurationUnit.getUnit(unit));
130        }
131
132        /**
133         * Set the base unit for this property definition (values including
134         * limits are specified in this unit). By default a duration property
135         * definition uses seconds.
136         *
137         * @param unit
138         *            The base unit (must not be <code>null</code>).
139         * @throws IllegalArgumentException
140         *             If the provided base unit is bigger than the maximum
141         *             unit.
142         */
143        public final void setBaseUnit(DurationUnit unit) {
144            Reject.ifNull(unit);
145
146            // Make sure that the base unit is not bigger than the maximum unit.
147            if (maximumUnit != null && unit.getDuration() > maximumUnit.getDuration()) {
148                throw new IllegalArgumentException("Base unit greater than maximum unit");
149            }
150
151            this.baseUnit = unit;
152        }
153
154        /**
155         * Set the maximum unit for this property definition. By default there
156         * is no maximum unit.
157         *
158         * @param unit
159         *            The string representation of the maximum unit, or
160         *            <code>null</code> if there should not be a maximum unit.
161         * @throws IllegalArgumentException
162         *             If the provided unit name did not correspond to a known
163         *             duration unit, or if the maximum unit is smaller than the
164         *             base unit.
165         */
166        public final void setMaximumUnit(String unit) {
167            setMaximumUnit(unit != null ? DurationUnit.getUnit(unit) : null);
168        }
169
170        /**
171         * Set the maximum unit for this property definition. By default there
172         * is no maximum unit.
173         *
174         * @param unit
175         *            The maximum unit, or <code>null</code> if there should not
176         *            be a maximum unit.
177         * @throws IllegalArgumentException
178         *             If the provided maximum unit is smaller than the base
179         *             unit.
180         */
181        public final void setMaximumUnit(DurationUnit unit) {
182            // Make sure that the maximum unit is not smaller than the base unit.
183            if (unit != null && unit.getDuration() < baseUnit.getDuration()) {
184                throw new IllegalArgumentException("Maximum unit smaller than base unit");
185            }
186
187            this.maximumUnit = unit;
188        }
189
190        /**
191         * Set the lower limit in milli-seconds.
192         *
193         * @param lowerLimit
194         *            The new lower limit (must be >= 0) in milli-seconds.
195         * @throws IllegalArgumentException
196         *             If a negative lower limit was specified, or the lower
197         *             limit is greater than the upper limit.
198         */
199        public final void setLowerLimit(long lowerLimit) {
200            if (lowerLimit < 0) {
201                throw new IllegalArgumentException("Negative lower limit");
202            }
203
204            if (upperLimit != null && lowerLimit > upperLimit) {
205                throw new IllegalArgumentException("Lower limit greater than upper limit");
206            }
207
208            this.lowerLimit = lowerLimit;
209        }
210
211        /**
212         * Set the lower limit using a string representation of the limit. If
213         * the string does not specify a unit, the current base unit will be
214         * used.
215         *
216         * @param lowerLimit
217         *            The string representation of the new lower limit.
218         * @throws IllegalArgumentException
219         *             If the lower limit could not be parsed, or if a negative
220         *             lower limit was specified, or the lower limit is greater
221         *             than the upper limit.
222         */
223        public final void setLowerLimit(String lowerLimit) {
224            setLowerLimit(DurationUnit.parseValue(lowerLimit, baseUnit));
225        }
226
227        /**
228         * Set the upper limit in milli-seconds.
229         *
230         * @param upperLimit
231         *            The new upper limit in milli-seconds, or <code>null</code>
232         *            if there is no upper limit.
233         * @throws IllegalArgumentException
234         *             If a negative upper limit was specified, or the lower
235         *             limit is greater than the upper limit or unlimited
236         *             durations are permitted.
237         */
238        public final void setUpperLimit(Long upperLimit) {
239            if (upperLimit != null) {
240                if (upperLimit < 0) {
241                    throw new IllegalArgumentException("Negative upper limit");
242                }
243
244                if (lowerLimit > upperLimit) {
245                    throw new IllegalArgumentException("Lower limit greater than upper limit");
246                }
247
248                if (allowUnlimited) {
249                    throw new IllegalArgumentException("Upper limit specified when unlimited durations are permitted");
250                }
251            }
252
253            this.upperLimit = upperLimit;
254        }
255
256        /**
257         * Set the upper limit using a string representation of the limit. If
258         * the string does not specify a unit, the current base unit will be
259         * used.
260         *
261         * @param upperLimit
262         *            The string representation of the new upper limit, or
263         *            <code>null</code> if there is no upper limit.
264         * @throws IllegalArgumentException
265         *             If the upper limit could not be parsed, or if the lower
266         *             limit is greater than the upper limit.
267         */
268        public final void setUpperLimit(String upperLimit) {
269            setUpperLimit(upperLimit != null ? DurationUnit.parseValue(upperLimit, baseUnit) : null);
270        }
271
272        /**
273         * Specify whether or not this property definition will allow unlimited
274         * values (default is false).
275         *
276         * @param allowUnlimited
277         *            <code>true</code> if the property will allow unlimited
278         *            values, or <code>false</code> otherwise.
279         * @throws IllegalArgumentException
280         *             If unlimited values are to be permitted but there is an
281         *             upper limit specified.
282         */
283        public final void setAllowUnlimited(boolean allowUnlimited) {
284            if (allowUnlimited && upperLimit != null) {
285                throw new IllegalArgumentException("Upper limit specified when unlimited durations are permitted");
286            }
287
288            this.allowUnlimited = allowUnlimited;
289        }
290
291        /** {@inheritDoc} */
292        @Override
293        protected DurationPropertyDefinition buildInstance(AbstractManagedObjectDefinition<?, ?> d,
294            String propertyName, EnumSet<PropertyOption> options, AdministratorAction adminAction,
295            DefaultBehaviorProvider<Long> defaultBehavior) {
296            return new DurationPropertyDefinition(d, propertyName, options, adminAction, defaultBehavior, baseUnit,
297                maximumUnit, lowerLimit, upperLimit, allowUnlimited);
298        }
299    }
300
301    /**
302     * Create a duration property definition builder.
303     *
304     * @param d
305     *            The managed object definition associated with this property
306     *            definition.
307     * @param propertyName
308     *            The property name.
309     * @return Returns the new integer property definition builder.
310     */
311    public static Builder createBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
312        return new Builder(d, propertyName);
313    }
314
315    /** Private constructor. */
316    private DurationPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
317        EnumSet<PropertyOption> options, AdministratorAction adminAction,
318        DefaultBehaviorProvider<Long> defaultBehavior, DurationUnit baseUnit, DurationUnit maximumUnit,
319        Long lowerLimit, Long upperLimit, boolean allowUnlimited) {
320        super(d, Long.class, propertyName, options, adminAction, defaultBehavior);
321        this.baseUnit = baseUnit;
322        this.maximumUnit = maximumUnit;
323        this.lowerLimit = lowerLimit;
324        this.upperLimit = upperLimit;
325        this.allowUnlimited = allowUnlimited;
326    }
327
328    /**
329     * Get the base unit for this property definition (values including limits
330     * are specified in this unit).
331     *
332     * @return Returns the base unit for this property definition (values
333     *         including limits are specified in this unit).
334     */
335    public DurationUnit getBaseUnit() {
336        return baseUnit;
337    }
338
339    /**
340     * Get the maximum unit for this property definition if specified.
341     *
342     * @return Returns the maximum unit for this property definition, or
343     *         <code>null</code> if there is no maximum unit.
344     */
345    public DurationUnit getMaximumUnit() {
346        return maximumUnit;
347    }
348
349    /**
350     * Get the lower limit in milli-seconds.
351     *
352     * @return Returns the lower limit in milli-seconds.
353     */
354    public long getLowerLimit() {
355        return lowerLimit;
356    }
357
358    /**
359     * Get the upper limit in milli-seconds.
360     *
361     * @return Returns the upper limit in milli-seconds, or <code>null</code> if
362     *         there is no upper limit.
363     */
364    public Long getUpperLimit() {
365        return upperLimit;
366    }
367
368    /**
369     * Determine whether this property allows unlimited durations.
370     *
371     * @return Returns <code>true</code> if this this property allows unlimited
372     *         durations.
373     */
374    public boolean isAllowUnlimited() {
375        return allowUnlimited;
376    }
377
378    /** {@inheritDoc} */
379    @Override
380    public void validateValue(Long value) {
381        Reject.ifNull(value);
382
383        long nvalue = baseUnit.toMilliSeconds(value);
384        if (!allowUnlimited && nvalue < lowerLimit) {
385            throw PropertyException.illegalPropertyValueException(this, value);
386
387            // unlimited allowed
388        } else if (nvalue >= 0 && nvalue < lowerLimit) {
389            throw PropertyException.illegalPropertyValueException(this, value);
390        }
391
392        if (upperLimit != null && nvalue > upperLimit) {
393            throw PropertyException.illegalPropertyValueException(this, value);
394        }
395    }
396
397    /** {@inheritDoc} */
398    @Override
399    public String encodeValue(Long value) {
400        Reject.ifNull(value);
401
402        // Make sure that we correctly encode negative values as "unlimited".
403        if (allowUnlimited && value < 0) {
404            return UNLIMITED;
405        }
406
407        // Encode the size value using the base unit.
408        StringBuilder builder = new StringBuilder();
409        builder.append(value);
410        builder.append(' ');
411        builder.append(baseUnit);
412        return builder.toString();
413    }
414
415    /** {@inheritDoc} */
416    @Override
417    public Long decodeValue(String value) {
418        Reject.ifNull(value);
419
420        // First check for the special "unlimited" value when necessary.
421        if (allowUnlimited && UNLIMITED.equalsIgnoreCase(value.trim())) {
422            return -1L;
423        }
424
425        // Parse the string representation.
426        long ms;
427        try {
428            ms = DurationUnit.parseValue(value);
429        } catch (NumberFormatException e) {
430            throw PropertyException.illegalPropertyValueException(this, value);
431        }
432
433        // Check the unit is in range - values must not be more granular
434        // than the base unit.
435        if (ms % baseUnit.getDuration() != 0) {
436            throw PropertyException.illegalPropertyValueException(this, value);
437        }
438
439        // Convert the value a long in the property's required unit.
440        Long i = (long) baseUnit.fromMilliSeconds(ms);
441        try {
442            validateValue(i);
443            return i;
444        } catch (PropertyException e) {
445            throw PropertyException.illegalPropertyValueException(this, value);
446        }
447    }
448
449    /** {@inheritDoc} */
450    @Override
451    public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) {
452        return v.visitDuration(this, p);
453    }
454
455    /** {@inheritDoc} */
456    @Override
457    public <R, P> R accept(PropertyValueVisitor<R, P> v, Long value, P p) {
458        return v.visitDuration(this, value, p);
459    }
460
461    /** {@inheritDoc} */
462    @Override
463    public void toString(StringBuilder builder) {
464        super.toString(builder);
465
466        builder.append(" baseUnit=");
467        builder.append(baseUnit);
468
469        if (maximumUnit != null) {
470            builder.append(" maximumUnit=");
471            builder.append(maximumUnit);
472        }
473
474        builder.append(" lowerLimit=");
475        builder.append(lowerLimit);
476        builder.append("ms");
477
478        if (upperLimit != null) {
479            builder.append(" upperLimit=");
480            builder.append(upperLimit);
481            builder.append("ms");
482        }
483
484        builder.append(" allowUnlimited=");
485        builder.append(allowUnlimited);
486    }
487
488    /** {@inheritDoc} */
489    @Override
490    public int compare(Long o1, Long o2) {
491        return o1.compareTo(o2);
492    }
493
494}