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.opends.server.admin;
019
020
021
022import static org.forgerock.util.Reject.*;
023import static org.opends.server.admin.PropertyException.*;
024
025import java.util.Collections;
026import java.util.EnumSet;
027import java.util.LinkedList;
028import java.util.List;
029
030
031
032/**
033 * Class property definition.
034 * <p>
035 * A class property definition defines a property whose values
036 * represent a Java class. It is possible to restrict the type of java
037 * class by specifying "instance of" constraints.
038 * <p>
039 * Note that in a client/server environment, the client is probably
040 * not capable of validating the Java class (e.g. it will not be able
041 * to load it nor have access to the interfaces it is supposed to
042 * implement). For this reason, it is possible to switch off
043 * validation in the client by calling the static method
044 * {@link #setAllowClassValidation(boolean)}.
045 */
046public final class ClassPropertyDefinition extends PropertyDefinition<String> {
047
048  /**
049   * An interface for incrementally constructing class property
050   * definitions.
051   */
052  public static class Builder extends
053      AbstractBuilder<String, ClassPropertyDefinition> {
054
055    /** List of interfaces which property values must implement. */
056    private List<String> instanceOfInterfaces = new LinkedList<>();
057
058    /** Private constructor. */
059    private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
060      super(d, propertyName);
061    }
062
063    /**
064     * Add an class name which property values must implement.
065     *
066     * @param className
067     *          The name of a class which property values must
068     *          implement.
069     */
070    public final void addInstanceOf(String className) {
071      ifNull(className);
072
073      /*
074       * Do some basic checks to make sure the string representation is valid.
075       */
076      String value = className.trim();
077      if (!value.matches(CLASS_RE)) {
078        throw new IllegalArgumentException("\"" + value
079            + "\" is not a valid Java class name");
080      }
081
082      /*
083       * If possible try and load the class in order to perform additional
084       * validation.
085       */
086      if (isAllowClassValidation()) {
087        /*
088         * Check that the class can be loaded so that validation can be
089         * performed.
090         */
091        try {
092          loadClass(value, true);
093        } catch (ClassNotFoundException e) {
094          // TODO: can we do something better here?
095          throw new RuntimeException(e);
096        }
097      }
098
099      instanceOfInterfaces.add(value);
100    }
101
102
103
104    /** {@inheritDoc} */
105    @Override
106    protected ClassPropertyDefinition buildInstance(
107        AbstractManagedObjectDefinition<?, ?> d,
108        String propertyName, EnumSet<PropertyOption> options,
109        AdministratorAction adminAction,
110        DefaultBehaviorProvider<String> defaultBehavior) {
111      return new ClassPropertyDefinition(d, propertyName, options,
112          adminAction, defaultBehavior, instanceOfInterfaces);
113    }
114
115  }
116
117  /** Regular expression for validating class names. */
118  private static final String CLASS_RE =
119    "^([A-Za-z][A-Za-z0-9_]*\\.)*[A-Za-z][A-Za-z0-9_]*(\\$[A-Za-z0-9_]+)*$";
120
121  /**
122   * Flag indicating whether class property values should be validated.
123   */
124  private static boolean allowClassValidation = true;
125
126
127
128  /**
129   * Create a class property definition builder.
130   *
131   * @param d
132   *          The managed object definition associated with this
133   *          property definition.
134   * @param propertyName
135   *          The property name.
136   * @return Returns the new class property definition builder.
137   */
138  public static Builder createBuilder(
139      AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
140    return new Builder(d, propertyName);
141  }
142
143
144
145  /**
146   * Determine whether or not class property definitions should
147   * validate class name property values. Validation involves checking
148   * that the class exists and that it implements the required
149   * interfaces.
150   *
151   * @return Returns <code>true</code> if class property definitions
152   *         should validate class name property values.
153   */
154  public static boolean isAllowClassValidation() {
155    return allowClassValidation;
156  }
157
158
159
160  /**
161   * Specify whether or not class property definitions should validate
162   * class name property values. Validation involves checking that the
163   * class exists and that it implements the required interfaces.
164   * <p>
165   * By default validation is switched on.
166   *
167   * @param value
168   *          <code>true</code> if class property definitions should
169   *          validate class name property values.
170   */
171  public static void setAllowClassValidation(boolean value) {
172    allowClassValidation = value;
173  }
174
175
176
177  /** Load a named class. */
178  private static Class<?> loadClass(String className, boolean initialize)
179      throws ClassNotFoundException, LinkageError {
180    return Class.forName(className, initialize, ClassLoaderProvider
181        .getInstance().getClassLoader());
182  }
183
184  /** List of interfaces which property values must implement. */
185  private final List<String> instanceOfInterfaces;
186
187  /** Private constructor. */
188  private ClassPropertyDefinition(
189      AbstractManagedObjectDefinition<?, ?> d, String propertyName,
190      EnumSet<PropertyOption> options,
191      AdministratorAction adminAction,
192      DefaultBehaviorProvider<String> defaultBehavior,
193      List<String> instanceOfInterfaces) {
194    super(d, String.class, propertyName, options, adminAction, defaultBehavior);
195
196    this.instanceOfInterfaces = Collections
197        .unmodifiableList(new LinkedList<String>(instanceOfInterfaces));
198  }
199
200
201
202  /** {@inheritDoc} */
203  @Override
204  public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) {
205    return v.visitClass(this, p);
206  }
207
208
209
210  /** {@inheritDoc} */
211  @Override
212  public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) {
213    return v.visitClass(this, value, p);
214  }
215
216
217
218  /** {@inheritDoc} */
219  @Override
220  public String decodeValue(String value)
221      throws PropertyException {
222    ifNull(value);
223
224    try {
225      validateValue(value);
226    } catch (PropertyException e) {
227      throw illegalPropertyValueException(this, value, e.getCause());
228    }
229
230    return value;
231  }
232
233
234
235  /**
236   * Get an unmodifiable list of classes which values of this property
237   * must implement.
238   *
239   * @return Returns an unmodifiable list of classes which values of
240   *         this property must implement.
241   */
242  public List<String> getInstanceOfInterface() {
243    return instanceOfInterfaces;
244  }
245
246
247
248  /**
249   * Validate and load the named class, and cast it to a subclass of
250   * the specified class.
251   *
252   * @param <T>
253   *          The requested type.
254   * @param className
255   *          The name of the class to validate and load.
256   * @param instanceOf
257   *          The class representing the requested type.
258   * @return Returns the named class cast to a subclass of the
259   *         specified class.
260   * @throws PropertyException
261   *           If the named class was invalid, could not be loaded, or
262   *           did not implement the required interfaces.
263   * @throws ClassCastException
264   *           If the referenced class does not implement the
265   *           requested type.
266   */
267  public <T> Class<? extends T> loadClass(String className,
268      Class<T> instanceOf) throws PropertyException,
269      ClassCastException {
270    ifNull(className, instanceOf);
271
272    // Make sure that the named class is valid.
273    validateClassName(className);
274    Class<?> theClass = validateClassInterfaces(className, true);
275
276    // Cast it to the required type.
277    return theClass.asSubclass(instanceOf);
278  }
279
280
281
282  /** {@inheritDoc} */
283  @Override
284  public String normalizeValue(String value)
285      throws PropertyException {
286    ifNull(value);
287
288    return value.trim();
289  }
290
291
292
293  /** {@inheritDoc} */
294  @Override
295  public void validateValue(String value)
296      throws PropertyException {
297    ifNull(value);
298
299    // Always make sure the name is a valid class name.
300    validateClassName(value);
301
302    /*
303     * If additional validation is enabled then attempt to load the class and
304     * check the interfaces that it implements/extends.
305     */
306    if (allowClassValidation) {
307      validateClassInterfaces(value, false);
308    }
309  }
310
311
312
313  /**
314   * Make sure that named class implements the interfaces named by this
315   * definition.
316   */
317  private Class<?> validateClassInterfaces(String className, boolean initialize)
318      throws PropertyException {
319    Class<?> theClass = loadClassForValidation(className, className,
320        initialize);
321    for (String i : instanceOfInterfaces) {
322      Class<?> instanceOfClass = loadClassForValidation(className, i,
323          initialize);
324      if (!instanceOfClass.isAssignableFrom(theClass)) {
325        throw PropertyException.illegalPropertyValueException(this, className);
326      }
327    }
328    return theClass;
329  }
330
331
332
333  private Class<?> loadClassForValidation(String componentClassName,
334      String classToBeLoaded, boolean initialize) {
335    try {
336      return loadClass(classToBeLoaded.trim(), initialize);
337    } catch (ClassNotFoundException | LinkageError e) {
338      // If the class cannot be loaded then it is an invalid value.
339      throw illegalPropertyValueException(this, componentClassName, e);
340    }
341  }
342
343
344
345  /**
346   * Do some basic checks to make sure the string representation is valid.
347   */
348  private void validateClassName(String className)
349      throws PropertyException {
350    String nvalue = className.trim();
351    if (!nvalue.matches(CLASS_RE)) {
352      throw PropertyException.illegalPropertyValueException(this, className);
353    }
354  }
355}