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.forgerock.opendj.config; 029 030import org.forgerock.util.Reject; 031 032import java.util.Collections; 033import java.util.EnumSet; 034import java.util.LinkedList; 035import java.util.List; 036 037/** 038 * Class property definition. 039 * <p> 040 * A class property definition defines a property whose values represent a Java 041 * class. It is possible to restrict the type of java class by specifying 042 * "instance of" constraints. 043 * <p> 044 * Note that in a client/server environment, the client is probably not capable 045 * of validating the Java class (e.g. it will not be able to load it nor have 046 * access to the interfaces it is supposed to implement). For this reason, 047 * validation is disabled in client applications. 048 */ 049public final class ClassPropertyDefinition extends PropertyDefinition<String> { 050 051 /** An interface for incrementally constructing class property definitions. */ 052 public static final class Builder extends AbstractBuilder<String, ClassPropertyDefinition> { 053 054 /** List of interfaces which property values must implement. */ 055 private final List<String> instanceOfInterfaces = new LinkedList<>(); 056 057 /** Private constructor. */ 058 private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) { 059 super(d, propertyName); 060 } 061 062 /** 063 * Add an class name which property values must implement. 064 * 065 * @param className 066 * The name of a class which property values must implement. 067 */ 068 public final void addInstanceOf(String className) { 069 Reject.ifNull(className); 070 071 /* 072 * Do some basic checks to make sure the string representation is 073 * valid. 074 */ 075 String value = className.trim(); 076 if (!value.matches(CLASS_RE)) { 077 throw new IllegalArgumentException("\"" + value + "\" is not a valid Java class name"); 078 } 079 080 instanceOfInterfaces.add(value); 081 } 082 083 /** {@inheritDoc} */ 084 @Override 085 protected ClassPropertyDefinition buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName, 086 EnumSet<PropertyOption> options, AdministratorAction adminAction, 087 DefaultBehaviorProvider<String> defaultBehavior) { 088 return new ClassPropertyDefinition(d, propertyName, options, adminAction, defaultBehavior, 089 instanceOfInterfaces); 090 } 091 092 } 093 094 /** Regular expression for validating class names. */ 095 private static final String CLASS_RE = "^([A-Za-z][A-Za-z0-9_]*\\.)*[A-Za-z][A-Za-z0-9_]*(\\$[A-Za-z0-9_]+)*$"; 096 097 /** 098 * Create a class property definition builder. 099 * 100 * @param d 101 * The managed object definition associated with this property 102 * definition. 103 * @param propertyName 104 * The property name. 105 * @return Returns the new class property definition builder. 106 */ 107 public static Builder createBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) { 108 return new Builder(d, propertyName); 109 } 110 111 /** Load a named class. */ 112 private static Class<?> loadClass(String className, boolean initialize) throws ClassNotFoundException { 113 return Class.forName(className, initialize, ConfigurationFramework.getInstance().getClassLoader()); 114 } 115 116 /** List of interfaces which property values must implement. */ 117 private final List<String> instanceOfInterfaces; 118 119 /** Private constructor. */ 120 private ClassPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName, 121 EnumSet<PropertyOption> options, AdministratorAction adminAction, 122 DefaultBehaviorProvider<String> defaultBehavior, List<String> instanceOfInterfaces) { 123 super(d, String.class, propertyName, options, adminAction, defaultBehavior); 124 125 this.instanceOfInterfaces = Collections.unmodifiableList(new LinkedList<String>(instanceOfInterfaces)); 126 } 127 128 /** {@inheritDoc} */ 129 @Override 130 public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) { 131 return v.visitClass(this, p); 132 } 133 134 /** {@inheritDoc} */ 135 @Override 136 public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) { 137 return v.visitClass(this, value, p); 138 } 139 140 /** {@inheritDoc} */ 141 @Override 142 public String decodeValue(String value) { 143 Reject.ifNull(value); 144 145 try { 146 validateValue(value); 147 } catch (PropertyException e) { 148 throw PropertyException.illegalPropertyValueException(this, value, e.getCause()); 149 } 150 151 return value; 152 } 153 154 /** 155 * Get an unmodifiable list of classes which values of this property must 156 * implement. 157 * 158 * @return Returns an unmodifiable list of classes which values of this 159 * property must implement. 160 */ 161 public List<String> getInstanceOfInterface() { 162 return instanceOfInterfaces; 163 } 164 165 /** 166 * Validate and load the named class, and cast it to a subclass of the 167 * specified class. 168 * 169 * @param <T> 170 * The requested type. 171 * @param className 172 * The name of the class to validate and load. 173 * @param instanceOf 174 * The class representing the requested type. 175 * @return Returns the named class cast to a subclass of the specified 176 * class. 177 * @throws PropertyException 178 * If the named class was invalid, could not be loaded, or did 179 * not implement the required interfaces. 180 * @throws ClassCastException 181 * If the referenced class does not implement the requested 182 * type. 183 */ 184 public <T> Class<? extends T> loadClass(String className, Class<T> instanceOf) { 185 Reject.ifNull(className, instanceOf); 186 187 // Make sure that the named class is valid. 188 validateClassName(className); 189 Class<?> theClass = validateClassInterfaces(className, true); 190 191 // Cast it to the required type. 192 return theClass.asSubclass(instanceOf); 193 } 194 195 /** {@inheritDoc} */ 196 @Override 197 public String normalizeValue(String value) { 198 Reject.ifNull(value); 199 200 return value.trim(); 201 } 202 203 /** {@inheritDoc} */ 204 @Override 205 public void validateValue(String value) { 206 Reject.ifNull(value); 207 208 // Always make sure the name is a valid class name. 209 validateClassName(value); 210 211 /* 212 * If additional validation is enabled then attempt to load the class 213 * and check the interfaces that it implements/extends. 214 */ 215 if (!ConfigurationFramework.getInstance().isClient()) { 216 validateClassInterfaces(value, false); 217 } 218 } 219 220 /** 221 * Do some basic checks to make sure the string representation is valid. 222 */ 223 private void validateClassName(String className) { 224 String nvalue = className.trim(); 225 if (!nvalue.matches(CLASS_RE)) { 226 throw PropertyException.illegalPropertyValueException(this, className); 227 } 228 } 229 230 /** 231 * Make sure that named class implements the interfaces named by this 232 * definition. 233 */ 234 private Class<?> validateClassInterfaces(String className, boolean initialize) { 235 Class<?> theClass = loadClassForValidation(className, className, initialize); 236 for (String i : instanceOfInterfaces) { 237 Class<?> instanceOfClass = loadClassForValidation(className, i, initialize); 238 if (!instanceOfClass.isAssignableFrom(theClass)) { 239 throw PropertyException.illegalPropertyValueException(this, className); 240 } 241 } 242 return theClass; 243 } 244 245 private Class<?> loadClassForValidation(String componentClassName, String classToBeLoaded, boolean initialize) { 246 try { 247 return loadClass(classToBeLoaded.trim(), initialize); 248 } catch (ClassNotFoundException | LinkageError e) { 249 // If the class cannot be loaded / initialized then it is an invalid value. 250 throw PropertyException.illegalPropertyValueException(this, componentClassName, e); 251 } 252 } 253}