001    /**
002     * Copyright 2010-2012 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     */
016    package org.kuali.common.util.property;
017    
018    import java.util.ArrayList;
019    import java.util.Arrays;
020    import java.util.List;
021    import java.util.Properties;
022    
023    import org.apache.commons.lang3.StringUtils;
024    import org.jasypt.util.text.TextEncryptor;
025    import org.kuali.common.util.EncUtils;
026    import org.kuali.common.util.EncryptionMode;
027    import org.kuali.common.util.EncryptionStrength;
028    import org.kuali.common.util.LoggerUtils;
029    import org.kuali.common.util.PropertyUtils;
030    import org.kuali.common.util.Str;
031    import org.kuali.common.util.obscure.DefaultObscurer;
032    import org.kuali.common.util.obscure.Obscurer;
033    import org.kuali.common.util.property.processor.AddPrefixProcessor;
034    import org.kuali.common.util.property.processor.EndsWithDecryptProcessor;
035    import org.kuali.common.util.property.processor.EndsWithEncryptProcessor;
036    import org.kuali.common.util.property.processor.GlobalOverrideProcessor;
037    import org.kuali.common.util.property.processor.OverrideProcessor;
038    import org.kuali.common.util.property.processor.PropertyProcessor;
039    import org.kuali.common.util.property.processor.ReformatKeysAsEnvVarsProcessor;
040    import org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor;
041    import org.slf4j.Logger;
042    import org.slf4j.LoggerFactory;
043    import org.springframework.util.Assert;
044    import org.springframework.util.PropertyPlaceholderHelper;
045    
046    public class DefaultPropertyContext implements PropertyContext {
047    
048            private static final Logger logger = LoggerFactory.getLogger(DefaultPropertyContext.class);
049            PropertyPlaceholderHelper helper = Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER;
050            String globalPropertiesMode = Constants.DEFAULT_GLOBAL_PROPERTIES_MODE.name();
051            String resolvePlaceholders = Boolean.toString(Constants.DEFAULT_RESOLVE_PLACEHOLDERS);
052            Obscurer obscurer = new DefaultObscurer();
053            String style = PropertyStyle.NORMAL.name();
054            String encryptionMode = EncryptionMode.NONE.name();
055            String encryptionStrength = EncryptionStrength.BASIC.name();
056            String encryptionPassword;
057            String prefix;
058            List<PropertyProcessor> processors;
059            Properties properties;
060            Properties buildProperties;
061            List<String> buildPropertyIncludes;
062            List<String> buildPropertyExcludes;
063    
064            protected List<PropertyProcessor> getDefaultProcessors() {
065                    List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>();
066    
067                    // If this context is being loaded as part of a build process, build properties win over properties from .properties files
068                    if (buildProperties != null) {
069                            OverrideProcessor overrideProcessor = new OverrideProcessor(Constants.DEFAULT_PROPERTY_OVERWRITE_MODE, buildProperties);
070                            if (buildPropertyIncludes != null) {
071                                    overrideProcessor.setIncludes(buildPropertyIncludes);
072                            }
073                            if (buildPropertyExcludes != null) {
074                                    overrideProcessor.setExcludes(buildPropertyExcludes);
075                            }
076                            processors.add(overrideProcessor);
077                    }
078    
079                    // Decrypt/encrypt as appropriate
080                    if (encryptionMode != null) {
081                            EncryptionMode mode = EncryptionMode.valueOf(encryptionMode);
082                            EncryptionStrength strength = EncryptionStrength.valueOf(encryptionStrength);
083                            processors.add(getEncProcessor(mode, strength, encryptionPassword));
084                    }
085    
086                    /**
087                     * Remove the local reference to the encryption password now that the TextEncryptor has been created.<br>
088                     * Note that the encryption password is VERY likely to be hanging around in memory even after being set to null locally.<br>
089                     * Setting it to null here just makes it slightly tougher for someone to obtain the password.<br>
090                     * Having a reference to this bean no longer does them any good, they'll have to search around in memory to find it.<br>
091                     */
092                    this.encryptionPassword = null;
093    
094                    GlobalPropertiesMode gpm = GlobalPropertiesMode.valueOf(globalPropertiesMode);
095    
096                    // By default, system/environment properties override loaded properties
097                    processors.add(new GlobalOverrideProcessor(gpm));
098    
099                    // By default, all placeholders in the properties are resolved
100                    if (Boolean.parseBoolean(resolvePlaceholders)) {
101                            processors.add(new ResolvePlaceholdersProcessor(helper, gpm));
102                    }
103    
104                    // Add a prefix to the property keys if appropriate
105                    if (!StringUtils.isBlank(prefix)) {
106                            processors.add(new AddPrefixProcessor(prefix));
107                    }
108    
109                    // Reformat the keys in environment variable format if appropriate
110                    if (style != null) {
111                            processors.add(getStyleProcessor(style));
112                    }
113    
114                    // Return the list of processors
115                    return processors;
116            }
117    
118            protected PropertyProcessor getStyleProcessor(String style) {
119                    switch (PropertyStyle.valueOf(style)) {
120                    case NORMAL:
121                            return Constants.NO_OP_PROCESSOR;
122                    case ENVIRONMENT_VARIABLE:
123                            return new ReformatKeysAsEnvVarsProcessor();
124                    default:
125                            throw new IllegalArgumentException("Property style " + style + " is unknown");
126                    }
127            }
128    
129            protected PropertyProcessor getEncProcessor(EncryptionMode mode, EncryptionStrength strength, String password) {
130                    switch (mode) {
131                    case NONE:
132                            return Constants.NO_OP_PROCESSOR;
133                    case ENCRYPT:
134                            TextEncryptor encryptor = EncUtils.getTextEncryptor(strength, password);
135                            return new EndsWithEncryptProcessor(encryptor);
136                    case DECRYPT:
137                            TextEncryptor decryptor = EncUtils.getTextEncryptor(strength, password);
138                            return new EndsWithDecryptProcessor(decryptor);
139                    default:
140                            throw new IllegalArgumentException("Encryption mode '" + mode + "' is unknown");
141                    }
142            }
143    
144            protected void log() {
145                    if (!StringUtils.equals(EncryptionMode.NONE.name(), encryptionMode)) {
146                            logger.info("Encryption mode - " + StringUtils.trimToEmpty(encryptionMode));
147                            logger.info("Encryption strength - " + StringUtils.trimToEmpty(encryptionStrength));
148                            String displayPassword = LoggerUtils.getNullAsNone(encryptionPassword);
149                            if (encryptionPassword != null) {
150                                    displayPassword = obscurer.obscure(encryptionPassword);
151                            }
152                            logger.info("Encryption password - " + displayPassword);
153                    }
154                    if (!StringUtils.equals(PropertyStyle.NORMAL.name(), style)) {
155                            logger.info("Property style - " + StringUtils.trimToEmpty(style));
156                    }
157                    if (!StringUtils.isEmpty(prefix)) {
158                            logger.info("Property prefix - " + StringUtils.trimToEmpty(prefix));
159                    }
160                    if (!StringUtils.equals(Boolean.toString(Constants.DEFAULT_RESOLVE_PLACEHOLDERS), resolvePlaceholders)) {
161                            logger.info("Resolve placeholders - " + resolvePlaceholders);
162                    }
163            }
164    
165            @Override
166            public void initialize(Properties properties) {
167                    GlobalPropertiesMode gpm = GlobalPropertiesMode.valueOf(globalPropertiesMode);
168                    Properties global = PropertyUtils.getProperties(properties, gpm);
169                    this.encryptionMode = resolve(encryptionMode, global);
170                    this.encryptionPassword = resolveAndRemove(encryptionPassword, global, properties);
171                    this.encryptionStrength = resolve(encryptionStrength, global);
172                    this.style = resolve(style, global);
173                    this.prefix = resolve(prefix, global);
174                    this.resolvePlaceholders = resolve(resolvePlaceholders, global);
175                    log();
176                    validate();
177                    addProcessors();
178                    logger.info("Proceeding with " + processors.size() + " processors.");
179            }
180    
181            protected void addProcessors() {
182                    List<PropertyProcessor> defaultProcessors = getDefaultProcessors();
183                    if (processors == null) {
184                            processors = defaultProcessors;
185                    } else {
186                            processors.addAll(0, defaultProcessors);
187                    }
188            }
189    
190            protected void validate() {
191                    EncryptionMode.valueOf(encryptionMode);
192                    EncryptionStrength.valueOf(encryptionStrength);
193                    PropertyStyle.valueOf(style);
194                    Boolean.parseBoolean(resolvePlaceholders);
195            }
196    
197            protected String getPlaceholderKey(String string) {
198                    String prefix = Constants.DEFAULT_PLACEHOLDER_PREFIX;
199                    String suffix = Constants.DEFAULT_PLACEHOLDER_SUFFIX;
200                    String separator = Constants.DEFAULT_VALUE_SEPARATOR;
201                    String key = StringUtils.substringBetween(string, prefix, separator);
202                    if (key == null) {
203                            return StringUtils.substringBetween(string, prefix, suffix);
204                    } else {
205                            return key;
206                    }
207            }
208    
209            protected void remove(String string, String resolvedString, Properties properties) {
210                    boolean placeholder = PropertyUtils.isSingleUnresolvedPlaceholder(string);
211                    boolean resolved = !StringUtils.equals(string, resolvedString);
212                    boolean irrelevant = Str.contains(Arrays.asList(Constants.NONE, Constants.NULL), resolvedString, false);
213                    boolean remove = placeholder && resolved && !irrelevant;
214                    if (remove) {
215                            String key = getPlaceholderKey(string);
216                            Assert.notNull(key, "key is null");
217                            if (properties.getProperty(key) != null) {
218                                    logger.info("Removing property '" + key + "'");
219                                    properties.remove(key);
220                            }
221                    }
222            }
223    
224            protected String resolveAndRemove(String string, Properties global, Properties properties) {
225                    String resolvedString = resolve(string, global);
226                    remove(string, resolvedString, properties);
227                    return resolvedString;
228            }
229    
230            protected String resolve(String string, Properties properties) {
231                    if (string == null) {
232                            return null;
233                    } else {
234                            String resolvedValue = helper.replacePlaceholders(string, properties);
235                            if (!StringUtils.equals(string, resolvedValue)) {
236                                    logger.debug("Resolved {} -> {}", string, resolvedValue);
237                            }
238                            return resolvedValue;
239                    }
240            }
241    
242            public String getPrefix() {
243                    return prefix;
244            }
245    
246            public void setPrefix(String prefix) {
247                    this.prefix = prefix;
248            }
249    
250            public String getStyle() {
251                    return style;
252            }
253    
254            public void setStyle(String style) {
255                    this.style = style;
256            }
257    
258            public PropertyPlaceholderHelper getHelper() {
259                    return helper;
260            }
261    
262            public void setHelper(PropertyPlaceholderHelper helper) {
263                    this.helper = helper;
264            }
265    
266            public String getEncryptionMode() {
267                    return encryptionMode;
268            }
269    
270            public void setEncryptionMode(String encryptionMode) {
271                    this.encryptionMode = encryptionMode;
272            }
273    
274            public String getEncryptionStrength() {
275                    return encryptionStrength;
276            }
277    
278            public void setEncryptionStrength(String encryptionStrength) {
279                    this.encryptionStrength = encryptionStrength;
280            }
281    
282            public String getEncryptionPassword() {
283                    return encryptionPassword;
284            }
285    
286            public void setEncryptionPassword(String encryptionPassword) {
287                    this.encryptionPassword = encryptionPassword;
288            }
289    
290            @Override
291            public List<PropertyProcessor> getProcessors() {
292                    return processors;
293            }
294    
295            public void setProcessors(List<PropertyProcessor> processors) {
296                    this.processors = processors;
297            }
298    
299            public Properties getProperties() {
300                    return properties;
301            }
302    
303            public void setProperties(Properties properties) {
304                    this.properties = properties;
305            }
306    
307            public String getGlobalPropertiesMode() {
308                    return globalPropertiesMode;
309            }
310    
311            public void setGlobalPropertiesMode(String globalPropertiesMode) {
312                    this.globalPropertiesMode = globalPropertiesMode;
313            }
314    
315            public String getResolvePlaceholders() {
316                    return resolvePlaceholders;
317            }
318    
319            public void setResolvePlaceholders(String resolvePlaceholders) {
320                    this.resolvePlaceholders = resolvePlaceholders;
321            }
322    
323            public Obscurer getObscurer() {
324                    return obscurer;
325            }
326    
327            public void setObscurer(Obscurer obscurer) {
328                    this.obscurer = obscurer;
329            }
330    
331            public Properties getBuildProperties() {
332                    return buildProperties;
333            }
334    
335            public void setBuildProperties(Properties buildProperties) {
336                    this.buildProperties = buildProperties;
337            }
338    
339            public List<String> getBuildPropertyIncludes() {
340                    return buildPropertyIncludes;
341            }
342    
343            public void setBuildPropertyIncludes(List<String> buildPropertyIncludes) {
344                    this.buildPropertyIncludes = buildPropertyIncludes;
345            }
346    
347            public List<String> getBuildPropertyExcludes() {
348                    return buildPropertyExcludes;
349            }
350    
351            public void setBuildPropertyExcludes(List<String> buildPropertyExcludes) {
352                    this.buildPropertyExcludes = buildPropertyExcludes;
353            }
354    }