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