de.uniluebeck.itm.spyglass.core.ConfigStore.java Source code

Java tutorial

Introduction

Here is the source code for de.uniluebeck.itm.spyglass.core.ConfigStore.java

Source

/*
 * ------------------------------------------------------------------------------ -- This file is
 * part of the WSN visualization framework SpyGlass. Copyright (C) 2004-2007 by the SwarmNet
 * (www.swarmnet.de) project SpyGlass is free software; you can redistribute it and/or modify it
 * under the terms of the BSD License. Refer to spyglass-licence.txt file in the root of the
 * SpyGlass source tree for further details. ----------------------------------------------
 * ----------------------------------
 */
package de.uniluebeck.itm.spyglass.core;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;

import org.apache.commons.lang.SystemUtils;
import org.apache.log4j.Logger;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.load.Persister;

import de.uniluebeck.itm.spyglass.gui.configuration.PropertyBean;
import de.uniluebeck.itm.spyglass.io.PacketReader;
import de.uniluebeck.itm.spyglass.io.PacketRecorder;
import de.uniluebeck.itm.spyglass.plugin.Plugin;
import de.uniluebeck.itm.spyglass.plugin.PluginFactory;
import de.uniluebeck.itm.spyglass.plugin.PluginListChangeListener;
import de.uniluebeck.itm.spyglass.plugin.PluginManager;
import de.uniluebeck.itm.spyglass.util.SpyglassLoggerFactory;

// ------------------------------------------------------------------------------
// --
/**
 * Instances of this class are used to load and store the spyglass configuration from and into the
 * local file system, respectively.<br>
 * Additionally, configuration parameters can be loaded and stored partially.
 *
 * @author Sebastian Ebers
 * @author Dariush Forouher
 *
 */
public class ConfigStore extends PropertyBean {

    private final File CONFIG_DIR = new File(SystemUtils.USER_HOME + File.separator + ".spyglass");

    private final File CONFIG_FILE = new File(CONFIG_DIR, "spyglass-config.xml");

    // --------------------------------------------------------------------------------
    /**
     * Thread, which is responsible for storing into the default config file.
     *
     * @author Dariush Forouher
     *
     */
    protected final class AsyncStoreRunnable implements Runnable {

        @SuppressWarnings("synthetic-access")
        @Override
        public void run() {

            log.debug("Async-Store thread started.");

            while (!Thread.currentThread().isInterrupted()) {
                try {

                    // wait until we're notified
                    synchronized (storePendingMutex) {
                        if (!storePending) {
                            storePendingMutex.wait();
                        }
                    }
                    log.debug("Woke up, waiting another second.");

                    // sleep for one second to wait for other calls which have not to be
                    // processed for that reason
                    Thread.sleep(1000);

                } catch (final InterruptedException e) {
                    Thread.currentThread().interrupt();

                } finally {

                    synchronized (storePendingMutex) {
                        // allow new store requests to be made
                        storePending = false;
                        storeSync(CONFIG_FILE);
                    }

                }
            }
        }
    }

    /** An object which is used for logging errors and other events */
    protected static Logger log = SpyglassLoggerFactory.getLogger(ConfigStore.class);

    /** Current instance of a SpyGlass configuration object */
    private final SpyglassConfiguration spyglassConfig;

    /** true, if the config should be written to file. */
    protected volatile boolean storePending = false;

    /**
     * Mutual exclusion object used to prevent simultaneous storing operations
     */
    protected final Object storePendingMutex = new Object();

    /** Thread for asynchronous stores */
    private final Thread storeThread = new Thread(new AsyncStoreRunnable(), "ConfigStore-Thread");

    // --------------------------------------------------------------------------
    /**
     * Listener for changes in the plug-in list
     */
    private final PluginListChangeListener pluginManagerListener = new PluginListChangeListener() {

        @Override
        public void pluginListChanged(final Plugin p, final ListChangeEvent what) {
            switch (what) {
            case NEW_PLUGIN:
                p.getXMLConfig().addPropertyChangeListener(pluginPropertyListener);
                store();
                break;
            case PLUGIN_REMOVED:
                p.getXMLConfig().removePropertyChangeListener(pluginPropertyListener);
                store();
                break;
            default:
                store();
            }

        }

    };

    // --------------------------------------------------------------------------
    /**
     * Listener for changes in the configuration of an plug-in.<br>
     */
    PropertyChangeListener pluginPropertyListener = new PropertyChangeListener() {

        @Override
        public void propertyChange(final PropertyChangeEvent evt) {
            store();
        }
    };

    // --------------------------------------------------------------------------
    /**
     * Reads the configuration from an hard-coded standard-path (which is stored internally in this
     * class)
     *
     * @throws Exception if anything goes wrong
     *
     */
    public ConfigStore() throws Exception {
        final File f = CONFIG_FILE;

        boolean newFile = false;
        // create the file if necessary
        if (!f.exists()) {
            if (!f.getParentFile().exists() && !f.getParentFile().mkdirs()) {
                throw new IOException("Could not create directory " + f.getParent());
            }
            if (!f.createNewFile()) {
                throw new IOException("Could not create config file " + f.getAbsolutePath());
            }
            newFile = true;
        }

        // no point in continuing here
        if (!f.canWrite()) {
            throw new IOException("Cannot open file " + f + " for writing!");
        }

        SpyglassConfiguration newConfig = null;

        if (!newFile) {

            try {
                newConfig = ConfigStore.load(f);
            } catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (final Exception e) {
                log.error(
                        "Unable to load configuration input:\r\nThe configuration file '" + f
                                + "' is invalid! I'll move" + " the broken one away and start with a fresh config",
                        e);

                final boolean ret = f
                        .renameTo(new File(f.getAbsolutePath() + ".save-" + System.currentTimeMillis()));
                if (!ret) {
                    throw new IOException("Could not move config file out of the way. Stopping.", e);
                }
            }
        }

        // create a new config, if none exists
        if (newConfig == null) {

            newConfig = new SpyglassConfiguration();

            store();
        }

        spyglassConfig = newConfig;

        registerListener();

        storeThread.start();

    }

    private void registerListener() {
        spyglassConfig.getPluginManager().addPluginListChangeListener(pluginManagerListener);
        spyglassConfig.getGeneralSettings().addPropertyChangeListener(pluginPropertyListener);
        spyglassConfig.getGeneralSettings().getMetrics().addPropertyChangeListener(pluginPropertyListener);
        for (final Plugin p : spyglassConfig.getDefaultPlugins()) {
            p.getXMLConfig().addPropertyChangeListener(pluginPropertyListener);
        }
        for (final Plugin p : spyglassConfig.getPluginManager().getPlugins()) {
            p.getXMLConfig().addPropertyChangeListener(pluginPropertyListener);
        }
    }

    private void unregisterListener() {
        spyglassConfig.getPluginManager().removePluginListChangeListener(pluginManagerListener);
        spyglassConfig.getGeneralSettings().removePropertyChangeListener(pluginPropertyListener);
        spyglassConfig.getGeneralSettings().getMetrics().removePropertyChangeListener(pluginPropertyListener);
        for (final Plugin p : spyglassConfig.getDefaultPlugins()) {
            p.getXMLConfig().removePropertyChangeListener(pluginPropertyListener);
        }
        for (final Plugin p : spyglassConfig.getPluginManager().getPlugins()) {
            p.getXMLConfig().removePropertyChangeListener(pluginPropertyListener);
        }
    }

    // --------------------------------------------------------------------------
    /**
     * @return the spyglassConfig
     */
    public SpyglassConfiguration getSpyglassConfig() {
        return spyglassConfig;
    }

    // --------------------------------------------------------------------------
    /**
     * Overwrites the current config with the one inside the given file.
     *
     * @param file the configuration file
     *
     * @throws Exception if anything goes wrong
     *
     */
    public void importConfig(final File file) throws Exception {
        if (spyglassConfig == null) {
            throw new IllegalStateException(
                    "Cannot import a configuration when we have" + "no working base configuration yet.");
        }

        final SpyglassConfiguration sgc = ConfigStore.load(file);

        /* get rid of the current configuration, listeners and plug-ins */

        log.debug("Unregistering old listener.");
        // unregister the old ones
        unregisterListener();

        log.debug("Stopping packet reader.");
        final PacketReader pr = spyglassConfig.getPacketReader();
        if (pr instanceof PacketRecorder) {
            ((PacketRecorder) pr).setReadFromFile(false);
            ((PacketRecorder) pr).setRecording(false);
        }

        /* now activate the new configuration */

        log.info("Overwriting the current configuration with the new one.");
        spyglassConfig.overwriteWith(sgc);

        log.debug("Registering new listener.");
        // register the new ones
        registerListener();

        log.debug("Finished with importing config. Yeah!");

    }

    // --------------------------------------------------------------------------
    /**
     * Exports the config into the given file
     *
     * Note: Do this synchronized, since users generally don't mind waiting on this occasions (lots
     * of programs hang when saving their files after explicitly been commanded by the user).
     *
     * @param file
     *            the export operation's target
     */
    public void exportConfig(final File file) {
        storeSync(file);
    }

    // --------------------------------------------------------------------------
    /**
     * Loads and returns the configuration from a file
     *
     * @param configFile the file containing the configuration data
     *
     * @throws Exception if anything goes wrong
     *
     * @return a valid SpyglassConfiguration
     */
    private static SpyglassConfiguration load(final File configFile) throws Exception {
        log.debug("Initializing. Reading config from file: " + configFile);

        if (!configFile.isFile()) {
            throw new IOException("Can't find config file '" + configFile + "'");
        }

        final Serializer serializer = new Persister();
        final SpyglassConfiguration config = serializer.read(SpyglassConfiguration.class, configFile);

        // if there is a default config missing, add it
        for (final Class<? extends Plugin> clazz : PluginManager.getAvailablePluginTypes()) {
            if (config.getDefaultConfig(clazz) == null) {
                final ArrayList<Plugin> set = new ArrayList<Plugin>(config.getDefaultPlugins());
                set.add(PluginFactory.createDefaultInstance(clazz));
                config.setDefaultPlugins(set);
            }
        }

        return config;
    }

    // --------------------------------------------------------------------------
    /**
     * Store the config into the system-given file.
     *
     * Stores the configuration in an extra {@link Thread} to improve the performance.<br>
     * The storing operation will be delayed for one second. Every call received within this second
     * will be ignored.
     */
    public void store() {
        synchronized (storePendingMutex) {
            // log.debug("Waking up async-store thread.");
            this.storePending = true;
            this.storePendingMutex.notifyAll();
        }
    }

    // --------------------------------------------------------------------------
    /**
     * Stores the configuration persistently into the given file.
     *
     * This method is synchronized to avoid storing into the same file concurrently.
     *
     * @param configFile
     *            the file which will contain the configuration data afterwards
     */
    protected synchronized void storeSync(final File configFile) {

        log.debug("Storing config to file: " + configFile);

        final StringWriter buf = new StringWriter();

        try {

            // If something happens here, the file will never have been opened.
            new Persister().write(spyglassConfig, buf);

            final FileWriter writer = new FileWriter(configFile);
            writer.write(buf.toString());
            writer.close();

        } catch (final ConcurrentModificationException e) {
            // Note: In theory this may lead to StackOverflow. But since under normal circumstances
            // the chances of hitting this Exception are already pretty small, we probably can risk
            // it.
            log.debug("Configuration was modified while the ConfigStore tried to store the config. Trying again...",
                    e);
            storeSync(configFile);
            return;
        } catch (final IOException e) {
            log.error("Unable to store configuration output: Error while writing the file!", e);
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (final Exception e) {
            log.error("Unable to store configuration output: " + e.getMessage(), e);
        }

        log.debug("Stored config to file: " + configFile + ".");

    }

    // --------------------------------------------------------------------------
    /**
     * Notify the ConfigStore that Spyglass is shutting down and thus we should not accept any mre
     * store requests.
     *
     * This method blocks until async-store thread has died.
     *
     * @throws InterruptedException
     *             if the executing thread is interrupted while this method is called.
     */
    public void shutdown() throws InterruptedException {
        storeThread.interrupt();
        storeThread.join();
    }

}