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 }