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