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