nl.isaac.dotcms.plugin.configuration.ConfigurationService.java Source code

Java tutorial

Introduction

Here is the source code for nl.isaac.dotcms.plugin.configuration.ConfigurationService.java

Source

package nl.isaac.dotcms.plugin.configuration;
/**
* dotCMS Configuration plugin by ISAAC - The Full Service Internet Agency is licensed 
* under a Creative Commons Attribution 3.0 Unported License
* - http://creativecommons.org/licenses/by/3.0/
* - http://www.geekyplugins.com/
* 
* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl)
*/

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.DefaultConfigurationBuilder.ConfigurationProvider;
import org.apache.commons.configuration.FileConfiguration;
import org.apache.commons.lang.text.StrLookup;

import com.dotcms.repackage.org.osgi.framework.Bundle;
import com.dotmarketing.osgi.OSGIProxyServlet;
import com.dotmarketing.util.Logger;

import nl.isaac.comp.configuration.CustomConfiguration;
import nl.isaac.comp.configuration.types.EnvironmentType;
import nl.isaac.dotcms.plugin.configuration.dotcms.DotCMSFileConfigurationProvider;
import nl.isaac.dotcms.plugin.configuration.exception.ConfigurationNotFoundException;
import nl.isaac.dotcms.plugin.configuration.listener.RequestStoringListener;
import nl.isaac.dotcms.plugin.configuration.osgi.OSGiFileConfigurationProvider;
import nl.isaac.dotcms.plugin.configuration.util.EmptyConfiguration;

/**
 * The class to retrieve your configurations from.
 * It look under {@link #serverConfigXmlLocation} for the general configuration, such as what environment type the server is, and where to find the host and plugin configuration stacks.
 * It also contains the server side, in memory, configuration cache.
 *
 * The hierarchy of configuration loading is primairly defined by {@link #locationConfigHosts} for host configurations and {@link #locationConfigPlugins} for plugin configurations.
 * At the time of writing the order for hosts is:
 * The serverConfig.xml
 * The file located at ${sys:catalina.home}/conf/applications/${isaac:hostName}/${isaac:ClientIPAddress}/hostConfig.xml
 * The file located at ${sys:catalina.home}/conf/applications/${isaac:hostName}/hostConfig.xml
 * And lastly, the file, for a specific host, located inside dotCMS itself at /config/hostConfig.xml
 *
 * At the time of writing the order for plugins is:
 * The serverConfig.xml
 * The file located at ${sys:catalina.home}/conf/applications/${isaac:hostName}/${isaac:pluginName}/${isaac:ClientIPAddress}/pluginConfig.xml
 * The file located at ${sys:catalina.home}/conf/applications/${isaac:hostName}/pluginConfig.xml
 * The file, for a specific host, located inside dotCMS itself at /config/${isaac:pluginName}/pluginConfig.xml
 * The file located at ${sys:catalina.home}/conf/applications/${isaac:pluginName}/pluginConfig.xml
 *
 * @author maarten
 *
 */
public class ConfigurationService {
    private static final String serverConfigXmlLocation = "${sys:catalina.home}/conf/applications/configurationPluginConfig.xml";
    private static final String locationConfigHosts = "${sys:catalina.home}/conf/applications/locationConfigHosts.xml";
    private static final String locationConfigPlugins = "${sys:catalina.home}/conf/applications/locationConfigPlugins.xml";

    private static final ConcurrentHashMap<String, CustomConfiguration> serverCache = new ConcurrentHashMap<String, CustomConfiguration>();
    private static final Set<String> ipForwardCache = new HashSet<String>();

    /**
     * The type of environment
     */
    public final static EnvironmentType environment;
    /**
     * Whether some external cache management should take place.
     */
    public final static boolean cacheOnIp;

    private final static CustomConfiguration serverConf;
    static {
        try {
            serverConf = ConfigurationFactory.createConfiguration("Server level", serverConfigXmlLocation);
        } catch (ConfigurationException e) {
            throw new RuntimeException("Failed to load system config", e);
        }

        environment = serverConf.getEnum(EnvironmentType.class, "environment");
        cacheOnIp = environment == EnvironmentType.DEV || environment == EnvironmentType.LOCAL;
    }

    /**
     * Get the default configuration for the current host, the default is managed per thread through {@link #storeDefaultParameters(ConfigurationParameters)} and {@link #clearDefaultParameters()}.
     *
     * @return
     * @throws ConfigurationException
     */
    public static CustomConfiguration getHostConfiguration() throws ConfigurationException {
        return retrieveFromCacheOrCreateConfiguration("host_", null, locationConfigHosts);
    }

    /**
     * Should the default configuration parameters not be set, return an empty configuration
     * @see {@link #getHostConfiguration()}
     * @return
     */
    public static CustomConfiguration tryGetHostConfiguration() {
        try {
            return getHostConfiguration();
        } catch (ConfigurationException e) {
            Logger.warn(ConfigurationService.class,
                    "Something went wrong trying to retrieve the default HostConfiguration, returning an empty configuration",
                    e);
            return new EmptyConfiguration();
        }
    }

    /**
     * The same as {@link #getHostConfiguration()} except it retrieves a Configuration for the specified <code>pluginName</code>
     * @param pluginName
     * @return
     * @throws ConfigurationException
     */
    public static CustomConfiguration getPluginConfiguration(String pluginName)
            throws ConfigurationException, ConfigurationNotFoundException {
        return retrieveFromCacheOrCreateConfiguration("plugin_" + pluginName + '_', pluginName,
                locationConfigPlugins);
    }

    /**
     * Should the default configuration parameters not be set, return an empty configuration
     * @see {@link #getPluginConfiguration(pluginName)}
     * @return
     */
    public static CustomConfiguration tryGetPluginConfiguration(String pluginName) {
        try {
            return getPluginConfiguration(pluginName);
        } catch (ConfigurationException e) {
            Logger.warn(ConfigurationService.class,
                    "Something went wrong trying to retrieve the default PluginConfiguration for: " + pluginName
                            + ", returning an empty configuration",
                    e);
            return new EmptyConfiguration();
        }
    }

    /**
     * Deals with getting the correct host specific parameters.
     * @param pluginName
     * @param configurationLocation
     * @return
     * @throws ConfigurationException
     */
    private static CustomConfiguration retrieveFromCacheOrCreateConfiguration(String keyPrefix, String pluginName,
            String configurationLocation) throws ConfigurationException, ConfigurationNotFoundException {
        ConfigurationParameters params = defaultParameters.get();
        if (params != null) {
            return retrieveFromCacheOrCreateConfiguration(keyPrefix, params.getHostName(), params.getIpAddress(),
                    params.getSessionId(), pluginName, configurationLocation);
        } else {
            Logger.warn(ConfigurationService.class,
                    "The default configuration has not been set, therefore you cannot use this method.");
            //         throw new ConfigurationNotFoundException("The default configuration has not been set, therefore you cannot use this method.");
            return retrieveFromCacheOrCreateConfiguration(keyPrefix, null, null, null, pluginName,
                    configurationLocation);
        }
    }

    /**
     * Get a configuration for a specific host
     *
     * @param hostName
     * @param ipAddress
     * @param sessionId
     * @return
     * @throws ConfigurationException
     */
    public static CustomConfiguration getHostConfiguration(String hostName, String ipAddress, String sessionId)
            throws ConfigurationException {
        return retrieveFromCacheOrCreateConfiguration("host_", hostName, ipAddress, sessionId, null,
                locationConfigHosts);
    }

    /**
     * Get a configuration for a specific plugin
     *
     * @param pluginName
     * @param hostName
     * @param ipAddress
     * @param sessionId
     * @return
     * @throws ConfigurationException
     */
    public static CustomConfiguration getPluginConfiguration(String pluginName, String hostName, String ipAddress,
            String sessionId) throws ConfigurationException {
        return retrieveFromCacheOrCreateConfiguration("plugin_" + pluginName + '_', hostName, ipAddress, sessionId,
                pluginName, locationConfigPlugins);
    }

    /**
     * This method deals with constructing a key for the specified configuration, checking the cache and if it isn't in the cache constructing (and caching) it.
     *
     * @param hostName
     * @param ipAddress
     * @param sessionId
     * @param pluginName
     * @param configurationLocation
     * @return
     * @throws ConfigurationException
     */
    private static CustomConfiguration retrieveFromCacheOrCreateConfiguration(String keyPrefix, String hostName,
            String ipAddress, String sessionId, String pluginName, String configurationLocation)
            throws ConfigurationException {

        if (ConfigurationDotCMSCacheGroupHandler.isFlushed()) {
            clearCache();
            ConfigurationDotCMSCacheGroupHandler.setLoaded();
        }

        String key;
        final String basicKey = keyPrefix + hostName;

        // On Windows the : character is not allowed, replace it with _
        if (ipAddress != null) {
            ipAddress = ipAddress.replace(':', '_');
        }

        if (cacheOnIp) {
            String ipKey = basicKey + '_' + ipAddress + '_' + sessionId;
            if (!ipForwardCache.contains(ipKey)) {
                key = ipKey;
            } else {
                key = basicKey;
            }
        } else {
            key = basicKey;
        }

        Logger.debug(ConfigurationService.class, "Looking up configuration under key: " + key);
        CustomConfiguration conf = serverCache.get(key);

        if (conf != null) {
            return conf;
        }

        Logger.debug(ConfigurationService.class, "Loading configuration for key: " + key);

        Map<String, String> interpolationValues = new HashMap<String, String>();
        final Bundle bundle;

        interpolationValues.put("hostName", hostName);
        if (pluginName != null && pluginName.startsWith("osgi/")) {
            //This is an OSGi plugin
            String[] splitPluginName = pluginName.split("/");
            interpolationValues.put("pluginName", splitPluginName[1]);

            if (splitPluginName.length > 2) {
                long bundleId = Long.valueOf(splitPluginName[2]);
                bundle = OSGIProxyServlet.bundleContext.getBundle(bundleId);
            } else {
                // If we didn't get a bundleId we need to search for it, somewhat slower, but the result is cached
                bundle = searchBundles(splitPluginName[1]);
            }
        } else {
            if (pluginName != null) {
                interpolationValues.put("pluginName", pluginName);
            }
            bundle = null;
        }

        // We only do ip specific things on DEV (and local of course!)
        if (cacheOnIp && ipAddress != null) {
            interpolationValues.put("ClientIPAddress", ipAddress);
        }

        Map<String, StrLookup> interpolators = new HashMap<String, StrLookup>();
        interpolators.put("param", StrLookup.mapLookup(interpolationValues));

        DotCMSFileConfigurationProvider dotCmsProvider = new DotCMSFileConfigurationProvider(hostName);
        Map<String, ConfigurationProvider> providers = new HashMap<String, ConfigurationProvider>();
        providers.put("dotcms", dotCmsProvider);

        OSGiFileConfigurationProvider osgiProvider = new OSGiFileConfigurationProvider(bundle);
        providers.put("osgi", osgiProvider);

        conf = ConfigurationFactory.createConfiguration(key, configurationLocation, interpolators, providers);

        boolean isIpSpecific = false;
        if (cacheOnIp && ipAddress != null) {
            for (FileConfiguration fileConf : conf.getLoadedFileConfigurations()) {
                if (fileConf.getFileName() != null && fileConf.getFileName().contains(ipAddress)) {

                    isIpSpecific = true;

                    return conf;
                }
            }
        }
        if (isIpSpecific) {
            ConfigurationParameters params = defaultParameters.get();
            if (params != null) {
                params.addConfigurationToSession(key);
            }
        }
        if (cacheOnIp && !isIpSpecific) {
            ipForwardCache.add(key);
            if (serverCache.contains(basicKey)) {
                return serverCache.get(basicKey);
            } else {
                key = basicKey;
            }
        }
        Logger.debug(ConfigurationService.class, "Created a new configuration! " + key);

        serverCache.put(key, conf);

        return conf;
    }

    private static Bundle searchBundles(String pluginName) {
        for (Bundle bundle : OSGIProxyServlet.bundleContext.getBundles()) {
            if (pluginName.equals(bundle.getHeaders().get("Bundle-Name"))) {
                return bundle;
            }
        }
        return null;
    }

    /**
     * This is, effectively, a tuple, being able to return the required parameters needed to construct a key.
     * It also contains a hook to whatever system is outside of this class to register a configuration object (or at least it's key) so that the outside system can clean up when it's no-longer needed.
     *
     * @author maarten
     *
     */
    public interface ConfigurationParameters {
        String getHostName();

        String getSessionId();

        String getIpAddress();

        void addConfigurationToSession(String key);
    }

    private static final ThreadLocal<ConfigurationParameters> defaultParameters = new ThreadLocal<ConfigurationParameters>();

    /**
     * The hook for the outside system to deposit the default parameters
     * @param params
     */
    public static final void storeDefaultParameters(ConfigurationParameters params) {
        defaultParameters.set(params);
    }

    /**
     * The hook for the outside system to notify {@link ConfigurationService} that the stored parameters are no longer needed
     */
    public static final void clearDefaultParameters() {
        defaultParameters.remove();
    }

    /**
     * Method to be used by {@link RequestStoringListener} to stack the parameters in nested requests
     */
    public static final ConfigurationParameters getDefaultParameters() {
        return defaultParameters.get();
    }

    /**
     * This will invalidate a specifically cached configuration.
     * @param key
     */
    public static final void removeConfigurationForKey(String key) {
        Logger.debug(ConfigurationService.class, "Removing configuration with key: " + key);
        serverCache.remove(key);
    }

    /**
     * This will invalidate the entire cache.
     */
    public static final void clearCache() {
        serverCache.clear();
        ipForwardCache.clear();
    }

    /**
     * This determines the number of loaded configuration variants.
     * 
     * @return
     */
    public static int getNumberOfLoadedConfigurations() {
        return serverCache.size();
    }
}