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}