001    package org.kuali.common.util.properties;
002    
003    import java.util.List;
004    import java.util.Properties;
005    
006    import org.kuali.common.util.Assert;
007    import org.kuali.common.util.PropertyUtils;
008    import org.kuali.common.util.cache.Cache;
009    import org.kuali.common.util.cache.SimpleCache;
010    import org.kuali.common.util.property.processor.NoOpProcessor;
011    import org.kuali.common.util.property.processor.OverridingProcessor;
012    import org.kuali.common.util.property.processor.PropertyProcessor;
013    import org.kuali.common.util.resolver.PropertiesValueResolver;
014    import org.kuali.common.util.resolver.ValueResolver;
015    import org.slf4j.Logger;
016    import org.slf4j.LoggerFactory;
017    
018    public final 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            @Override
039            public Properties getProperties(List<Location> locations) {
040    
041                    // Allocate a new properties object
042                    Properties properties = new Properties();
043    
044                    // Cycle through our list of locations
045                    for (Location location : locations) {
046    
047                            // Override anything we've loaded with properties from overrides
048                            Properties combined = PropertyUtils.combine(properties, overrides);
049    
050                            // Use the combined properties to resolve values
051                            ValueResolver resolver = new PropertiesValueResolver(combined);
052    
053                            // Resolve the location using the resolver
054                            String resolvedLocation = resolver.resolve(location.getValue());
055    
056                            // Setup a loader capable of correctly handling things
057                            // It might be perfectly acceptable for the location to not even exist
058                            // The location might point to the default location for user specified overrides and the user hasn't provided any (for example)
059                            // The loader is allowed to ignore missing locations, emit a log message about missing locations, or throw an exception
060                            PropertiesLoader loader = new CachingLoader(location, resolvedLocation, CACHE);
061    
062                            // This may return an empty properties object depending on the configuration of the corresponding Location object
063                            Properties loaded = loader.load();
064    
065                            // Any freshly loaded properties "win" over previous properties
066                            new OverridingProcessor(loaded).process(properties);
067                    }
068    
069                    // Do any post processing as needed
070                    postProcessor.process(properties);
071    
072                    // This is expensive, only do this in debug mode
073                    if (logger.isDebugEnabled()) {
074                            logger.debug("Displaying {} property values:\n\n{}", properties.size(), PropertyUtils.toString(properties));
075                    }
076    
077                    // Return what we've found
078                    return properties;
079            }
080    
081            public void clearCache() {
082                    CACHE.clear();
083            }
084    
085            public Properties getOverrides() {
086                    return overrides;
087            }
088    
089            public PropertyProcessor getPostProcessor() {
090                    return postProcessor;
091            }
092    
093    }