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}