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