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