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-2016 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            /* Do some basic checks to make sure the string representation is valid. */
062            String value = className.trim();
063            if (!value.matches(CLASS_RE)) {
064                throw new IllegalArgumentException("\"" + value + "\" is not a valid Java class name");
065            }
066
067            instanceOfInterfaces.add(value);
068        }
069
070        @Override
071        protected ClassPropertyDefinition buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
072            EnumSet<PropertyOption> options, AdministratorAction adminAction,
073            DefaultBehaviorProvider<String> defaultBehavior) {
074            return new ClassPropertyDefinition(d, propertyName, options, adminAction, defaultBehavior,
075                instanceOfInterfaces);
076        }
077
078    }
079
080    /** Regular expression for validating class names. */
081    private static final String CLASS_RE = "^([A-Za-z][A-Za-z0-9_]*\\.)*[A-Za-z][A-Za-z0-9_]*(\\$[A-Za-z0-9_]+)*$";
082
083    /**
084     * Create a class property definition builder.
085     *
086     * @param d
087     *            The managed object definition associated with this property
088     *            definition.
089     * @param propertyName
090     *            The property name.
091     * @return Returns the new class property definition builder.
092     */
093    public static Builder createBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
094        return new Builder(d, propertyName);
095    }
096
097    /** Load a named class. */
098    private static Class<?> loadClass(String className, boolean initialize) throws ClassNotFoundException {
099        return Class.forName(className, initialize, ConfigurationFramework.getInstance().getClassLoader());
100    }
101
102    /** List of interfaces which property values must implement. */
103    private final List<String> instanceOfInterfaces;
104
105    /** Private constructor. */
106    private ClassPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
107        EnumSet<PropertyOption> options, AdministratorAction adminAction,
108        DefaultBehaviorProvider<String> defaultBehavior, List<String> instanceOfInterfaces) {
109        super(d, String.class, propertyName, options, adminAction, defaultBehavior);
110
111        this.instanceOfInterfaces = Collections.unmodifiableList(new LinkedList<String>(instanceOfInterfaces));
112    }
113
114    @Override
115    public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) {
116        return v.visitClass(this, p);
117    }
118
119    @Override
120    public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) {
121        return v.visitClass(this, value, p);
122    }
123
124    @Override
125    public String decodeValue(String value) {
126        Reject.ifNull(value);
127
128        try {
129            validateValue(value);
130        } catch (PropertyException e) {
131            throw PropertyException.illegalPropertyValueException(this, value, e.getCause());
132        }
133
134        return value;
135    }
136
137    /**
138     * Get an unmodifiable list of classes which values of this property must
139     * implement.
140     *
141     * @return Returns an unmodifiable list of classes which values of this
142     *         property must implement.
143     */
144    public List<String> getInstanceOfInterface() {
145        return instanceOfInterfaces;
146    }
147
148    /**
149     * Validate and load the named class, and cast it to a subclass of the
150     * specified class.
151     *
152     * @param <T>
153     *            The requested type.
154     * @param className
155     *            The name of the class to validate and load.
156     * @param instanceOf
157     *            The class representing the requested type.
158     * @return Returns the named class cast to a subclass of the specified
159     *         class.
160     * @throws PropertyException
161     *             If the named class was invalid, could not be loaded, or did
162     *             not implement the required interfaces.
163     * @throws ClassCastException
164     *             If the referenced class does not implement the requested
165     *             type.
166     */
167    public <T> Class<? extends T> loadClass(String className, Class<T> instanceOf) {
168        Reject.ifNull(className, instanceOf);
169
170        // Make sure that the named class is valid.
171        validateClassName(className);
172        Class<?> theClass = validateClassInterfaces(className, true);
173
174        // Cast it to the required type.
175        return theClass.asSubclass(instanceOf);
176    }
177
178    @Override
179    public String normalizeValue(String value) {
180        Reject.ifNull(value);
181
182        return value.trim();
183    }
184
185    @Override
186    public void validateValue(String value) {
187        Reject.ifNull(value);
188
189        // Always make sure the name is a valid class name.
190        validateClassName(value);
191
192        /*
193         * If additional validation is enabled then attempt to load the class
194         * and check the interfaces that it implements/extends.
195         */
196        if (!ConfigurationFramework.getInstance().isClient()) {
197            validateClassInterfaces(value, false);
198        }
199    }
200
201    /** Do some basic checks to make sure the string representation is valid. */
202    private void validateClassName(String className) {
203        String nvalue = className.trim();
204        if (!nvalue.matches(CLASS_RE)) {
205            throw PropertyException.illegalPropertyValueException(this, className);
206        }
207    }
208
209    /** Make sure that named class implements the interfaces named by this definition. */
210    private Class<?> validateClassInterfaces(String className, boolean initialize) {
211        Class<?> theClass = loadClassForValidation(className, className, initialize);
212        for (String i : instanceOfInterfaces) {
213            Class<?> instanceOfClass = loadClassForValidation(className, i, initialize);
214            if (!instanceOfClass.isAssignableFrom(theClass)) {
215                throw PropertyException.illegalPropertyValueException(this, className);
216            }
217        }
218        return theClass;
219    }
220
221    private Class<?> loadClassForValidation(String componentClassName, String classToBeLoaded, boolean initialize) {
222        try {
223            return loadClass(classToBeLoaded.trim(), initialize);
224        } catch (ClassNotFoundException | LinkageError e) {
225            // If the class cannot be loaded / initialized then it is an invalid value.
226            throw PropertyException.illegalPropertyValueException(this, componentClassName, e);
227        }
228    }
229}