001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.builder.combined; 018 019import java.lang.reflect.Constructor; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Map; 024 025import org.apache.commons.configuration2.Configuration; 026import org.apache.commons.configuration2.ConfigurationUtils; 027import org.apache.commons.configuration2.builder.BasicConfigurationBuilder; 028import org.apache.commons.configuration2.builder.BuilderParameters; 029import org.apache.commons.configuration2.builder.ConfigurationBuilder; 030import org.apache.commons.configuration2.ex.ConfigurationException; 031 032/** 033 * <p> 034 * A fully-functional, reflection-based implementation of the 035 * {@code ConfigurationBuilderProvider} interface which can deal with the 036 * default tags defining configuration sources. 037 * </p> 038 * <p> 039 * An instance of this class is initialized with the names of the 040 * {@code ConfigurationBuilder} class used by this provider and the concrete 041 * {@code Configuration} class. The {@code ConfigurationBuilder} class must be 042 * derived from {@link BasicConfigurationBuilder}. When asked for the builder 043 * object, an instance of the builder class is created and initialized from the 044 * bean declaration associated with the current configuration source. 045 * </p> 046 * <p> 047 * {@code ConfigurationBuilder} objects are configured using parameter objects. 048 * When declaring configuration sources in XML it should not be necessary to 049 * define the single parameter objects. Rather, simple and complex properties 050 * are set in the typical way of a bean declaration (i.e. as attributes of the 051 * current XML element or as child elements). This class creates all supported 052 * parameter objects (whose names also must be provided at construction time) 053 * and takes care that their properties are initialized according to the current 054 * bean declaration. 055 * </p> 056 * <p> 057 * The use of reflection to create builder instances allows a generic 058 * implementation supporting many concrete builder classes. Another reason for 059 * this approach is that builder classes are only loaded if actually needed. 060 * Some specialized {@code Configuration} implementations require specific 061 * external dependencies which should not be mandatory for the use of 062 * {@code CombinedConfigurationBuilder}. Because such classes are lazily loaded, 063 * an application only has to include the dependencies it actually uses. 064 * </p> 065 * 066 * @version $Id: BaseConfigurationBuilderProvider.java 1842194 2018-09-27 22:24:23Z ggregory $ 067 * @since 2.0 068 */ 069public class BaseConfigurationBuilderProvider implements 070 ConfigurationBuilderProvider 071{ 072 /** The types of the constructor parameters for a basic builder. */ 073 private static final Class<?>[] CTOR_PARAM_TYPES = { 074 Class.class, Map.class, Boolean.TYPE 075 }; 076 077 /** The name of the builder class. */ 078 private final String builderClass; 079 080 /** The name of a builder class with reloading support. */ 081 private final String reloadingBuilderClass; 082 083 /** Stores the name of the configuration class to be created. */ 084 private final String configurationClass; 085 086 /** A collection with the names of parameter classes. */ 087 private final Collection<String> parameterClasses; 088 089 /** 090 * Creates a new instance of {@code BaseConfigurationBuilderProvider} and 091 * initializes all its properties. 092 * 093 * @param bldrCls the name of the builder class (must not be <b>null</b>) 094 * @param reloadBldrCls the name of a builder class to be used if reloading 095 * support is required (<b>null</b> if reloading is not supported) 096 * @param configCls the name of the configuration class (must not be 097 * <b>null</b>) 098 * @param paramCls a collection with the names of parameters classes 099 * @throws IllegalArgumentException if a required parameter is missing 100 */ 101 public BaseConfigurationBuilderProvider(final String bldrCls, 102 final String reloadBldrCls, final String configCls, final Collection<String> paramCls) 103 { 104 if (bldrCls == null) 105 { 106 throw new IllegalArgumentException( 107 "Builder class must not be null!"); 108 } 109 if (configCls == null) 110 { 111 throw new IllegalArgumentException( 112 "Configuration class must not be null!"); 113 } 114 115 builderClass = bldrCls; 116 reloadingBuilderClass = reloadBldrCls; 117 configurationClass = configCls; 118 parameterClasses = initParameterClasses(paramCls); 119 } 120 121 /** 122 * Returns the name of the class of the builder created by this provider. 123 * 124 * @return the builder class 125 */ 126 public String getBuilderClass() 127 { 128 return builderClass; 129 } 130 131 /** 132 * Returns the name of the class of the builder created by this provider if 133 * the reload flag is set. If this method returns <b>null</b>, reloading 134 * builders are not supported by this provider. 135 * 136 * @return the reloading builder class 137 */ 138 public String getReloadingBuilderClass() 139 { 140 return reloadingBuilderClass; 141 } 142 143 /** 144 * Returns the name of the configuration class created by the builder 145 * produced by this provider. 146 * 147 * @return the configuration class 148 */ 149 public String getConfigurationClass() 150 { 151 return configurationClass; 152 } 153 154 /** 155 * Returns an unmodifiable collection with the names of parameter classes 156 * supported by this provider. 157 * 158 * @return the parameter classes 159 */ 160 public Collection<String> getParameterClasses() 161 { 162 return parameterClasses; 163 } 164 165 /** 166 * {@inheritDoc} This implementation delegates to some protected methods to 167 * create a new builder instance using reflection and to configure it with 168 * parameter values defined by the passed in {@code BeanDeclaration}. 169 */ 170 @Override 171 public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder( 172 final ConfigurationDeclaration decl) throws ConfigurationException 173 { 174 try 175 { 176 final Collection<BuilderParameters> params = createParameterObjects(); 177 initializeParameterObjects(decl, params); 178 final BasicConfigurationBuilder<? extends Configuration> builder = 179 createBuilder(decl, params); 180 configureBuilder(builder, decl, params); 181 return builder; 182 } 183 catch (final ConfigurationException cex) 184 { 185 throw cex; 186 } 187 catch (final Exception ex) 188 { 189 throw new ConfigurationException(ex); 190 } 191 } 192 193 /** 194 * Determines the <em>allowFailOnInit</em> flag for the newly created 195 * builder based on the given {@code ConfigurationDeclaration}. Some 196 * combinations of flags in the declaration say that a configuration source 197 * is optional, but an empty instance should be created if its creation 198 * fail. 199 * 200 * @param decl the current {@code ConfigurationDeclaration} 201 * @return the value of the <em>allowFailOnInit</em> flag 202 */ 203 protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl) 204 { 205 return decl.isOptional() && decl.isForceCreate(); 206 } 207 208 /** 209 * Creates a collection of parameter objects to be used for configuring the 210 * builder. This method creates instances of the parameter classes passed to 211 * the constructor. 212 * 213 * @return a collection with parameter objects for the builder 214 * @throws Exception if an error occurs while creating parameter objects via 215 * reflection 216 */ 217 protected Collection<BuilderParameters> createParameterObjects() 218 throws Exception 219 { 220 final Collection<BuilderParameters> params = 221 new ArrayList<>( 222 getParameterClasses().size()); 223 for (final String paramcls : getParameterClasses()) 224 { 225 params.add(createParameterObject(paramcls)); 226 } 227 return params; 228 } 229 230 /** 231 * Initializes the parameter objects with data stored in the current bean 232 * declaration. This method is called before the newly created builder 233 * instance is configured with the parameter objects. It maps attributes of 234 * the bean declaration to properties of parameter objects. In addition, 235 * it invokes the parent {@code CombinedConfigurationBuilder} so that the 236 * parameters object can inherit properties already defined for this 237 * builder. 238 * 239 * @param decl the current {@code ConfigurationDeclaration} 240 * @param params the collection with (uninitialized) parameter objects 241 * @throws Exception if an error occurs 242 */ 243 protected void initializeParameterObjects(final ConfigurationDeclaration decl, 244 final Collection<BuilderParameters> params) throws Exception 245 { 246 inheritParentBuilderProperties(decl, params); 247 final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params); 248 decl.getConfigurationBuilder().initBean(wrapBean, decl); 249 } 250 251 /** 252 * Passes all parameter objects to the parent 253 * {@code CombinedConfigurationBuilder} so that properties already defined 254 * for the parent builder can be added. This method is called before the 255 * parameter objects are initialized from the definition configuration. This 256 * way properties from the parent builder are inherited, but can be 257 * overridden for child configurations. 258 * 259 * @param decl the current {@code ConfigurationDeclaration} 260 * @param params the collection with (uninitialized) parameter objects 261 */ 262 protected void inheritParentBuilderProperties( 263 final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 264 { 265 for (final BuilderParameters p : params) 266 { 267 decl.getConfigurationBuilder().initChildBuilderParameters(p); 268 } 269 } 270 271 /** 272 * Creates a new, uninitialized instance of the builder class managed by 273 * this provider. This implementation determines the builder class to be 274 * used by delegating to {@code determineBuilderClass()}. It then calls the 275 * constructor expecting the configuration class, the map with properties, 276 * and the<em>allowFailOnInit</em> flag. 277 * 278 * @param decl the current {@code ConfigurationDeclaration} 279 * @param params initialization parameters for the new builder object 280 * @return the newly created builder instance 281 * @throws Exception if an error occurs 282 */ 283 protected BasicConfigurationBuilder<? extends Configuration> createBuilder( 284 final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 285 throws Exception 286 { 287 final Class<?> bldCls = 288 ConfigurationUtils.loadClass(determineBuilderClass(decl)); 289 final Class<?> configCls = 290 ConfigurationUtils.loadClass(determineConfigurationClass(decl, 291 params)); 292 final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES); 293 // ? extends Configuration is the minimum constraint 294 @SuppressWarnings("unchecked") 295 final 296 BasicConfigurationBuilder<? extends Configuration> builder = 297 (BasicConfigurationBuilder<? extends Configuration>) ctor 298 .newInstance(configCls, null, isAllowFailOnInit(decl)); 299 return builder; 300 } 301 302 /** 303 * Configures a newly created builder instance with its initialization 304 * parameters. This method is called after a new instance was created using 305 * reflection. This implementation passes the parameter objects to the 306 * builder's {@code configure()} method. 307 * 308 * @param builder the builder to be initialized 309 * @param decl the current {@code ConfigurationDeclaration} 310 * @param params the collection with initialization parameter objects 311 * @throws Exception if an error occurs 312 */ 313 protected void configureBuilder( 314 final BasicConfigurationBuilder<? extends Configuration> builder, 315 final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 316 throws Exception 317 { 318 builder.configure(params.toArray(new BuilderParameters[params.size()])); 319 } 320 321 /** 322 * Determines the name of the class to be used for a new builder instance. 323 * This implementation selects between the normal and the reloading builder 324 * class, based on the passed in {@code ConfigurationDeclaration}. If a 325 * reloading builder is desired, but this provider has no reloading support, 326 * an exception is thrown. 327 * 328 * @param decl the current {@code ConfigurationDeclaration} 329 * @return the name of the builder class 330 * @throws ConfigurationException if the builder class cannot be determined 331 */ 332 protected String determineBuilderClass(final ConfigurationDeclaration decl) 333 throws ConfigurationException 334 { 335 if (decl.isReload()) 336 { 337 if (getReloadingBuilderClass() == null) 338 { 339 throw new ConfigurationException( 340 "No support for reloading for builder class " 341 + getBuilderClass()); 342 } 343 return getReloadingBuilderClass(); 344 } 345 return getBuilderClass(); 346 } 347 348 /** 349 * Determines the name of the configuration class produced by the builder. 350 * This method is called when obtaining the arguments for invoking the 351 * constructor of the builder class. This implementation just returns the 352 * pre-configured configuration class name. Derived classes may determine 353 * this class name dynamically based on the passed in parameters. 354 * 355 * @param decl the current {@code ConfigurationDeclaration} 356 * @param params the collection with parameter objects 357 * @return the name of the builder's result configuration class 358 * @throws ConfigurationException if an error occurs 359 */ 360 protected String determineConfigurationClass(final ConfigurationDeclaration decl, 361 final Collection<BuilderParameters> params) throws ConfigurationException 362 { 363 return getConfigurationClass(); 364 } 365 366 /** 367 * Creates an instance of a parameter class using reflection. 368 * 369 * @param paramcls the parameter class 370 * @return the newly created instance 371 * @throws Exception if an error occurs 372 */ 373 private static BuilderParameters createParameterObject(final String paramcls) 374 throws Exception 375 { 376 final Class<?> cls = ConfigurationUtils.loadClass(paramcls); 377 final BuilderParameters p = (BuilderParameters) cls.newInstance(); 378 return p; 379 } 380 381 /** 382 * Creates a new, unmodifiable collection for the parameter classes. 383 * 384 * @param paramCls the collection with parameter classes passed to the 385 * constructor 386 * @return the collection to be stored 387 */ 388 private static Collection<String> initParameterClasses( 389 final Collection<String> paramCls) 390 { 391 if (paramCls == null) 392 { 393 return Collections.emptySet(); 394 } 395 return Collections.unmodifiableCollection(new ArrayList<>( 396 paramCls)); 397 } 398}