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 2013-2015 ForgeRock AS.
026 */
027
028package org.forgerock.opendj.config;
029
030import org.forgerock.util.Reject;
031
032import java.util.Collections;
033import java.util.EnumSet;
034import java.util.LinkedList;
035import java.util.List;
036
037/**
038 * Class property definition.
039 * <p>
040 * A class property definition defines a property whose values represent a Java
041 * class. It is possible to restrict the type of java class by specifying
042 * "instance of" constraints.
043 * <p>
044 * Note that in a client/server environment, the client is probably not capable
045 * of validating the Java class (e.g. it will not be able to load it nor have
046 * access to the interfaces it is supposed to implement). For this reason,
047 * validation is disabled in client applications.
048 */
049public final class ClassPropertyDefinition extends PropertyDefinition<String> {
050
051    /** An interface for incrementally constructing class property definitions. */
052    public static final class Builder extends AbstractBuilder<String, ClassPropertyDefinition> {
053
054        /** List of interfaces which property values must implement. */
055        private final List<String> instanceOfInterfaces = new LinkedList<>();
056
057        /** Private constructor. */
058        private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
059            super(d, propertyName);
060        }
061
062        /**
063         * Add an class name which property values must implement.
064         *
065         * @param className
066         *            The name of a class which property values must implement.
067         */
068        public final void addInstanceOf(String className) {
069            Reject.ifNull(className);
070
071            /*
072             * Do some basic checks to make sure the string representation is
073             * valid.
074             */
075            String value = className.trim();
076            if (!value.matches(CLASS_RE)) {
077                throw new IllegalArgumentException("\"" + value + "\" is not a valid Java class name");
078            }
079
080            instanceOfInterfaces.add(value);
081        }
082
083        /** {@inheritDoc} */
084        @Override
085        protected ClassPropertyDefinition buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
086            EnumSet<PropertyOption> options, AdministratorAction adminAction,
087            DefaultBehaviorProvider<String> defaultBehavior) {
088            return new ClassPropertyDefinition(d, propertyName, options, adminAction, defaultBehavior,
089                instanceOfInterfaces);
090        }
091
092    }
093
094    /** Regular expression for validating class names. */
095    private static final String CLASS_RE = "^([A-Za-z][A-Za-z0-9_]*\\.)*[A-Za-z][A-Za-z0-9_]*(\\$[A-Za-z0-9_]+)*$";
096
097    /**
098     * Create a class property definition builder.
099     *
100     * @param d
101     *            The managed object definition associated with this property
102     *            definition.
103     * @param propertyName
104     *            The property name.
105     * @return Returns the new class property definition builder.
106     */
107    public static Builder createBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
108        return new Builder(d, propertyName);
109    }
110
111    /** Load a named class. */
112    private static Class<?> loadClass(String className, boolean initialize) throws ClassNotFoundException {
113        return Class.forName(className, initialize, ConfigurationFramework.getInstance().getClassLoader());
114    }
115
116    /** List of interfaces which property values must implement. */
117    private final List<String> instanceOfInterfaces;
118
119    /** Private constructor. */
120    private ClassPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
121        EnumSet<PropertyOption> options, AdministratorAction adminAction,
122        DefaultBehaviorProvider<String> defaultBehavior, List<String> instanceOfInterfaces) {
123        super(d, String.class, propertyName, options, adminAction, defaultBehavior);
124
125        this.instanceOfInterfaces = Collections.unmodifiableList(new LinkedList<String>(instanceOfInterfaces));
126    }
127
128    /** {@inheritDoc} */
129    @Override
130    public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) {
131        return v.visitClass(this, p);
132    }
133
134    /** {@inheritDoc} */
135    @Override
136    public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) {
137        return v.visitClass(this, value, p);
138    }
139
140    /** {@inheritDoc} */
141    @Override
142    public String decodeValue(String value) {
143        Reject.ifNull(value);
144
145        try {
146            validateValue(value);
147        } catch (PropertyException e) {
148            throw PropertyException.illegalPropertyValueException(this, value, e.getCause());
149        }
150
151        return value;
152    }
153
154    /**
155     * Get an unmodifiable list of classes which values of this property must
156     * implement.
157     *
158     * @return Returns an unmodifiable list of classes which values of this
159     *         property must implement.
160     */
161    public List<String> getInstanceOfInterface() {
162        return instanceOfInterfaces;
163    }
164
165    /**
166     * Validate and load the named class, and cast it to a subclass of the
167     * specified class.
168     *
169     * @param <T>
170     *            The requested type.
171     * @param className
172     *            The name of the class to validate and load.
173     * @param instanceOf
174     *            The class representing the requested type.
175     * @return Returns the named class cast to a subclass of the specified
176     *         class.
177     * @throws PropertyException
178     *             If the named class was invalid, could not be loaded, or did
179     *             not implement the required interfaces.
180     * @throws ClassCastException
181     *             If the referenced class does not implement the requested
182     *             type.
183     */
184    public <T> Class<? extends T> loadClass(String className, Class<T> instanceOf) {
185        Reject.ifNull(className, instanceOf);
186
187        // Make sure that the named class is valid.
188        validateClassName(className);
189        Class<?> theClass = validateClassInterfaces(className, true);
190
191        // Cast it to the required type.
192        return theClass.asSubclass(instanceOf);
193    }
194
195    /** {@inheritDoc} */
196    @Override
197    public String normalizeValue(String value) {
198        Reject.ifNull(value);
199
200        return value.trim();
201    }
202
203    /** {@inheritDoc} */
204    @Override
205    public void validateValue(String value) {
206        Reject.ifNull(value);
207
208        // Always make sure the name is a valid class name.
209        validateClassName(value);
210
211        /*
212         * If additional validation is enabled then attempt to load the class
213         * and check the interfaces that it implements/extends.
214         */
215        if (!ConfigurationFramework.getInstance().isClient()) {
216            validateClassInterfaces(value, false);
217        }
218    }
219
220    /**
221     * Do some basic checks to make sure the string representation is valid.
222     */
223    private void validateClassName(String className) {
224        String nvalue = className.trim();
225        if (!nvalue.matches(CLASS_RE)) {
226            throw PropertyException.illegalPropertyValueException(this, className);
227        }
228    }
229
230    /**
231     * Make sure that named class implements the interfaces named by this
232     * definition.
233     */
234    private Class<?> validateClassInterfaces(String className, boolean initialize) {
235        Class<?> theClass = loadClassForValidation(className, className, initialize);
236        for (String i : instanceOfInterfaces) {
237            Class<?> instanceOfClass = loadClassForValidation(className, i, initialize);
238            if (!instanceOfClass.isAssignableFrom(theClass)) {
239                throw PropertyException.illegalPropertyValueException(this, className);
240            }
241        }
242        return theClass;
243    }
244
245    private Class<?> loadClassForValidation(String componentClassName, String classToBeLoaded, boolean initialize) {
246        try {
247            return loadClass(classToBeLoaded.trim(), initialize);
248        } catch (ClassNotFoundException | LinkageError e) {
249            // If the class cannot be loaded / initialized then it is an invalid value.
250            throw PropertyException.illegalPropertyValueException(this, componentClassName, e);
251        }
252    }
253}