001/**
002 * Copyright 2005-2016 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.rice.xml.ingest;
017
018import java.io.IOException;
019import java.util.Properties;
020import java.util.SortedSet;
021
022import javax.servlet.ServletContext;
023
024import org.kuali.common.util.PropertyUtils;
025import org.kuali.common.util.Str;
026import org.kuali.common.util.log.LoggerUtils;
027import org.kuali.common.util.property.ImmutableProperties;
028import org.kuali.rice.core.api.config.property.Config;
029import org.kuali.rice.core.api.config.property.ConfigContext;
030import org.kuali.rice.core.impl.config.property.ConfigLogger;
031import org.kuali.rice.core.impl.config.property.JAXBConfigImpl;
032import org.kuali.rice.core.web.util.PropertySources;
033import org.slf4j.Logger;
034
035import com.google.common.base.Preconditions;
036import com.google.common.collect.Sets;
037
038/**
039 * Utility class to handle {@link PropertySources} and Rice config files.
040 * 
041 * @author Kuali Rice Team (rice.collab@kuali.org)
042 */
043public class RiceConfigUtils {
044
045        private static final Logger logger = LoggerUtils.make();
046
047    private RiceConfigUtils() {}
048
049    /**
050     * Gets the {@link Config} from both the current configuration and the ones in {@code loaded}, at {@code location},
051     * and in the {@code servletContext}.
052     *
053     * @param loaded the loaded properties
054     * @param location the location of additional properties
055     * @param servletContext the servlet context in which to add more properties
056     *
057     * @return the final configuration
058     */
059        public static Config getRootConfig(Properties loaded, String location, ServletContext servletContext) {
060                // Get the Rice config object the listener created
061                Config config = ConfigContext.getCurrentContextConfig();
062                Preconditions.checkNotNull(config, "'config' cannot be null");
063                Properties listenerProperties = getProperties(config);
064
065                // Parse config from the location indicated, using listener properties in the process of doing so
066                JAXBConfigImpl parsed = parseConfig(location, listenerProperties);
067
068                // Add and override loaded properties with parsed properties
069                addAndOverride(loaded, parsed.getRawProperties());
070
071                // Priority is servlet -> env -> system
072                // Override anything we've loaded with servlet, env, and system properties
073                Properties servlet = PropertySources.convert(servletContext);
074                Properties global = PropertyUtils.getGlobalProperties(servlet);
075                addAndOverride(loaded, global);
076                logger.info("Using {} distinct properties", Integer.valueOf(loaded.size()));
077
078                // Use JAXBConfigImpl in order to perform Rice's custom placeholder resolution logic now that everything is loaded
079                return new JAXBConfigImpl(loaded);
080
081        }
082
083        /**
084         * Parse the configuration stored at {@code location}.
085     *
086     * @param location the location to get properties from
087     *
088     * @return the new configuration
089     */
090        public static JAXBConfigImpl parseConfig(String location) {
091                return parseConfig(location, ImmutableProperties.of());
092        }
093
094        /**
095         * Parse the configuration stored at {@code location}, adding any additional properties from {@code properties}.
096     *
097     * @param location the location to get properties from
098     * @param properties any additional properties to add
099     *
100     * @return the new configuration
101         */
102        public static JAXBConfigImpl parseConfig(String location, Properties properties) {
103                try {
104                        JAXBConfigImpl config = new JAXBConfigImpl(location, properties);
105                        config.parseConfig();
106                        return config;
107                } catch (IOException e) {
108                        throw new IllegalStateException("Unexpected error parsing config", e);
109                }
110        }
111
112        /**
113         * Parse the configuration stored at {@code location} and initialize.
114     *
115     * @param location the location to get properties from
116     *
117     * @return the new configuration
118     */
119        public static JAXBConfigImpl parseAndInit(String location) {
120                JAXBConfigImpl config = parseConfig(location);
121                ConfigContext.init(config);
122                return config;
123        }
124
125    /**
126     * Returns the {@link Properties} from the given {@code config}.
127     *
128     * @param config the {@link Config} to get the {@link Properties} from
129     *
130     * @return the {@link Properties}
131     */
132        public static Properties getProperties(Config config) {
133                if (config instanceof JAXBConfigImpl) {
134                        JAXBConfigImpl jci = (JAXBConfigImpl) config;
135                        return jci.getRawProperties();
136                } else {
137                        logger.warn("Unable to access raw Rice config properties.");
138                        return config.getProperties();
139                }
140        }
141
142    /**
143     * Put all of the given {@code properties} into the given {@code config}.
144     *
145     * @param config the {@link Config} to add the {@link Properties} to
146     * @param properties the {@link Properties} to add
147     */
148    public static void putProperties(Config config, Properties properties) {
149        SortedSet<String> keys = Sets.newTreeSet(properties.stringPropertyNames());
150        for (String key : keys) {
151            config.putProperty(key, properties.getProperty(key));
152        }
153    }
154
155    private static void add(Properties oldProperties, Properties newProperties) {
156        SortedSet<String> newKeys = Sets.newTreeSet(Sets.difference(newProperties.stringPropertyNames(), oldProperties.stringPropertyNames()));
157
158        if (newKeys.isEmpty()) {
159            return;
160        }
161
162        logger.info("Adding {} properties", Integer.valueOf(newKeys.size()));
163
164        for (String newKey : newKeys) {
165            String value = newProperties.getProperty(newKey);
166            logger.debug("Adding - [{}]=[{}]", newKey, toLogMsg(newKey, value));
167            oldProperties.setProperty(newKey, value);
168        }
169    }
170
171        private static void override(Properties oldProperties, Properties newProperties) {
172                SortedSet<String> commonKeys = Sets.newTreeSet(Sets.intersection(newProperties.stringPropertyNames(), oldProperties.stringPropertyNames()));
173
174                if (commonKeys.isEmpty()) {
175                        return;
176                }
177
178                logger.debug("{} keys in common", Integer.valueOf(commonKeys.size()));
179
180        for (String commonKey : commonKeys) {
181                        String oldValue = oldProperties.getProperty(commonKey);
182                        String newValue = newProperties.getProperty(commonKey);
183
184                        if (!newValue.equals(oldValue)) {
185                                Object[] args = { commonKey, toLogMsg(commonKey, oldValue), toLogMsg(commonKey, newValue) };
186                                logger.info("Overriding - [{}]=[{}]->[{}]", args);
187                oldProperties.setProperty(commonKey, newValue);
188                        }
189                }
190        }
191
192    private static void addAndOverride(Properties oldProperties, Properties newProperties) {
193        add(oldProperties, newProperties);
194        override(oldProperties, newProperties);
195    }
196
197        private static String toLogMsg(String key, String value) {
198                return Str.flatten(ConfigLogger.getDisplaySafeValue(key, value));
199        }
200
201}