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 * https://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 {@code ConfigurationBuilderProvider} interface which can 035 * deal with the default tags defining configuration sources. 036 * </p> 037 * <p> 038 * An instance of this class is initialized with the names of the {@code ConfigurationBuilder} class used by this 039 * provider and the concrete {@code Configuration} class. The {@code ConfigurationBuilder} class must be derived from 040 * {@link BasicConfigurationBuilder}. When asked for the builder object, an instance of the builder class is created and 041 * initialized from the bean declaration associated with the current configuration source. 042 * </p> 043 * <p> 044 * {@code ConfigurationBuilder} objects are configured using parameter objects. When declaring configuration sources in 045 * XML it should not be necessary to define the single parameter objects. Rather, simple and complex properties are set 046 * in the typical way of a bean declaration (i.e. as attributes of the current XML element or as child elements). This 047 * class creates all supported parameter objects (whose names also must be provided at construction time) and takes care 048 * that their properties are initialized according to the current bean declaration. 049 * </p> 050 * <p> 051 * The use of reflection to create builder instances allows a generic implementation supporting many concrete builder 052 * classes. Another reason for this approach is that builder classes are only loaded if actually needed. Some 053 * specialized {@code Configuration} implementations require specific external dependencies which should not be 054 * mandatory for the use of {@code CombinedConfigurationBuilder}. Because such classes are lazily loaded, an application 055 * only has to include the dependencies it actually uses. 056 * </p> 057 * 058 * @since 2.0 059 */ 060public class BaseConfigurationBuilderProvider implements ConfigurationBuilderProvider { 061 062 /** The types of the constructor parameters for a basic builder. */ 063 private static final Class<?>[] CTOR_PARAM_TYPES = {Class.class, Map.class, Boolean.TYPE}; 064 065 /** 066 * Creates an instance of a parameter class using reflection. 067 * 068 * @param paramcls the parameter class 069 * @return the newly created instance 070 * @throws Exception if an error occurs 071 */ 072 private static BuilderParameters createParameterObject(final String paramcls) throws ReflectiveOperationException { 073 return (BuilderParameters) ConfigurationUtils.loadClass(paramcls).getConstructor().newInstance(); 074 } 075 076 /** 077 * Creates a new, unmodifiable collection for the parameter classes. 078 * 079 * @param paramCls the collection with parameter classes passed to the constructor 080 * @return the collection to be stored 081 */ 082 private static Collection<String> initParameterClasses(final Collection<String> paramCls) { 083 if (paramCls == null) { 084 return Collections.emptySet(); 085 } 086 return Collections.unmodifiableCollection(new ArrayList<>(paramCls)); 087 } 088 089 /** The name of the builder class. */ 090 private final String builderClass; 091 092 /** The name of a builder class with reloading support. */ 093 private final String reloadingBuilderClass; 094 095 /** Stores the name of the configuration class to be created. */ 096 private final String configurationClass; 097 098 /** A collection with the names of parameter classes. */ 099 private final Collection<String> parameterClasses; 100 101 /** 102 * Creates a new instance of {@code BaseConfigurationBuilderProvider} and initializes all its properties. 103 * 104 * @param bldrCls the name of the builder class (must not be <strong>null</strong>) 105 * @param reloadBldrCls the name of a builder class to be used if reloading support is required (<strong>null</strong> if 106 * reloading is not supported) 107 * @param configCls the name of the configuration class (must not be <strong>null</strong>) 108 * @param paramCls a collection with the names of parameters classes 109 * @throws IllegalArgumentException if a required parameter is missing 110 */ 111 public BaseConfigurationBuilderProvider(final String bldrCls, final String reloadBldrCls, final String configCls, final Collection<String> paramCls) { 112 if (bldrCls == null) { 113 throw new IllegalArgumentException("Builder class must not be null!"); 114 } 115 if (configCls == null) { 116 throw new IllegalArgumentException("Configuration class must not be null!"); 117 } 118 119 builderClass = bldrCls; 120 reloadingBuilderClass = reloadBldrCls; 121 configurationClass = configCls; 122 parameterClasses = initParameterClasses(paramCls); 123 } 124 125 /** 126 * Configures a newly created builder instance with its initialization parameters. This method is called after a new 127 * instance was created using reflection. This implementation passes the parameter objects to the builder's 128 * {@code configure()} method. 129 * 130 * @param builder the builder to be initialized 131 * @param decl the current {@code ConfigurationDeclaration} 132 * @param params the collection with initialization parameter objects 133 * @throws Exception if an error occurs 134 */ 135 protected void configureBuilder(final BasicConfigurationBuilder<? extends Configuration> builder, final ConfigurationDeclaration decl, 136 final Collection<BuilderParameters> params) throws Exception { 137 builder.configure(params.toArray(new BuilderParameters[params.size()])); 138 } 139 140 /** 141 * Creates a new, uninitialized instance of the builder class managed by this provider. This implementation determines 142 * the builder class to be used by delegating to {@code determineBuilderClass()}. It then calls the constructor 143 * expecting the configuration class, the map with properties, and the<em>allowFailOnInit</em> flag. 144 * 145 * @param decl the current {@code ConfigurationDeclaration} 146 * @param params initialization parameters for the new builder object 147 * @return the newly created builder instance 148 * @throws Exception if an error occurs 149 */ 150 protected BasicConfigurationBuilder<? extends Configuration> createBuilder(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 151 throws Exception { 152 final Class<?> bldCls = ConfigurationUtils.loadClass(determineBuilderClass(decl)); 153 final Class<?> configCls = ConfigurationUtils.loadClass(determineConfigurationClass(decl, params)); 154 final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES); 155 // ? extends Configuration is the minimum constraint 156 @SuppressWarnings("unchecked") 157 final BasicConfigurationBuilder<? extends Configuration> builder = (BasicConfigurationBuilder<? extends Configuration>) ctor.newInstance(configCls, 158 null, isAllowFailOnInit(decl)); 159 return builder; 160 } 161 162 /** 163 * Creates a collection of parameter objects to be used for configuring the builder. This method creates instances of 164 * the parameter classes passed to the constructor. 165 * 166 * @return a collection with parameter objects for the builder 167 * @throws Exception if an error occurs while creating parameter objects via reflection 168 */ 169 protected Collection<BuilderParameters> createParameterObjects() throws Exception { 170 final Collection<BuilderParameters> params = new ArrayList<>(getParameterClasses().size()); 171 for (final String paramcls : getParameterClasses()) { 172 params.add(createParameterObject(paramcls)); 173 } 174 return params; 175 } 176 177 /** 178 * Determines the name of the class to be used for a new builder instance. This implementation selects between the 179 * normal and the reloading builder class, based on the passed in {@code ConfigurationDeclaration}. If a reloading 180 * builder is desired, but this provider has no reloading support, an exception is thrown. 181 * 182 * @param decl the current {@code ConfigurationDeclaration} 183 * @return the name of the builder class 184 * @throws ConfigurationException if the builder class cannot be determined 185 */ 186 protected String determineBuilderClass(final ConfigurationDeclaration decl) throws ConfigurationException { 187 if (decl.isReload()) { 188 if (getReloadingBuilderClass() == null) { 189 throw new ConfigurationException("No support for reloading for builder class %s", getBuilderClass()); 190 } 191 return getReloadingBuilderClass(); 192 } 193 return getBuilderClass(); 194 } 195 196 /** 197 * Determines the name of the configuration class produced by the builder. This method is called when obtaining the 198 * arguments for invoking the constructor of the builder class. This implementation just returns the pre-configured 199 * configuration class name. Derived classes may determine this class name dynamically based on the passed in 200 * parameters. 201 * 202 * @param decl the current {@code ConfigurationDeclaration} 203 * @param params the collection with parameter objects 204 * @return the name of the builder's result configuration class 205 * @throws ConfigurationException if an error occurs 206 */ 207 protected String determineConfigurationClass(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) 208 throws ConfigurationException { 209 return getConfigurationClass(); 210 } 211 212 /** 213 * Gets the name of the class of the builder created by this provider. 214 * 215 * @return the builder class 216 */ 217 public String getBuilderClass() { 218 return builderClass; 219 } 220 221 /** 222 * {@inheritDoc} This implementation delegates to some protected methods to create a new builder instance using 223 * reflection and to configure it with parameter values defined by the passed in {@code BeanDeclaration}. 224 */ 225 @Override 226 public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException { 227 try { 228 final Collection<BuilderParameters> params = createParameterObjects(); 229 initializeParameterObjects(decl, params); 230 final BasicConfigurationBuilder<? extends Configuration> builder = createBuilder(decl, params); 231 configureBuilder(builder, decl, params); 232 return builder; 233 } catch (final ConfigurationException cex) { 234 throw cex; 235 } catch (final Exception ex) { 236 throw new ConfigurationException(ex); 237 } 238 } 239 240 /** 241 * Gets the name of the configuration class created by the builder produced by this provider. 242 * 243 * @return the configuration class 244 */ 245 public String getConfigurationClass() { 246 return configurationClass; 247 } 248 249 /** 250 * Gets an unmodifiable collection with the names of parameter classes supported by this provider. 251 * 252 * @return the parameter classes 253 */ 254 public Collection<String> getParameterClasses() { 255 return parameterClasses; 256 } 257 258 /** 259 * Gets the name of the class of the builder created by this provider if the reload flag is set. If this method 260 * returns <strong>null</strong>, reloading builders are not supported by this provider. 261 * 262 * @return the reloading builder class 263 */ 264 public String getReloadingBuilderClass() { 265 return reloadingBuilderClass; 266 } 267 268 /** 269 * Passes all parameter objects to the parent {@code CombinedConfigurationBuilder} so that properties already defined 270 * for the parent builder can be added. This method is called before the parameter objects are initialized from the 271 * definition configuration. This way properties from the parent builder are inherited, but can be overridden for child 272 * configurations. 273 * 274 * @param decl the current {@code ConfigurationDeclaration} 275 * @param params the collection with (uninitialized) parameter objects 276 */ 277 protected void inheritParentBuilderProperties(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) { 278 params.forEach(p -> decl.getConfigurationBuilder().initChildBuilderParameters(p)); 279 } 280 281 /** 282 * Initializes the parameter objects with data stored in the current bean declaration. This method is called before the 283 * newly created builder instance is configured with the parameter objects. It maps attributes of the bean declaration 284 * to properties of parameter objects. In addition, it invokes the parent {@code CombinedConfigurationBuilder} so that 285 * the parameters object can inherit properties already defined for this builder. 286 * 287 * @param decl the current {@code ConfigurationDeclaration} 288 * @param params the collection with (uninitialized) parameter objects 289 * @throws Exception if an error occurs 290 */ 291 protected void initializeParameterObjects(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws Exception { 292 inheritParentBuilderProperties(decl, params); 293 final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params); 294 decl.getConfigurationBuilder().initBean(wrapBean, decl); 295 } 296 297 /** 298 * Determines the <em>allowFailOnInit</em> flag for the newly created builder based on the given 299 * {@code ConfigurationDeclaration}. Some combinations of flags in the declaration say that a configuration source is 300 * optional, but an empty instance should be created if its creation fail. 301 * 302 * @param decl the current {@code ConfigurationDeclaration} 303 * @return the value of the <em>allowFailOnInit</em> flag 304 */ 305 protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl) { 306 return decl.isOptional() && decl.isForceCreate(); 307 } 308}