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.properties;
017
018import java.util.List;
019import java.util.Properties;
020
021import org.kuali.common.util.Assert;
022import org.kuali.common.util.PropertyUtils;
023import org.kuali.common.util.cache.Cache;
024import org.kuali.common.util.cache.SimpleCache;
025import org.kuali.common.util.property.processor.NoOpProcessor;
026import org.kuali.common.util.property.processor.OverridingProcessor;
027import org.kuali.common.util.property.processor.PropertyProcessor;
028import org.kuali.common.util.resolver.PropertiesValueResolver;
029import org.kuali.common.util.resolver.ValueResolver;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033public class DefaultPropertiesService implements PropertiesService {
034
035        private static final Logger logger = LoggerFactory.getLogger(DefaultPropertiesService.class);
036
037        private static final Cache<String, Properties> CACHE = new SimpleCache<String, Properties>();
038        private static final PropertyProcessor DEFAULT_POST_PROCESSOR = NoOpProcessor.INSTANCE;
039
040        private final Properties overrides;
041        private final PropertyProcessor postProcessor;
042
043        public DefaultPropertiesService(Properties overrides) {
044                this(overrides, DEFAULT_POST_PROCESSOR);
045        }
046
047        public DefaultPropertiesService(Properties overrides, PropertyProcessor postProcessor) {
048                Assert.noNulls(overrides, postProcessor);
049                this.overrides = PropertyUtils.toImmutable(overrides);
050                this.postProcessor = postProcessor;
051        }
052
053        protected PropertiesLoader getLoader(Location location, Cache<String, Properties> cache, Properties combined) {
054                return new CachingLoader(location, CACHE);
055        }
056
057        @Override
058        public Properties getProperties(List<Location> locations) {
059
060                // Allocate a new properties object
061                Properties properties = new Properties();
062
063                // Cycle through our list of locations
064                for (Location location : locations) {
065
066                        // Override anything we've loaded with properties from overrides
067                        Properties combined = PropertyUtils.combine(properties, overrides);
068
069                        // Use the combined properties to resolve values
070                        ValueResolver resolver = new PropertiesValueResolver(combined);
071
072                        // Resolve the location using the resolver
073                        String resolvedLocation = resolver.resolve(location.getValue());
074
075                        // If the resolved location is different from the original location, create a new location object
076                        Location actualLocation = getLocation(location, location.getValue(), resolvedLocation);
077
078                        // Setup a loader capable of correctly handling things
079                        // It might be perfectly acceptable for the location to not even exist
080                        // The location might point to the default location for user specified overrides and the user hasn't provided any (for example)
081                        // The loader is allowed to ignore missing locations, emit a log message about missing locations, or throw an exception
082                        PropertiesLoader loader = getLoader(actualLocation, CACHE, combined);
083
084                        // This may return an empty properties object depending on the configuration of the corresponding Location object
085                        Properties loaded = loader.load();
086
087                        // Any freshly loaded properties "win" over previous properties
088                        new OverridingProcessor(loaded).process(properties);
089                }
090
091                // Do any post processing as needed
092                postProcessor.process(properties);
093
094                // This is expensive, only do this in debug mode
095                if (logger.isDebugEnabled()) {
096                        logger.debug("Displaying {} property values:\n\n{}", properties.size(), PropertyUtils.toString(properties));
097                }
098
099                // Return what we've found
100                return properties;
101        }
102
103        private Location getLocation(Location location, String originalLocation, String resolvedLocation) {
104                if (originalLocation.equals(resolvedLocation)) {
105                        return location;
106                } else {
107                        return Location.builder(location, resolvedLocation).build();
108                }
109        }
110
111        public void clearCache() {
112                CACHE.clear();
113        }
114
115        public Properties getOverrides() {
116                return overrides;
117        }
118
119        public PropertyProcessor getPostProcessor() {
120                return postProcessor;
121        }
122
123}