de.fu_berlin.inf.dpp.Saros.java Source code

Java tutorial

Introduction

Here is the source code for de.fu_berlin.inf.dpp.Saros.java

Source

/*
 * DPP - Serious Distributed Pair Programming
 * (c) Freie Universitt Berlin - Fachbereich Mathematik und Informatik - 2006
 * (c) Riad Djemili - 2006
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package de.fu_berlin.inf.dpp;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.List;
import java.util.Properties;
import java.util.Random;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.helpers.LogLog;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.equinox.security.storage.ISecurePreferences;
import org.eclipse.equinox.security.storage.SecurePreferencesFactory;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

import de.fu_berlin.inf.dpp.account.XMPPAccountStore;
import de.fu_berlin.inf.dpp.annotations.Component;
import de.fu_berlin.inf.dpp.communication.connection.ConnectionHandler;
import de.fu_berlin.inf.dpp.editor.annotations.SarosAnnotation;
import de.fu_berlin.inf.dpp.editor.colorstorage.UserColorID;
import de.fu_berlin.inf.dpp.misc.pico.DotGraphMonitor;
import de.fu_berlin.inf.dpp.preferences.PreferenceConstants;
import de.fu_berlin.inf.dpp.preferences.PreferenceUtils;
import de.fu_berlin.inf.dpp.project.ISarosSessionManager;
import de.fu_berlin.inf.dpp.stf.server.STFController;
import de.fu_berlin.inf.dpp.util.StackTrace;
import de.fu_berlin.inf.dpp.util.ThreadUtils;
import de.fu_berlin.inf.dpp.versioning.VersionManager;

/**
 * The main plug-in of Saros.
 * 
 * @author rdjemili
 * @author coezbek
 */
@Component(module = "core")
public class Saros extends AbstractUIPlugin {

    /**
     * @JTourBusStop 1, Some Basics:
     * 
     *               This class manages the lifecycle of the Saros plug-in,
     *               contains some important supporting data members and
     *               provides methods for the integration of Saros into Eclipse.
     * 
     *               Browse the data members. Some are quite obvious (version,
     *               feature etc.) some need closer examination.
     * 
     */

    /**
     * The single instance of the Saros plugin.
     */
    protected static Saros plugin;

    /**
     * True if the Saros instance has been initialized so that calling
     * reinject() will be well defined.
     */
    protected static boolean isInitialized;

    /**
     * This is the Bundle-SymbolicName (a.k.a the pluginID)
     */
    public static final String SAROS = "de.fu_berlin.inf.dpp"; //$NON-NLS-1$

    /**
     * The name of the XMPP namespace used by Saros. At the moment it is only
     * used to advertise the Saros feature in the Service Discovery.
     * 
     * TODO Add version information, so that only compatible versions of Saros
     * can use each other.
     */
    public final static String NAMESPACE = SAROS;

    /**
     * Sub-namespace for the server. It is used advertise when a server is
     * active.
     */
    public static final String NAMESPACE_SERVER = NAMESPACE + ".server"; //$NON-NLS-1$

    /**
     * The name of the resource identifier used by Saros when connecting to the
     * XMPP server (for instance when logging in as john@doe.com, Saros will
     * connect using john@doe.com/Saros)
     * 
     * @deprecated Do not use this resource identifier to build a fully
     *             qualified Jabber identifier, e.g the logic connects to a XMPP
     *             server as foo@bar/Saros but the assigned Jabber identifier
     *             may be something like foo@bar/Saros765E18ED !
     */
    @Deprecated
    public final static String RESOURCE = "Saros"; //$NON-NLS-1$

    private static final String VERSION_COMPATIBILITY_PROPERTY_FILE = "version.comp"; //$NON-NLS-1$

    private String sarosVersion;

    private String sarosFeatureID;

    private ISarosSessionManager sessionManager;

    private PreferenceUtils preferenceUtils;

    private ConnectionHandler connectionHandler;

    /**
     * To print an architecture diagram at the end of the plug-in life-cycle
     * initialize the dotMonitor with a new instance:
     * 
     * <code>dotMonitor= new DotGraphMonitor();</code>
     */
    protected DotGraphMonitor dotMonitor;

    /**
     * @JTourBusStop 2, Some Basics:
     * 
     *               Preferences are managed by Eclipse-provided classes. Most
     *               are kept by Preferences, but some sensitive data (like user
     *               account data) is kept in a SecurePreference.
     * 
     *               If you press Ctrl+Shift+R and type in "*preference*" you
     *               will see every class in Saros that deals with preferences.
     *               Classes named "*PreferencePage" implement individual pages
     *               within the Eclipse preferences's Saros section. Preference
     *               labels go in PreferenceConstants.java.
     */

    /**
     * The global plug-in preferences, shared among all workspaces. Should only
     * be accessed over {@link #getGlobalPreferences()} from outside this class.
     */
    protected Preferences configPrefs;

    /**
     * The secure preferences store, used to store sensitive data that may (at
     * the user's option) be stored encrypted.
     */
    protected ISecurePreferences securePrefs;

    protected Logger log;

    /**
     * @JTourBusStop 4, Invitation Process:
     * 
     *               If you haven't already read about PicoContainer, stop and
     *               do so now (http://picocontainer.codehaus.org).
     * 
     *               Saros uses PicoContainer to manage dependencies on our
     *               behalf. The SarosContext class encapsulates our usage of
     *               PicoContainer. It's a well documented class, so take a look
     *               at it.
     */

    protected SarosContext sarosContext;

    /**
     * Create the shared instance.
     */
    public Saros() {

        try {
            InputStream sarosProperties = Saros.class.getClassLoader().getResourceAsStream("saros.properties"); //$NON-NLS-1$

            if (sarosProperties == null) {
                LogLog.warn("could not initialize Saros properties because the 'saros.properties'"
                        + " file could not be found on the current JAVA class path");
            } else {
                System.getProperties().load(sarosProperties);
                sarosProperties.close();
            }
        } catch (Exception e) {
            LogLog.error("could not load saros property file 'saros.properties'", e); //$NON-NLS-1$
        }

        // Only start a DotGraphMonitor if asserts are enabled (aka debug mode)
        assert (dotMonitor = new DotGraphMonitor()) != null;

        setInitialized(false);
        setDefault(this);
    }

    protected static void setInitialized(boolean initialized) {
        isInitialized = initialized;
    }

    protected static void checkInitialized() {
        if (plugin == null || !isInitialized()) {
            LogLog.error("Saros not initialized", new StackTrace());
            throw new IllegalStateException();
        }
    }

    /**
     * Returns true if the Saros instance has been initialized so that calling
     * {@link SarosContext#reinject(Object)} will be well defined.
     */
    public static boolean isInitialized() {
        return isInitialized;
    }

    /**
     * This method is called upon plug-in activation
     */
    @Override
    public void start(BundleContext context) throws Exception {

        super.start(context);

        setupLoggers();

        sarosVersion = getBundle().getVersion().toString();

        log.info("Starting Saros " + sarosVersion + " running:\n" + getPlatformInfo());

        sarosContext = new SarosContext(new SarosEclipseContextFactory(this, new SarosCoreContextFactory()),
                dotMonitor);

        SarosPluginContext.setSarosContext(sarosContext);

        sarosFeatureID = SAROS + "_" + sarosVersion; //$NON-NLS-1$

        // Remove the Bundle if an instance of it was already registered
        sarosContext.removeComponent(Bundle.class);
        sarosContext.addComponent(Bundle.class, getBundle());

        connectionHandler = sarosContext.getComponent(ConnectionHandler.class);
        sessionManager = sarosContext.getComponent(ISarosSessionManager.class);
        preferenceUtils = sarosContext.getComponent(PreferenceUtils.class);

        initVersionCompatibilityChart(VERSION_COMPATIBILITY_PROPERTY_FILE,
                sarosContext.getComponent(VersionManager.class));

        // Make sure that all components in the container are
        // instantiated
        sarosContext.getComponents(Object.class);

        isInitialized = true;

        /*
         * If other colors than the ones we support are set in the
         * PreferenceStore, overwrite them
         */
        SarosAnnotation.resetColors();

        /*
         * Hack for MARCH 2013 release, ensure a good favorite color
         * distribution for upgrading clients
         */

        int favoriteColorID = preferenceUtils.getFavoriteColorID();

        if (!UserColorID.isValid(favoriteColorID)
                && getPreferenceStore().getBoolean("FAVORITE_COLOR_ID_HACK_CREATE_RANDOM_COLOR")) {
            favoriteColorID = new Random().nextInt(SarosAnnotation.SIZE);
            log.debug("autogenerated favorite color id is: " + favoriteColorID);
            getPreferenceStore().setValue(PreferenceConstants.FAVORITE_SESSION_COLOR_ID, favoriteColorID);
        }

        getPreferenceStore().setValue("FAVORITE_COLOR_ID_HACK_CREATE_RANDOM_COLOR", false);

        convertAccountStore();
    }

    /**
     * This method is called when the plug-in is stopped
     */
    @Override
    public void stop(BundleContext context) throws Exception {

        // TODO Devise a general way to stop and dispose our components
        saveGlobalPreferences();
        saveSecurePrefs();

        if (dotMonitor != null) {
            File file = ResourcesPlugin.getWorkspace().getRoot().getLocation().toFile();
            file = new File(file, ".metadata"); //$NON-NLS-1$
            file = new File(file, "saros-" + sarosFeatureID + ".dot"); //$NON-NLS-1$ //$NON-NLS-2$
            log.info("Saving Saros architecture diagram dot file: " + file.getAbsolutePath());
            dotMonitor.save(file);
        }

        try {
            Thread shutdownThread = ThreadUtils.runSafeAsync("ShutdownProcess", log, new Runnable() { //$NON-NLS-1$
                @Override
                public void run() {

                    try {

                        sessionManager.stopSarosSession();
                        connectionHandler.disconnect();
                    } finally {
                        /*
                         * Always shutdown the network to ensure a proper
                         * cleanup(currently only UPNP)
                         */

                        /*
                         * This will cause dispose() to be called on all
                         * components managed by PicoContainer which
                         * implement {@link Disposable}.
                         */
                        sarosContext.dispose();
                    }
                }
            });

            shutdownThread.join(10000);
            if (shutdownThread.isAlive())
                log.error("could not shutdown Saros gracefully");

        } finally {
            super.stop(context);
        }

        isInitialized = false;
        setDefault(null);
    }

    public static void setDefault(Saros newPlugin) {
        Saros.plugin = newPlugin;

    }

    /**
     * Returns the global {@link Preferences} with {@link ConfigurationScope}
     * for this plug-in or null if the node couldn't be determined. <br>
     * <br>
     * The returned Preferences can be accessed concurrently by multiple threads
     * of the same JVM without external synchronization. If they are used by
     * multiple JVMs no guarantees can be made concerning data consistency (see
     * {@link Preferences} for details).
     * 
     * @return the preferences node for this plug-in containing global
     *         preferences that are visible for all workspaces of this eclipse
     *         installation
     */
    public synchronized Preferences getGlobalPreferences() {
        // TODO Singleton-Pattern code smell: ConfigPrefs should be a @component
        if (configPrefs == null) {
            configPrefs = new ConfigurationScope().getNode(SAROS);
        }
        return configPrefs;
    }

    /**
     * Saves the global preferences to disk. Should be called at least before
     * the bundle is stopped to prevent loss of data. Can be called whenever
     * found necessary.
     */
    public synchronized void saveGlobalPreferences() {
        /*
         * Note: If multiple JVMs use the config preferences and the underlying
         * backing store, they might not always work with latest data, e.g. when
         * using multiple instances of the same eclipse installation.
         */
        if (configPrefs != null) {
            try {
                configPrefs.flush();
            } catch (BackingStoreException e) {
                log.error("Couldn't store global plug-in preferences", e);
            }
        }
    }

    protected void setupLoggers() {
        /*
         * HACK this is not the way OSGi works but it currently fulfill its
         * purpose
         */
        final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

        try {
            // change the context class loader so Log4J will find the appenders
            Thread.currentThread().setContextClassLoader(STFController.class.getClassLoader());

            PropertyConfigurator.configure(Saros.class.getClassLoader().getResource("saros.log4j.properties")); //$NON-NLS-1$
        } catch (RuntimeException e) {
            System.err.println("initializing log support failed"); //$NON-NLS-1$
            e.printStackTrace();
        } finally {
            Thread.currentThread().setContextClassLoader(contextClassLoader);
        }

        log = Logger.getLogger("de.fu_berlin.inf.dpp"); //$NON-NLS-1$

    }

    /**
     * Returns a string representing the Saros Version number for instance
     * "9.5.7.r1266"
     * 
     * This method only returns a valid version string after the plugin has been
     * started.
     * 
     * This is equivalent to the bundle version.
     */
    public String getVersion() {
        return sarosVersion;
    }

    /**
     * @deprecated Only of one-time use to convert from the old, IDE-dependent
     *             format to the new, IDE-independent format. Will be removed in
     *             Release n+2
     */
    @Deprecated
    public void convertAccountStore() {

        final XMPPAccountStore accountStore = sarosContext.getComponent(XMPPAccountStore.class);

        if (!accountStore.isEmpty()) {
            log.debug("skipping conversion of old XMPP accounts, because there are already new ones");
            return;
        }

        try {
            de.fu_berlin.inf.dpp.accountManagement.XMPPAccountStore oldStore = new de.fu_berlin.inf.dpp.accountManagement.XMPPAccountStore(
                    getPreferenceStore(), getSecurePrefs());

            if (oldStore.isEmpty())
                return;

            de.fu_berlin.inf.dpp.accountManagement.XMPPAccount oldActiveAccount;

            oldActiveAccount = oldStore.getActiveAccount();

            List<de.fu_berlin.inf.dpp.accountManagement.XMPPAccount> accounts = oldStore.getAllAccounts();

            accounts.remove(oldActiveAccount);
            accounts.add(0, oldActiveAccount);

            for (de.fu_berlin.inf.dpp.accountManagement.XMPPAccount account : accounts) {
                log.debug("converting old account to new one: " + account);

                try {
                    accountStore.createAccount(account.getUsername(), account.getPassword(), account.getDomain(),
                            account.getServer(), account.getPort(), account.useTLS(), account.useSASL());
                } catch (RuntimeException e) {
                    log.error("failed to convert old account: " + account, e);
                }
            }

        } catch (RuntimeException e) {
            log.error("failed to convert old account store", e);
        }
    }

    /**
     * @deprecated remove after next release
     */
    @Deprecated
    private synchronized ISecurePreferences getSecurePrefs() {

        if (securePrefs == null) {
            try {
                File storeFile = new File(getStateLocation().toFile(), "/.pref"); //$NON-NLS-1$
                URI workspaceURI = storeFile.toURI();

                /*
                 * The SecurePreferencesFactory does not accept percent-encoded
                 * URLs, so we must decode the URL before passing it.
                 */
                String prefLocation = URLDecoder.decode(workspaceURI.toString(), "UTF-8"); //$NON-NLS-1$
                URL prefURL = new URL(prefLocation);

                securePrefs = SecurePreferencesFactory.open(prefURL, null);
            } catch (MalformedURLException e) {
                log.error("Problem with URL when attempting to access secure preferences: " + e);
            } catch (IOException e) {
                log.error("I/O problem when attempting to access secure preferences: " + e);
            } finally {
                if (securePrefs == null)
                    securePrefs = SecurePreferencesFactory.getDefault();
            }
        }

        return securePrefs;
    }

    /**
     * @deprecated remove after next release
     */
    @Deprecated
    private synchronized void saveSecurePrefs() {
        try {
            if (securePrefs != null) {
                securePrefs.flush();
            }
        } catch (IOException e) {
            log.error("Exception when trying to store secure preferences: " + e);
        }
    }

    private void initVersionCompatibilityChart(final String filename, final VersionManager versionManager) {

        if (versionManager == null) {
            log.error("no version manager component available");
            return;
        }

        final InputStream in = VersionManager.class.getClassLoader().getResourceAsStream(filename);

        final Properties chart = new Properties();

        if (in == null) {
            log.warn("could not find compatibility property file: " + filename);
            return;
        }

        try {
            chart.load(in);
        } catch (IOException e) {
            log.warn("could not read compatibility property file: " + filename, e);

            return;
        } finally {
            IOUtils.closeQuietly(in);
        }

        versionManager.setCompatibilityChart(chart);
    }

    private String getPlatformInfo() {

        String javaVersion = System.getProperty("java.version", "Unknown Java Version");
        String javaVendor = System.getProperty("java.vendor", "Unknown Vendor");
        String os = System.getProperty("os.name", "Unknown OS");
        String osVersion = System.getProperty("os.version", "Unknown Version");
        String hardware = System.getProperty("os.arch", "Unknown Architecture");

        StringBuilder sb = new StringBuilder();

        sb.append("  Java Version: " + javaVersion + "\n");
        sb.append("  Java Vendor: " + javaVendor + "\n");
        sb.append("  Eclipse Runtime Version: "
                + Platform.getBundle("org.eclipse.core.runtime").getVersion().toString() + "\n");
        sb.append("  Operating System: " + os + " (" + osVersion + ")\n");
        sb.append("  Hardware Architecture: " + hardware);

        return sb.toString();
    }
}