1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.configuration2.builder.combined; 18 19 import java.lang.reflect.Constructor; 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.Map; 24 25 import org.apache.commons.configuration2.Configuration; 26 import org.apache.commons.configuration2.ConfigurationUtils; 27 import org.apache.commons.configuration2.builder.BasicConfigurationBuilder; 28 import org.apache.commons.configuration2.builder.BuilderParameters; 29 import org.apache.commons.configuration2.builder.ConfigurationBuilder; 30 import org.apache.commons.configuration2.ex.ConfigurationException; 31 32 /** 33 * <p> 34 * A fully-functional, reflection-based implementation of the {@code ConfigurationBuilderProvider} interface which can 35 * deal with the default tags defining configuration sources. 36 * </p> 37 * <p> 38 * An instance of this class is initialized with the names of the {@code ConfigurationBuilder} class used by this 39 * provider and the concrete {@code Configuration} class. The {@code ConfigurationBuilder} class must be derived from 40 * {@link BasicConfigurationBuilder}. When asked for the builder object, an instance of the builder class is created and 41 * initialized from the bean declaration associated with the current configuration source. 42 * </p> 43 * <p> 44 * {@code ConfigurationBuilder} objects are configured using parameter objects. When declaring configuration sources in 45 * XML it should not be necessary to define the single parameter objects. Rather, simple and complex properties are set 46 * in the typical way of a bean declaration (i.e. as attributes of the current XML element or as child elements). This 47 * class creates all supported parameter objects (whose names also must be provided at construction time) and takes care 48 * that their properties are initialized according to the current bean declaration. 49 * </p> 50 * <p> 51 * The use of reflection to create builder instances allows a generic implementation supporting many concrete builder 52 * classes. Another reason for this approach is that builder classes are only loaded if actually needed. Some 53 * specialized {@code Configuration} implementations require specific external dependencies which should not be 54 * mandatory for the use of {@code CombinedConfigurationBuilder}. Because such classes are lazily loaded, an application 55 * only has to include the dependencies it actually uses. 56 * </p> 57 * 58 * @since 2.0 59 */ 60 public class BaseConfigurationBuilderProvider implements ConfigurationBuilderProvider { 61 /** The types of the constructor parameters for a basic builder. */ 62 private static final Class<?>[] CTOR_PARAM_TYPES = {Class.class, Map.class, Boolean.TYPE}; 63 64 /** The name of the builder class. */ 65 private final String builderClass; 66 67 /** The name of a builder class with reloading support. */ 68 private final String reloadingBuilderClass; 69 70 /** Stores the name of the configuration class to be created. */ 71 private final String configurationClass; 72 73 /** A collection with the names of parameter classes. */ 74 private final Collection<String> parameterClasses; 75 76 /** 77 * Creates a new instance of {@code BaseConfigurationBuilderProvider} and initializes all its properties. 78 * 79 * @param bldrCls the name of the builder class (must not be <b>null</b>) 80 * @param reloadBldrCls the name of a builder class to be used if reloading support is required (<b>null</b> if 81 * reloading is not supported) 82 * @param configCls the name of the configuration class (must not be <b>null</b>) 83 * @param paramCls a collection with the names of parameters classes 84 * @throws IllegalArgumentException if a required parameter is missing 85 */ 86 public BaseConfigurationBuilderProvider(final String bldrCls, final String reloadBldrCls, final String configCls, final Collection<String> paramCls) { 87 if (bldrCls == null) { 88 throw new IllegalArgumentException("Builder class must not be null!"); 89 } 90 if (configCls == null) { 91 throw new IllegalArgumentException("Configuration class must not be null!"); 92 } 93 94 builderClass = bldrCls; 95 reloadingBuilderClass = reloadBldrCls; 96 configurationClass = configCls; 97 parameterClasses = initParameterClasses(paramCls); 98 } 99 100 /** 101 * Gets the name of the class of the builder created by this provider. 102 * 103 * @return the builder class 104 */ 105 public String getBuilderClass() { 106 return builderClass; 107 } 108 109 /** 110 * Gets the name of the class of the builder created by this provider if the reload flag is set. If this method 111 * returns <b>null</b>, reloading builders are not supported by this provider. 112 * 113 * @return the reloading builder class 114 */ 115 public String getReloadingBuilderClass() { 116 return reloadingBuilderClass; 117 } 118 119 /** 120 * Gets the name of the configuration class created by the builder produced by this provider. 121 * 122 * @return the configuration class 123 */ 124 public String getConfigurationClass() { 125 return configurationClass; 126 } 127 128 /** 129 * Gets an unmodifiable collection with the names of parameter classes supported by this provider. 130 * 131 * @return the parameter classes 132 */ 133 public Collection<String> getParameterClasses() { 134 return parameterClasses; 135 } 136 137 /** 138 * {@inheritDoc} This implementation delegates to some protected methods to create a new builder instance using 139 * reflection and to configure it with parameter values defined by the passed in {@code BeanDeclaration}. 140 */ 141 @Override 142 public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException { 143 try { 144 final Collection<BuilderParameters> params = createParameterObjects(); 145 initializeParameterObjects(decl, params); 146 final BasicConfigurationBuilder<? extends Configuration> builder = createBuilder(decl, params); 147 configureBuilder(builder, decl, params); 148 return builder; 149 } catch (final ConfigurationException cex) { 150 throw cex; 151 } catch (final Exception ex) { 152 throw new ConfigurationException(ex); 153 } 154 } 155 156 /** 157 * Determines the <em>allowFailOnInit</em> flag for the newly created builder based on the given 158 * {@code ConfigurationDeclaration}. Some combinations of flags in the declaration say that a configuration source is 159 * optional, but an empty instance should be created if its creation fail. 160 * 161 * @param decl the current {@code ConfigurationDeclaration} 162 * @return the value of the <em>allowFailOnInit</em> flag 163 */ 164 protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl) { 165 return decl.isOptional() && decl.isForceCreate(); 166 } 167 168 /** 169 * Creates a collection of parameter objects to be used for configuring the builder. This method creates instances of 170 * the parameter classes passed to the constructor. 171 * 172 * @return a collection with parameter objects for the builder 173 * @throws Exception if an error occurs while creating parameter objects via reflection 174 */ 175 protected Collection<BuilderParameters> createParameterObjects() throws Exception { 176 final Collection<BuilderParameters> params = new ArrayList<>(getParameterClasses().size()); 177 for (final String paramcls : getParameterClasses()) { 178 params.add(createParameterObject(paramcls)); 179 } 180 return params; 181 } 182 183 /** 184 * Initializes the parameter objects with data stored in the current bean declaration. This method is called before the 185 * newly created builder instance is configured with the parameter objects. It maps attributes of the bean declaration 186 * to properties of parameter objects. In addition, it invokes the parent {@code CombinedConfigurationBuilder} so that 187 * the parameters object can inherit properties already defined for this builder. 188 * 189 * @param decl the current {@code ConfigurationDeclaration} 190 * @param params the collection with (uninitialized) parameter objects 191 * @throws Exception if an error occurs 192 */ 193 protected void initializeParameterObjects(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws Exception { 194 inheritParentBuilderProperties(decl, params); 195 final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params); 196 decl.getConfigurationBuilder().initBean(wrapBean, decl); 197 } 198 199 /** 200 * Passes all parameter objects to the parent {@code CombinedConfigurationBuilder} so that properties already defined 201 * for the parent builder can be added. This method is called before the parameter objects are initialized from the 202 * definition configuration. This way properties from the parent builder are inherited, but can be overridden for child 203 * configurations. 204 * 205 * @param decl the current {@code ConfigurationDeclaration} 206 * @param params the collection with (uninitialized) parameter objects 207 */ 208 protected void inheritParentBuilderProperties(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) { 209 params.forEach(p -> decl.getConfigurationBuilder().initChildBuilderParameters(p)); 210 } 211 212 /** 213 * Creates a new, uninitialized instance of the builder class managed by this provider. This implementation determines 214 * the builder class to be used by delegating to {@code determineBuilderClass()}. It then calls the constructor 215 * expecting the configuration class, the map with properties, and the<em>allowFailOnInit</em> flag. 216 * 217 * @param decl the current {@code ConfigurationDeclaration} 218 * @param params initialization parameters for the new builder object 219 * @return the newly created builder instance 220 * @throws Exception if an error occurs 221 */ 222 protected BasicConfigurationBuilder<? extends Configuration> createBuilder(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 223 throws Exception { 224 final Class<?> bldCls = ConfigurationUtils.loadClass(determineBuilderClass(decl)); 225 final Class<?> configCls = ConfigurationUtils.loadClass(determineConfigurationClass(decl, params)); 226 final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES); 227 // ? extends Configuration is the minimum constraint 228 @SuppressWarnings("unchecked") 229 final BasicConfigurationBuilder<? extends Configuration> builder = (BasicConfigurationBuilder<? extends Configuration>) ctor.newInstance(configCls, 230 null, isAllowFailOnInit(decl)); 231 return builder; 232 } 233 234 /** 235 * Configures a newly created builder instance with its initialization parameters. This method is called after a new 236 * instance was created using reflection. This implementation passes the parameter objects to the builder's 237 * {@code configure()} method. 238 * 239 * @param builder the builder to be initialized 240 * @param decl the current {@code ConfigurationDeclaration} 241 * @param params the collection with initialization parameter objects 242 * @throws Exception if an error occurs 243 */ 244 protected void configureBuilder(final BasicConfigurationBuilder<? extends Configuration> builder, final ConfigurationDeclaration decl, 245 final Collection<BuilderParameters> params) throws Exception { 246 builder.configure(params.toArray(new BuilderParameters[params.size()])); 247 } 248 249 /** 250 * Determines the name of the class to be used for a new builder instance. This implementation selects between the 251 * normal and the reloading builder class, based on the passed in {@code ConfigurationDeclaration}. If a reloading 252 * builder is desired, but this provider has no reloading support, an exception is thrown. 253 * 254 * @param decl the current {@code ConfigurationDeclaration} 255 * @return the name of the builder class 256 * @throws ConfigurationException if the builder class cannot be determined 257 */ 258 protected String determineBuilderClass(final ConfigurationDeclaration decl) throws ConfigurationException { 259 if (decl.isReload()) { 260 if (getReloadingBuilderClass() == null) { 261 throw new ConfigurationException("No support for reloading for builder class " + getBuilderClass()); 262 } 263 return getReloadingBuilderClass(); 264 } 265 return getBuilderClass(); 266 } 267 268 /** 269 * Determines the name of the configuration class produced by the builder. This method is called when obtaining the 270 * arguments for invoking the constructor of the builder class. This implementation just returns the pre-configured 271 * configuration class name. Derived classes may determine this class name dynamically based on the passed in 272 * parameters. 273 * 274 * @param decl the current {@code ConfigurationDeclaration} 275 * @param params the collection with parameter objects 276 * @return the name of the builder's result configuration class 277 * @throws ConfigurationException if an error occurs 278 */ 279 protected String determineConfigurationClass(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 280 throws ConfigurationException { 281 return getConfigurationClass(); 282 } 283 284 /** 285 * Creates an instance of a parameter class using reflection. 286 * 287 * @param paramcls the parameter class 288 * @return the newly created instance 289 * @throws Exception if an error occurs 290 */ 291 private static BuilderParameters createParameterObject(final String paramcls) throws ReflectiveOperationException { 292 return (BuilderParameters) ConfigurationUtils.loadClass(paramcls).getConstructor().newInstance(); 293 } 294 295 /** 296 * Creates a new, unmodifiable collection for the parameter classes. 297 * 298 * @param paramCls the collection with parameter classes passed to the constructor 299 * @return the collection to be stored 300 */ 301 private static Collection<String> initParameterClasses(final Collection<String> paramCls) { 302 if (paramCls == null) { 303 return Collections.emptySet(); 304 } 305 return Collections.unmodifiableCollection(new ArrayList<>(paramCls)); 306 } 307 }