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 */
017
018package org.apache.commons.configuration2.spring;
019
020import java.net.URL;
021import java.util.Properties;
022
023import org.apache.commons.configuration2.CompositeConfiguration;
024import org.apache.commons.configuration2.Configuration;
025import org.apache.commons.configuration2.ConfigurationConverter;
026import org.apache.commons.configuration2.builder.fluent.Configurations;
027import org.apache.commons.lang3.ArrayUtils;
028import org.springframework.beans.factory.FactoryBean;
029import org.springframework.beans.factory.InitializingBean;
030import org.springframework.core.io.Resource;
031import org.springframework.util.Assert;
032
033/**
034 * <p>FactoryBean which wraps a Commons CompositeConfiguration object for usage
035 * with PropertiesLoaderSupport. This allows the compositeConfiguration object to behave
036 * like a normal java.util.Properties object which can be passed on to
037 * setProperties() method allowing PropertyOverrideConfigurer and
038 * PropertyPlaceholderConfigurer to take advantage of Commons Configuration.
039 * </p>
040 * <p>Internally a CompositeConfiguration object is used for merging multiple
041 * Configuration objects.</p>
042 *
043 * @see java.util.Properties
044 * @see org.springframework.core.io.support.PropertiesLoaderSupport
045 *
046 */
047public class ConfigurationPropertiesFactoryBean implements InitializingBean, FactoryBean<Properties>
048{
049
050    /** internal CompositeConfiguration containing the merged configuration objects **/
051    private CompositeConfiguration compositeConfiguration;
052
053    /** supplied configurations that will be merged in compositeConfiguration **/
054    private Configuration[] configurations;
055
056    /** Spring resources for loading configurations **/
057    private Resource[] locations;
058
059    /** @see org.apache.commons.configuration2.AbstractConfiguration#throwExceptionOnMissing **/
060    private boolean throwExceptionOnMissing = true;
061
062    public ConfigurationPropertiesFactoryBean()
063    {
064    }
065
066    public ConfigurationPropertiesFactoryBean(final Configuration configuration)
067    {
068        Assert.notNull(configuration, "configuration");
069        this.compositeConfiguration = new CompositeConfiguration(configuration);
070    }
071
072    /**
073     * @see org.springframework.beans.factory.FactoryBean#getObject()
074     */
075    @Override
076    public Properties getObject() throws Exception
077    {
078        return compositeConfiguration != null ? ConfigurationConverter.getProperties(compositeConfiguration) : null;
079    }
080
081    /**
082     * @see org.springframework.beans.factory.FactoryBean#getObjectType()
083     */
084    @Override
085    public Class<?> getObjectType()
086    {
087        return java.util.Properties.class;
088    }
089
090    /**
091     * @see org.springframework.beans.factory.FactoryBean#isSingleton()
092     */
093    @Override
094    public boolean isSingleton()
095    {
096        return true;
097    }
098
099    /**
100     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
101     */
102    @Override
103    public void afterPropertiesSet() throws Exception
104    {
105        if (compositeConfiguration == null && ArrayUtils.isEmpty(configurations) && ArrayUtils.isEmpty(locations))
106        {
107            throw new IllegalArgumentException("no configuration object or location specified");
108        }
109
110        if (compositeConfiguration == null)
111        {
112            compositeConfiguration = new CompositeConfiguration();
113        }
114
115        compositeConfiguration.setThrowExceptionOnMissing(throwExceptionOnMissing);
116
117        if (configurations != null)
118        {
119            for (final Configuration configuration : configurations)
120            {
121                compositeConfiguration.addConfiguration(configuration);
122            }
123        }
124
125        if (locations != null)
126        {
127            for (final Resource location : locations)
128            {
129                final URL url = location.getURL();
130                final Configuration props = new Configurations().properties(url);
131                compositeConfiguration.addConfiguration(props);
132            }
133        }
134    }
135
136    public Configuration[] getConfigurations()
137    {
138        return defensiveCopy(configurations);
139    }
140
141    /**
142     * Set the commons configurations objects which will be used as properties.
143     *
144     * @param configurations commons configurations objects which will be used as properties.
145     */
146    public void setConfigurations(final Configuration... configurations)
147    {
148        this.configurations = defensiveCopy(configurations);
149    }
150
151    public Resource[] getLocations()
152    {
153        return defensiveCopy(locations);
154    }
155
156    /**
157     * Shortcut for loading compositeConfiguration from Spring resources. It will
158     * internally create a PropertiesConfiguration object based on the URL
159     * retrieved from the given Resources.
160     *
161     * @param locations resources of configuration files
162     */
163    public void setLocations(final Resource... locations)
164    {
165        this.locations = defensiveCopy(locations);
166    }
167
168    public boolean isThrowExceptionOnMissing()
169    {
170        return throwExceptionOnMissing;
171    }
172
173    /**
174     * Set the underlying Commons CompositeConfiguration throwExceptionOnMissing flag.
175     *
176     * @see org.apache.commons.configuration2.AbstractConfiguration#setThrowExceptionOnMissing(boolean)
177     * @param throwExceptionOnMissing The new value for the property
178     */
179    public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing)
180    {
181        this.throwExceptionOnMissing = throwExceptionOnMissing;
182    }
183
184    public CompositeConfiguration getConfiguration()
185    {
186        return compositeConfiguration;
187    }
188
189    /**
190     * Creates a defensive copy of the specified array. Handles null values
191     * correctly.
192     *
193     * @param src the source array
194     * @param <T> the type of the array
195     * @return the defensive copy of the array
196     */
197    private static <T> T[] defensiveCopy(final T[] src)
198    {
199        return src != null ? src.clone() : null;
200    }
201}