001/**
002 * Copyright 2010-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util.spring;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.Comparator;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024import java.util.Properties;
025
026import org.kuali.common.util.Assert;
027import org.kuali.common.util.PropertyUtils;
028import org.kuali.common.util.log.LoggerUtils;
029import org.kuali.common.util.properties.Location;
030import org.kuali.common.util.properties.PropertiesService;
031import org.kuali.common.util.property.ImmutableProperties;
032import org.kuali.common.util.spring.service.PropertySourceContext;
033import org.kuali.common.util.spring.service.SpringContext;
034import org.slf4j.Logger;
035import org.springframework.beans.factory.BeanFactoryUtils;
036import org.springframework.context.ConfigurableApplicationContext;
037import org.springframework.context.annotation.AnnotationConfigApplicationContext;
038import org.springframework.core.env.ConfigurableEnvironment;
039import org.springframework.core.env.EnumerablePropertySource;
040import org.springframework.core.env.MutablePropertySources;
041import org.springframework.core.env.PropertiesPropertySource;
042import org.springframework.core.env.PropertySource;
043
044import com.google.common.base.Preconditions;
045
046public class PropertySourceUtils {
047
048        private static final String PROPERTIES_PROPERTY_SOURCE = "propertiesPropertySource";
049
050        private static final Logger logger = LoggerUtils.make();
051
052        /**
053         * Return a property source from system properties plus the environment
054         */
055        public static PropertySource<?> getDefaultPropertySource() {
056                return new PropertiesPropertySource(PROPERTIES_PROPERTY_SOURCE, PropertyUtils.getGlobalProperties());
057        }
058
059        /**
060         * Return a property source based on the properties object passed in, but where system properties plus environment properties "win"
061         */
062        public static PropertySource<?> getPropertySource(Properties properties) {
063                return new PropertiesPropertySource(PROPERTIES_PROPERTY_SOURCE, PropertyUtils.getGlobalProperties(properties));
064        }
065
066        /**
067         * Return a property source based on the properties loaded from the locations passed in.
068         */
069        public static PropertySource<?> getPropertySource(PropertiesService service, List<Location> locations) {
070                return getPropertySource(service, locations, false);
071        }
072
073        /**
074         * Return a property source based on the properties loaded from the locations passed in, but where system properties plus environment properties "win" if
075         * {@code includeGlobal=true}
076         */
077        public static PropertySource<?> getPropertySource(PropertiesService service, List<Location> locations, boolean includeGlobal) {
078                Properties properties = service.getProperties(locations);
079                if (includeGlobal) {
080                        properties = PropertyUtils.getGlobalProperties(properties);
081                }
082                return new PropertiesPropertySource(PROPERTIES_PROPERTY_SOURCE, properties);
083        }
084
085        /**
086         * Aggregate every property from every <code>PropertySource</code> in the <code>ConfigurableEnvironment</code> into a <code>Properties</code> object.
087         * 
088         * @throws IllegalArgumentException
089         *             If any <code>PropertySource</code> is not an <code>EnumerablePropertySource</code> or if any values are not <code>java.lang.String</code>
090         */
091        public static Properties getAllEnumerableProperties(ConfigurableEnvironment env) {
092
093                // Extract the list of PropertySources from the environment
094                List<PropertySource<?>> sources = getPropertySources(env);
095
096                // Spring provides PropertySource objects ordered from highest priority to lowest priority
097                // We reverse the order here so things follow the "last one in wins" strategy
098                Collections.reverse(sources);
099
100                // Make sure every property source is enumerable
101                List<EnumerablePropertySource<?>> enumerables = asEnumerableList(sources);
102
103                // Combine them into a single Properties object
104                return convert(enumerables);
105        }
106
107        /**
108         * Aggregate every property from every <code>PropertySource</code> in the <code>ConfigurableEnvironment</code> into a <code>Properties</code> object.
109         * 
110         * If a PropertySource is not Enumerable, just omit it from the list, but do not throw an exception
111         */
112        public static Properties getAllEnumerablePropertiesQuietly(ConfigurableEnvironment env) {
113
114                // Extract the list of PropertySources from the environment
115                List<PropertySource<?>> sources = getPropertySources(env);
116
117                // Spring provides PropertySource objects ordered from highest priority to lowest priority
118                // We reverse the order here so things follow the "last one in wins" strategy
119                Collections.reverse(sources);
120
121                // Make sure every property source is enumerable
122                List<EnumerablePropertySource<?>> enumerables = asEnumerableListQuietly(sources);
123
124                // Combine them into a single Properties object
125                return convert(enumerables);
126        }
127
128        /**
129         * Aggregate every property from every <code>PropertySource</code> in the <code>ConfigurableEnvironment</code> into an <code>Properties</code> object.
130         * 
131         * @throws IllegalArgumentException
132         *             If any <code>PropertySource</code> is not an <code>EnumerablePropertySource</code> or if any values are not <code>java.lang.String</code>
133         */
134        public static ImmutableProperties getEnvAsImmutableProperties(ConfigurableEnvironment env) {
135                return new ImmutableProperties(getAllEnumerableProperties(env));
136        }
137
138        /**
139         * Create an <code>EnumerablePropertySource</code> list from a <code>PropertySource</code> list
140         * 
141         * @throws <code>IllegalArgumentException</code> if any element in <code>sources</code> is not an <code>EnumerablePropertySource</code>
142         */
143        public static List<EnumerablePropertySource<?>> asEnumerableList(List<PropertySource<?>> sources) {
144                List<EnumerablePropertySource<?>> list = new ArrayList<EnumerablePropertySource<?>>();
145                for (PropertySource<?> source : sources) {
146                        boolean expression = source instanceof EnumerablePropertySource<?>;
147                        String errorMessage = "'%s' is not enumerable [%s]";
148                        Object[] args = { source.getName(), source.getClass().getCanonicalName() };
149                        Preconditions.checkState(expression, errorMessage, args);
150                        EnumerablePropertySource<?> element = (EnumerablePropertySource<?>) source;
151                        list.add(element);
152                }
153                return list;
154        }
155
156        /**
157         * Create an <code>EnumerablePropertySource</code> list from a <code>PropertySource</code> list
158         * 
159         * @throws <code>IllegalArgumentException</code> if any element in <code>sources</code> is not an <code>EnumerablePropertySource</code>
160         */
161        public static List<EnumerablePropertySource<?>> asEnumerableListQuietly(List<PropertySource<?>> sources) {
162                List<EnumerablePropertySource<?>> list = new ArrayList<EnumerablePropertySource<?>>();
163                for (PropertySource<?> source : sources) {
164                        if (source instanceof EnumerablePropertySource<?>) {
165                                EnumerablePropertySource<?> element = (EnumerablePropertySource<?>) source;
166                                list.add(element);
167                        } else {
168                                logger.warn("'{}' is not enumerable [{}]", source.getName(), source.getClass().getCanonicalName());
169                        }
170                }
171                return list;
172        }
173
174        public static List<PropertySource<?>> getPropertySources(Class<?> annotatedClass) {
175                ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(annotatedClass);
176                return extractPropertySourcesAndClose(context);
177        }
178
179        public static List<PropertySource<?>> extractPropertySourcesAndClose(ConfigurableApplicationContext context) {
180                // Extract PropertySources (if any)
181                List<PropertySource<?>> sources = getPropertySources(context);
182
183                // Close the context
184                SpringUtils.closeQuietly(context);
185
186                // Return the list
187                return sources;
188        }
189
190        /**
191         * Copy the key/value pairs from <code>source</code> into a <code>java.util.Properties</code> object.
192         * 
193         * @throws <code>IllegalArgumentException</code> if any value is <code>null</code> or is not a <code>java.lang.String</code>
194         */
195        public static Properties convert(EnumerablePropertySource<?> source) {
196                Assert.notNull(source, "source is null");
197                Properties properties = new Properties();
198                String[] names = source.getPropertyNames();
199                for (String name : names) {
200                        Object object = source.getProperty(name);
201                        Assert.notNull(object, "[" + name + "] is null");
202                        Assert.isTrue(object instanceof String, "[" + name + "] is not a string");
203                        properties.setProperty(name, (String) object);
204                }
205                return properties;
206        }
207
208        /**
209         * Copy the key/value pairs from <code>sources</code> into a <code>java.util.Properties</code> object.
210         * 
211         * @throws <code>IllegalArgumentException</code> if any value is <code>null</code> or is not a <code>java.lang.String</code>
212         */
213        public static Properties convert(List<EnumerablePropertySource<?>> sources) {
214                Properties converted = new Properties();
215                for (EnumerablePropertySource<?> source : sources) {
216                        Properties properties = convert(source);
217                        converted.putAll(properties);
218                }
219                return converted;
220        }
221
222        /**
223         * Aggregate all <code>PropertySource<?><code> objects from the environment into a <code>List</code>
224         */
225        public static List<PropertySource<?>> getPropertySources(ConfigurableEnvironment env) {
226                Preconditions.checkNotNull(env, "'env' cannot be null");
227                MutablePropertySources mps = env.getPropertySources();
228                List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
229                Iterator<PropertySource<?>> itr = mps.iterator();
230                while (itr.hasNext()) {
231                        PropertySource<?> source = itr.next();
232                        sources.add(source);
233                }
234                return sources;
235        }
236
237        /**
238         * Remove all property sources from <code>env</code> and replace them with a single <code>PropertiesPropertySource</code> backed by <code>properties</code>
239         */
240        public static void reconfigurePropertySources(ConfigurableEnvironment env, String name, Properties properties) {
241                // Remove all existing property sources
242                removeAllPropertySources(env);
243
244                // MutablePropertySources allow us to manipulate the list of property sources
245                MutablePropertySources mps = env.getPropertySources();
246
247                // Make sure there are no existing property sources
248                Assert.isTrue(mps.size() == 0);
249
250                // Create a property source backed by the properties object passed in
251                PropertiesPropertySource pps = new PropertiesPropertySource(name, properties);
252
253                // Add it to the environment
254                mps.addFirst(pps);
255        }
256
257        /**
258         * Remove all property sources from <code>env</code>.
259         */
260        public static void removeAllPropertySources(ConfigurableEnvironment env) {
261                MutablePropertySources mps = env.getPropertySources();
262                List<PropertySource<?>> sources = getPropertySources(env);
263                for (PropertySource<?> source : sources) {
264                        String name = source.getName();
265                        mps.remove(name);
266                }
267        }
268
269        /**
270         * Null safe conversion of <code>PropertySource<?>[]</code> into <code>List&lt;PropertySource&lt;?>></code>
271         */
272        public static List<PropertySource<?>> asList(PropertySource<?>... sources) {
273                List<PropertySource<?>> list = new ArrayList<PropertySource<?>>();
274                if (sources == null) {
275                        return list;
276                }
277                for (PropertySource<?> element : sources) {
278                        if (element != null) {
279                                list.add(element);
280                        }
281                }
282                return list;
283        }
284
285        /**
286         * Return all <code>PropertySource</code> beans registered in the context, sorted use <code>comparator</code>
287         */
288        public static List<PropertySource<?>> getPropertySources(ConfigurableApplicationContext context, Comparator<PropertySource<?>> comparator) {
289                // Extract all beans that implement the PropertySource interface
290                @SuppressWarnings("rawtypes")
291                Map<String, PropertySource> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, PropertySource.class);
292
293                // Extract the PropertySource beans into a list
294                List<PropertySource<?>> list = new ArrayList<PropertySource<?>>();
295                for (PropertySource<?> source : map.values()) {
296                        list.add(source);
297                }
298
299                // Sort them using the provided comparator
300                Collections.sort(list, comparator);
301
302                // Return the list
303                return list;
304        }
305
306        /**
307         * Return all <code>PropertySource</code> beans registered in the context, sorted by name.
308         */
309        public static List<PropertySource<?>> getPropertySources(ConfigurableApplicationContext context) {
310                // Sort them by name
311                return getPropertySources(context, new PropertySourceNameComparator());
312        }
313
314        /**
315         * Return a <code>SpringContext</code> such that <code>source</code> is the only thing Spring uses to resolve placeholders
316         */
317        public static SpringContext getSinglePropertySourceContext(PropertySource<?> source) {
318                // Setup a property source context such that this property source is the only one registered with Spring
319                // This PropertySource will be the ONLY thing used to resolve placeholders
320                PropertySourceContext psc = new PropertySourceContext(source, true);
321
322                // Setup a Spring context
323                SpringContext context = new SpringContext();
324
325                // Supply Spring with our PropertySource
326                context.setPropertySourceContext(psc);
327
328                // Return a Spring context configured with a single property source
329                return context;
330        }
331
332}