com.google.code.jconfig.ConfigurationManager.java Source code

Java tutorial

Introduction

Here is the source code for com.google.code.jconfig.ConfigurationManager.java

Source

/*
 * @(#)ConfigurationManager.java          Data: 22/gen/2011
 * 
 * 
 * Copyright 2011 Gabriele Fedeli
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package com.google.code.jconfig;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.log4j.Logger;

import com.google.code.jconfig.exception.ConfigurationParsingException;
import com.google.code.jconfig.factory.ConfigurationReaderFactory;
import com.google.code.jconfig.helper.WatchdogService;
import com.google.code.jconfig.listener.IConfigurationChangeListener;
import com.google.code.jconfig.model.ConfigurationInfo;
import com.rits.cloning.Cloner;

/**
 * <p>
 *    Allows to manage application configurations from an external file.
 * </p>
 * 
 * <p>
 *    Upon configuration exception will be maintained the last working
 *    configuration, the error message will be logged, will be sent no
 *    notification to the registered listeners. <br/>
 *    To recover this situation simply change the configuration files in order
 *    to solve the logged errors.
 * </p>
 *
 * @author Gabriele Fedeli (gabriele.fedeli@gmail.com)
 */
public class ConfigurationManager {

    private String filepath;
    private ConfigurationInfo currentConfigurationInfo;
    private Map<String, IConfigurationChangeListener> activeListeners;

    private long delay;
    private static ConfigurationManager instance;
    private static Cloner cloner = new Cloner();
    private static final Logger logger = Logger.getLogger(ConfigurationManager.class);
    private static final Executor poolExecutor = Executors.newSingleThreadExecutor();
    private static final Executor listenerPoolExecutor = Executors.newCachedThreadPool();

    private ConfigurationManager() {
    }

    private ConfigurationManager(Map<String, IConfigurationChangeListener> listeners, String filepath) {
        logger.debug("Running on machine with Java version: " + SystemUtils.JAVA_RUNTIME_VERSION);
        if (!SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_5)) {
            logger.fatal("Current Java version: " + SystemUtils.JAVA_RUNTIME_VERSION + " - NEEDED "
                    + JavaVersion.JAVA_1_5 + " or above.");
            throw new RuntimeException("You must have at leat Java 1.5 using this library.");
        }

        logger.info("******* ConfigurationManager initialization *******");
        logger.info(" -> configuration: " + filepath);
        logger.info(" -> registered listeners: " + listeners);
        logger.info("***************************************************");

        this.filepath = filepath;
        activeListeners = listeners;
        currentConfigurationInfo = new ConfigurationInfo();
    }

    /**
     * <p>
     *    Configure the configuration manager to load a configuration file.
     *    Register a map of listener, that will be invoked on configuration
     *    load.
     * </p>
     * 
     * @param listeners a map with the registered listener
     * @param filepath the real path of the configuration file
     */
    public static synchronized void configure(Map<String, IConfigurationChangeListener> listeners,
            String filepath) {
        if (instance == null) {
            instance = new ConfigurationManager(listeners, filepath);
            instance.doConfigure();
        }
    }

    /**
     * <p>
     *    Configure the configuration manager to load a configuration file and 
     *    to notify changes to to the appropriate listener.
     * </p>
     * 
     * @param listeners a map with the registered listener
     * @param filepath the real path of the configuration file
     */
    public static void configureAndWatch(Map<String, IConfigurationChangeListener> listeners, String filepath) {
        configureAndWatch(listeners, filepath, WatchdogService.DEFAULT_DELAY);
    }

    /**
     * <p>
     *    Configure the configuration manager to load a configuration file and 
     *    to notify changes to to the appropriate listener.
     * </p>
     * 
     * @param listeners a map with the registered listener
     * @param filepath the real path of the configuration file
     * @param delay the time in millis for checking configuration changes
     */
    public static synchronized void configureAndWatch(Map<String, IConfigurationChangeListener> listeners,
            String filepath, long delay) {
        if (instance == null) {
            instance = new ConfigurationManager(listeners, filepath);
            instance.delay = delay;
            instance.doConfigure();
        }
    }

    /**
     * <p>
     *    Release all resources allocated by this manager.
     * </p>
     */
    public static void shutdown() {
        logger.info("Shutdown resources");
        WatchdogService.shutdown();
    }

    /**
     * <p>
     *    Save the current configuration and send a notify only for changed ones
     *    and a new ones registered. Of course for the latter case, a listener
     *    had to be provided when calling <em>configureAndWatch<\em> method.
     * </p>
     * 
     * @throws ConfigurationException
     */
    public void doConfigure() {
        Runnable runnable = new Runnable() {
            public void run() {
                try {
                    ConfigurationInfo newConfigurationInfo = ConfigurationReaderFactory.read(filepath);
                    Map<String, Object> confToBeNotified = new HashMap<String, Object>();
                    Map<String, Object> curConfMap = currentConfigurationInfo.getConfigurationMap();
                    if (newConfigurationInfo != null) {
                        for (Entry<String, Object> aNewConfEntry : newConfigurationInfo.getConfigurationMap()
                                .entrySet()) {
                            String key = aNewConfEntry.getKey();
                            Object theConf = aNewConfEntry.getValue();
                            /* only for new conf or changed configurations will be send a notify */
                            if ((curConfMap.containsKey(key)
                                    && !EqualsBuilder.reflectionEquals(theConf, curConfMap.get(key), false))
                                    || !curConfMap.containsKey(key)) {
                                confToBeNotified.put(key, theConf);
                            }
                        }

                        /* clear the old infos, put in the new ones cloned then release resources of the reader */
                        currentConfigurationInfo.clear();
                        currentConfigurationInfo.add(newConfigurationInfo);
                        newConfigurationInfo.clear();
                        /* notify changes to listener */
                        notifyListeners(confToBeNotified);
                        /* start watch on files */
                        WatchdogService.watch(instance, currentConfigurationInfo.getConfFileList(), delay);
                    }
                } catch (ConfigurationParsingException e) {
                    logger.error(e.getMessage(), e);
                    WatchdogService.watch(instance, e.getFileParsedList(), delay);
                }
            }
        };

        poolExecutor.execute(runnable);
    }

    private void notifyListeners(Map<String, Object> confToBeNotified) {
        logger.debug("Notify listeners about configuration changes");
        if (confToBeNotified != null) {
            for (Entry<String, Object> aConfEntry : confToBeNotified.entrySet()) {
                final String key = aConfEntry.getKey();
                final Object confData = aConfEntry.getValue();
                final IConfigurationChangeListener listener = activeListeners.get(key);
                if ((listener != null) && (confData != null)) {
                    try {
                        logger.debug("Notifying listener <" + listener.getClass().getName()
                                + "> for configuratio id <" + key + ">");
                        Runnable runnable = new Runnable() {
                            public void run() {
                                listener.loadConfiguration(cloner.deepClone(confData));
                            }
                        };

                        listenerPoolExecutor.execute(runnable);

                    } catch (Throwable e) {
                        // for not terminating the watchdog thread on configuration changes.
                        logger.error("Received an uncaught exception", e);
                    }
                }
            }
        }
    }
}