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-2016 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 /* Do some basic checks to make sure the string representation is valid. */ 062 String value = className.trim(); 063 if (!value.matches(CLASS_RE)) { 064 throw new IllegalArgumentException("\"" + value + "\" is not a valid Java class name"); 065 } 066 067 instanceOfInterfaces.add(value); 068 } 069 070 @Override 071 protected ClassPropertyDefinition buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName, 072 EnumSet<PropertyOption> options, AdministratorAction adminAction, 073 DefaultBehaviorProvider<String> defaultBehavior) { 074 return new ClassPropertyDefinition(d, propertyName, options, adminAction, defaultBehavior, 075 instanceOfInterfaces); 076 } 077 078 } 079 080 /** Regular expression for validating class names. */ 081 private static final String CLASS_RE = "^([A-Za-z][A-Za-z0-9_]*\\.)*[A-Za-z][A-Za-z0-9_]*(\\$[A-Za-z0-9_]+)*$"; 082 083 /** 084 * Create a class property definition builder. 085 * 086 * @param d 087 * The managed object definition associated with this property 088 * definition. 089 * @param propertyName 090 * The property name. 091 * @return Returns the new class property definition builder. 092 */ 093 public static Builder createBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) { 094 return new Builder(d, propertyName); 095 } 096 097 /** Load a named class. */ 098 private static Class<?> loadClass(String className, boolean initialize) throws ClassNotFoundException { 099 return Class.forName(className, initialize, ConfigurationFramework.getInstance().getClassLoader()); 100 } 101 102 /** List of interfaces which property values must implement. */ 103 private final List<String> instanceOfInterfaces; 104 105 /** Private constructor. */ 106 private ClassPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName, 107 EnumSet<PropertyOption> options, AdministratorAction adminAction, 108 DefaultBehaviorProvider<String> defaultBehavior, List<String> instanceOfInterfaces) { 109 super(d, String.class, propertyName, options, adminAction, defaultBehavior); 110 111 this.instanceOfInterfaces = Collections.unmodifiableList(new LinkedList<String>(instanceOfInterfaces)); 112 } 113 114 @Override 115 public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) { 116 return v.visitClass(this, p); 117 } 118 119 @Override 120 public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) { 121 return v.visitClass(this, value, p); 122 } 123 124 @Override 125 public String decodeValue(String value) { 126 Reject.ifNull(value); 127 128 try { 129 validateValue(value); 130 } catch (PropertyException e) { 131 throw PropertyException.illegalPropertyValueException(this, value, e.getCause()); 132 } 133 134 return value; 135 } 136 137 /** 138 * Get an unmodifiable list of classes which values of this property must 139 * implement. 140 * 141 * @return Returns an unmodifiable list of classes which values of this 142 * property must implement. 143 */ 144 public List<String> getInstanceOfInterface() { 145 return instanceOfInterfaces; 146 } 147 148 /** 149 * Validate and load the named class, and cast it to a subclass of the 150 * specified class. 151 * 152 * @param <T> 153 * The requested type. 154 * @param className 155 * The name of the class to validate and load. 156 * @param instanceOf 157 * The class representing the requested type. 158 * @return Returns the named class cast to a subclass of the specified 159 * class. 160 * @throws PropertyException 161 * If the named class was invalid, could not be loaded, or did 162 * not implement the required interfaces. 163 * @throws ClassCastException 164 * If the referenced class does not implement the requested 165 * type. 166 */ 167 public <T> Class<? extends T> loadClass(String className, Class<T> instanceOf) { 168 Reject.ifNull(className, instanceOf); 169 170 // Make sure that the named class is valid. 171 validateClassName(className); 172 Class<?> theClass = validateClassInterfaces(className, true); 173 174 // Cast it to the required type. 175 return theClass.asSubclass(instanceOf); 176 } 177 178 @Override 179 public String normalizeValue(String value) { 180 Reject.ifNull(value); 181 182 return value.trim(); 183 } 184 185 @Override 186 public void validateValue(String value) { 187 Reject.ifNull(value); 188 189 // Always make sure the name is a valid class name. 190 validateClassName(value); 191 192 /* 193 * If additional validation is enabled then attempt to load the class 194 * and check the interfaces that it implements/extends. 195 */ 196 if (!ConfigurationFramework.getInstance().isClient()) { 197 validateClassInterfaces(value, false); 198 } 199 } 200 201 /** Do some basic checks to make sure the string representation is valid. */ 202 private void validateClassName(String className) { 203 String nvalue = className.trim(); 204 if (!nvalue.matches(CLASS_RE)) { 205 throw PropertyException.illegalPropertyValueException(this, className); 206 } 207 } 208 209 /** Make sure that named class implements the interfaces named by this definition. */ 210 private Class<?> validateClassInterfaces(String className, boolean initialize) { 211 Class<?> theClass = loadClassForValidation(className, className, initialize); 212 for (String i : instanceOfInterfaces) { 213 Class<?> instanceOfClass = loadClassForValidation(className, i, initialize); 214 if (!instanceOfClass.isAssignableFrom(theClass)) { 215 throw PropertyException.illegalPropertyValueException(this, className); 216 } 217 } 218 return theClass; 219 } 220 221 private Class<?> loadClassForValidation(String componentClassName, String classToBeLoaded, boolean initialize) { 222 try { 223 return loadClass(classToBeLoaded.trim(), initialize); 224 } catch (ClassNotFoundException | LinkageError e) { 225 // If the class cannot be loaded / initialized then it is an invalid value. 226 throw PropertyException.illegalPropertyValueException(this, componentClassName, e); 227 } 228 } 229}