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 * https://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
18 package org.apache.commons.configuration2.spring;
19
20 import java.util.Properties;
21 import java.util.stream.Stream;
22
23 import org.apache.commons.configuration2.CompositeConfiguration;
24 import org.apache.commons.configuration2.Configuration;
25 import org.apache.commons.configuration2.ConfigurationConverter;
26 import org.apache.commons.configuration2.builder.fluent.Configurations;
27 import org.apache.commons.lang3.ArrayUtils;
28 import org.springframework.beans.factory.FactoryBean;
29 import org.springframework.beans.factory.InitializingBean;
30 import org.springframework.core.io.Resource;
31 import org.springframework.util.Assert;
32
33 /**
34 * <p>
35 * FactoryBean which wraps a Commons CompositeConfiguration object for usage with PropertiesLoaderSupport. This allows
36 * the compositeConfiguration object to behave like a normal {@link Properties} object which can be passed on to
37 * setProperties() method allowing PropertyOverrideConfigurer and PropertyPlaceholderConfigurer to take advantage of
38 * Commons Configuration.
39 * </p>
40 * <p>
41 * Internally a CompositeConfiguration object is used for merging multiple Configuration objects.
42 * </p>
43 *
44 * @see java.util.Properties
45 * @see org.springframework.core.io.support.PropertiesLoaderSupport
46 */
47 public class ConfigurationPropertiesFactoryBean implements InitializingBean, FactoryBean<Properties> {
48
49 /**
50 * Creates a defensive copy of the specified array. Handles null values correctly.
51 *
52 * @param src the source array
53 * @param <T> the type of the array
54 * @return the defensive copy of the array
55 */
56 private static <T> T[] clone(final T[] src) {
57 return src != null ? src.clone() : null;
58 }
59
60 /** Internal CompositeConfiguration containing the merged configuration objects **/
61 private CompositeConfiguration compositeConfiguration;
62
63 /** Supplied configurations that will be merged in compositeConfiguration **/
64 private Configuration[] configurations;
65
66 /** Spring resources for loading configurations **/
67 private Resource[] locations;
68
69 /** @see org.apache.commons.configuration2.AbstractConfiguration#throwExceptionOnMissing **/
70 private boolean throwExceptionOnMissing = true;
71
72 /**
73 * Constructs a new instance.
74 */
75 public ConfigurationPropertiesFactoryBean() {
76 }
77
78 /**
79 * Constructs a new instance.
80 *
81 * @param configuration The configuration to compose.
82 */
83 public ConfigurationPropertiesFactoryBean(final Configuration configuration) {
84 Assert.notNull(configuration, "configuration");
85 this.compositeConfiguration = new CompositeConfiguration(configuration);
86 }
87
88 /**
89 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
90 */
91 @Override
92 public void afterPropertiesSet() throws Exception {
93 if (compositeConfiguration == null && ArrayUtils.isEmpty(configurations) && ArrayUtils.isEmpty(locations)) {
94 throw new IllegalArgumentException("no configuration object or location specified");
95 }
96
97 if (compositeConfiguration == null) {
98 compositeConfiguration = new CompositeConfiguration();
99 }
100
101 compositeConfiguration.setThrowExceptionOnMissing(throwExceptionOnMissing);
102
103 if (configurations != null) {
104 Stream.of(configurations).forEach(compositeConfiguration::addConfiguration);
105 }
106
107 if (locations != null) {
108 for (final Resource location : locations) {
109 compositeConfiguration.addConfiguration(new Configurations().properties(location.getURL()));
110 }
111 }
112 }
113
114 /**
115 * Gets the composite configuration.
116 *
117 * @return the composite configuration.
118 */
119 public CompositeConfiguration getConfiguration() {
120 return compositeConfiguration;
121 }
122
123 /**
124 * Gets a copy of the configurations.
125 *
126 * @return a copy of the configurations.
127 */
128 public Configuration[] getConfigurations() {
129 return clone(configurations);
130 }
131
132 /**
133 * Gets a copy of the resource locations.
134 *
135 * @return a copy of the resource locations.
136 */
137 public Resource[] getLocations() {
138 return clone(locations);
139 }
140
141 /**
142 * @see org.springframework.beans.factory.FactoryBean#getObject()
143 */
144 @Override
145 public Properties getObject() throws Exception {
146 return compositeConfiguration != null ? ConfigurationConverter.getProperties(compositeConfiguration) : null;
147 }
148
149 /**
150 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
151 */
152 @Override
153 public Class<?> getObjectType() {
154 return Properties.class;
155 }
156
157 /**
158 * @see org.springframework.beans.factory.FactoryBean#isSingleton()
159 */
160 @Override
161 public boolean isSingleton() {
162 return true;
163 }
164
165 /**
166 * Tests the underlying CompositeConfiguration throwExceptionOnMissing flag.
167 *
168 * @return the underlying CompositeConfiguration throwExceptionOnMissing flag.
169 */
170 public boolean isThrowExceptionOnMissing() {
171 return throwExceptionOnMissing;
172 }
173
174 /**
175 * Sets the commons configurations objects which will be used as properties.
176 *
177 * @param configurations commons configurations objects which will be used as properties.
178 */
179 public void setConfigurations(final Configuration... configurations) {
180 this.configurations = clone(configurations);
181 }
182
183 /**
184 * Shortcut for loading compositeConfiguration from Spring resources. It will internally create a
185 * PropertiesConfiguration object based on the URL retrieved from the given Resources.
186 *
187 * @param locations resources of configuration files
188 */
189 public void setLocations(final Resource... locations) {
190 this.locations = clone(locations);
191 }
192
193 /**
194 * Sets the underlying CompositeConfiguration throwExceptionOnMissing flag.
195 *
196 * @see org.apache.commons.configuration2.AbstractConfiguration#setThrowExceptionOnMissing(boolean)
197 * @param throwExceptionOnMissing The new value for the property
198 */
199 public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) {
200 this.throwExceptionOnMissing = throwExceptionOnMissing;
201 }
202 }