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