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 }