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