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 * @since 2.0 067 */ 068public class BaseConfigurationBuilderProvider implements 069 ConfigurationBuilderProvider 070{ 071 /** The types of the constructor parameters for a basic builder. */ 072 private static final Class<?>[] CTOR_PARAM_TYPES = { 073 Class.class, Map.class, Boolean.TYPE 074 }; 075 076 /** The name of the builder class. */ 077 private final String builderClass; 078 079 /** The name of a builder class with reloading support. */ 080 private final String reloadingBuilderClass; 081 082 /** Stores the name of the configuration class to be created. */ 083 private final String configurationClass; 084 085 /** A collection with the names of parameter classes. */ 086 private final Collection<String> parameterClasses; 087 088 /** 089 * Creates a new instance of {@code BaseConfigurationBuilderProvider} and 090 * initializes all its properties. 091 * 092 * @param bldrCls the name of the builder class (must not be <b>null</b>) 093 * @param reloadBldrCls the name of a builder class to be used if reloading 094 * support is required (<b>null</b> if reloading is not supported) 095 * @param configCls the name of the configuration class (must not be 096 * <b>null</b>) 097 * @param paramCls a collection with the names of parameters classes 098 * @throws IllegalArgumentException if a required parameter is missing 099 */ 100 public BaseConfigurationBuilderProvider(final String bldrCls, 101 final String reloadBldrCls, final String configCls, final Collection<String> paramCls) 102 { 103 if (bldrCls == null) 104 { 105 throw new IllegalArgumentException( 106 "Builder class must not be null!"); 107 } 108 if (configCls == null) 109 { 110 throw new IllegalArgumentException( 111 "Configuration class must not be null!"); 112 } 113 114 builderClass = bldrCls; 115 reloadingBuilderClass = reloadBldrCls; 116 configurationClass = configCls; 117 parameterClasses = initParameterClasses(paramCls); 118 } 119 120 /** 121 * Returns the name of the class of the builder created by this provider. 122 * 123 * @return the builder class 124 */ 125 public String getBuilderClass() 126 { 127 return builderClass; 128 } 129 130 /** 131 * Returns the name of the class of the builder created by this provider if 132 * the reload flag is set. If this method returns <b>null</b>, reloading 133 * builders are not supported by this provider. 134 * 135 * @return the reloading builder class 136 */ 137 public String getReloadingBuilderClass() 138 { 139 return reloadingBuilderClass; 140 } 141 142 /** 143 * Returns the name of the configuration class created by the builder 144 * produced by this provider. 145 * 146 * @return the configuration class 147 */ 148 public String getConfigurationClass() 149 { 150 return configurationClass; 151 } 152 153 /** 154 * Returns an unmodifiable collection with the names of parameter classes 155 * supported by this provider. 156 * 157 * @return the parameter classes 158 */ 159 public Collection<String> getParameterClasses() 160 { 161 return parameterClasses; 162 } 163 164 /** 165 * {@inheritDoc} This implementation delegates to some protected methods to 166 * create a new builder instance using reflection and to configure it with 167 * parameter values defined by the passed in {@code BeanDeclaration}. 168 */ 169 @Override 170 public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder( 171 final ConfigurationDeclaration decl) throws ConfigurationException 172 { 173 try 174 { 175 final Collection<BuilderParameters> params = createParameterObjects(); 176 initializeParameterObjects(decl, params); 177 final BasicConfigurationBuilder<? extends Configuration> builder = 178 createBuilder(decl, params); 179 configureBuilder(builder, decl, params); 180 return builder; 181 } 182 catch (final ConfigurationException cex) 183 { 184 throw cex; 185 } 186 catch (final Exception ex) 187 { 188 throw new ConfigurationException(ex); 189 } 190 } 191 192 /** 193 * Determines the <em>allowFailOnInit</em> flag for the newly created 194 * builder based on the given {@code ConfigurationDeclaration}. Some 195 * combinations of flags in the declaration say that a configuration source 196 * is optional, but an empty instance should be created if its creation 197 * fail. 198 * 199 * @param decl the current {@code ConfigurationDeclaration} 200 * @return the value of the <em>allowFailOnInit</em> flag 201 */ 202 protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl) 203 { 204 return decl.isOptional() && decl.isForceCreate(); 205 } 206 207 /** 208 * Creates a collection of parameter objects to be used for configuring the 209 * builder. This method creates instances of the parameter classes passed to 210 * the constructor. 211 * 212 * @return a collection with parameter objects for the builder 213 * @throws Exception if an error occurs while creating parameter objects via 214 * reflection 215 */ 216 protected Collection<BuilderParameters> createParameterObjects() 217 throws Exception 218 { 219 final Collection<BuilderParameters> params = 220 new ArrayList<>( 221 getParameterClasses().size()); 222 for (final String paramcls : getParameterClasses()) 223 { 224 params.add(createParameterObject(paramcls)); 225 } 226 return params; 227 } 228 229 /** 230 * Initializes the parameter objects with data stored in the current bean 231 * declaration. This method is called before the newly created builder 232 * instance is configured with the parameter objects. It maps attributes of 233 * the bean declaration to properties of parameter objects. In addition, 234 * it invokes the parent {@code CombinedConfigurationBuilder} so that the 235 * parameters object can inherit properties already defined for this 236 * builder. 237 * 238 * @param decl the current {@code ConfigurationDeclaration} 239 * @param params the collection with (uninitialized) parameter objects 240 * @throws Exception if an error occurs 241 */ 242 protected void initializeParameterObjects(final ConfigurationDeclaration decl, 243 final Collection<BuilderParameters> params) throws Exception 244 { 245 inheritParentBuilderProperties(decl, params); 246 final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params); 247 decl.getConfigurationBuilder().initBean(wrapBean, decl); 248 } 249 250 /** 251 * Passes all parameter objects to the parent 252 * {@code CombinedConfigurationBuilder} so that properties already defined 253 * for the parent builder can be added. This method is called before the 254 * parameter objects are initialized from the definition configuration. This 255 * way properties from the parent builder are inherited, but can be 256 * overridden for child configurations. 257 * 258 * @param decl the current {@code ConfigurationDeclaration} 259 * @param params the collection with (uninitialized) parameter objects 260 */ 261 protected void inheritParentBuilderProperties( 262 final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 263 { 264 for (final BuilderParameters p : params) 265 { 266 decl.getConfigurationBuilder().initChildBuilderParameters(p); 267 } 268 } 269 270 /** 271 * Creates a new, uninitialized instance of the builder class managed by 272 * this provider. This implementation determines the builder class to be 273 * used by delegating to {@code determineBuilderClass()}. It then calls the 274 * constructor expecting the configuration class, the map with properties, 275 * and the<em>allowFailOnInit</em> flag. 276 * 277 * @param decl the current {@code ConfigurationDeclaration} 278 * @param params initialization parameters for the new builder object 279 * @return the newly created builder instance 280 * @throws Exception if an error occurs 281 */ 282 protected BasicConfigurationBuilder<? extends Configuration> createBuilder( 283 final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 284 throws Exception 285 { 286 final Class<?> bldCls = 287 ConfigurationUtils.loadClass(determineBuilderClass(decl)); 288 final Class<?> configCls = 289 ConfigurationUtils.loadClass(determineConfigurationClass(decl, 290 params)); 291 final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES); 292 // ? extends Configuration is the minimum constraint 293 @SuppressWarnings("unchecked") 294 final 295 BasicConfigurationBuilder<? extends Configuration> builder = 296 (BasicConfigurationBuilder<? extends Configuration>) ctor 297 .newInstance(configCls, null, isAllowFailOnInit(decl)); 298 return builder; 299 } 300 301 /** 302 * Configures a newly created builder instance with its initialization 303 * parameters. This method is called after a new instance was created using 304 * reflection. This implementation passes the parameter objects to the 305 * builder's {@code configure()} method. 306 * 307 * @param builder the builder to be initialized 308 * @param decl the current {@code ConfigurationDeclaration} 309 * @param params the collection with initialization parameter objects 310 * @throws Exception if an error occurs 311 */ 312 protected void configureBuilder( 313 final BasicConfigurationBuilder<? extends Configuration> builder, 314 final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 315 throws Exception 316 { 317 builder.configure(params.toArray(new BuilderParameters[params.size()])); 318 } 319 320 /** 321 * Determines the name of the class to be used for a new builder instance. 322 * This implementation selects between the normal and the reloading builder 323 * class, based on the passed in {@code ConfigurationDeclaration}. If a 324 * reloading builder is desired, but this provider has no reloading support, 325 * an exception is thrown. 326 * 327 * @param decl the current {@code ConfigurationDeclaration} 328 * @return the name of the builder class 329 * @throws ConfigurationException if the builder class cannot be determined 330 */ 331 protected String determineBuilderClass(final ConfigurationDeclaration decl) 332 throws ConfigurationException 333 { 334 if (decl.isReload()) 335 { 336 if (getReloadingBuilderClass() == null) 337 { 338 throw new ConfigurationException( 339 "No support for reloading for builder class " 340 + getBuilderClass()); 341 } 342 return getReloadingBuilderClass(); 343 } 344 return getBuilderClass(); 345 } 346 347 /** 348 * Determines the name of the configuration class produced by the builder. 349 * This method is called when obtaining the arguments for invoking the 350 * constructor of the builder class. This implementation just returns the 351 * pre-configured configuration class name. Derived classes may determine 352 * this class name dynamically based on the passed in parameters. 353 * 354 * @param decl the current {@code ConfigurationDeclaration} 355 * @param params the collection with parameter objects 356 * @return the name of the builder's result configuration class 357 * @throws ConfigurationException if an error occurs 358 */ 359 protected String determineConfigurationClass(final ConfigurationDeclaration decl, 360 final Collection<BuilderParameters> params) throws ConfigurationException 361 { 362 return getConfigurationClass(); 363 } 364 365 /** 366 * Creates an instance of a parameter class using reflection. 367 * 368 * @param paramcls the parameter class 369 * @return the newly created instance 370 * @throws Exception if an error occurs 371 */ 372 private static BuilderParameters createParameterObject(final String paramcls) 373 throws Exception 374 { 375 final Class<?> cls = ConfigurationUtils.loadClass(paramcls); 376 final BuilderParameters p = (BuilderParameters) cls.newInstance(); 377 return p; 378 } 379 380 /** 381 * Creates a new, unmodifiable collection for the parameter classes. 382 * 383 * @param paramCls the collection with parameter classes passed to the 384 * constructor 385 * @return the collection to be stored 386 */ 387 private static Collection<String> initParameterClasses( 388 final Collection<String> paramCls) 389 { 390 if (paramCls == null) 391 { 392 return Collections.emptySet(); 393 } 394 return Collections.unmodifiableCollection(new ArrayList<>( 395 paramCls)); 396 } 397}