com.salas.bb.core.ApplicationLauncher.java Source code

Java tutorial

Introduction

Here is the source code for com.salas.bb.core.ApplicationLauncher.java

Source

// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// 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 2 of the License, 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., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: ApplicationLauncher.java,v 1.240 2008/12/25 07:49:29 spyromus Exp $
//

package com.salas.bb.core;

import com.jgoodies.looks.*;
import com.jgoodies.looks.plastic.PlasticLookAndFeel;
import com.jgoodies.uif.AbstractFrame;
import com.jgoodies.uif.application.Application;
import com.jgoodies.uif.application.ApplicationConfiguration;
import com.jgoodies.uif.application.ApplicationDescription;
import com.jgoodies.uif.application.ResourceIDs;
import com.jgoodies.uif.splash.ImageSplash;
import com.jgoodies.uif.splash.Splash;
import com.jgoodies.uif.splash.SplashProvider;
import com.jgoodies.uif.util.ResourceUtils;
import com.jgoodies.uif.util.SystemUtils;
import com.jgoodies.uifextras.convenience.DefaultApplicationStarter;
import com.jgoodies.uifextras.convenience.SetupManager;
import com.limegroup.gnutella.gui.GURLHandler;
import com.salas.bb.core.actions.ActionsTable;
import com.salas.bb.core.actions.EDTLockupHandler;
import com.salas.bb.core.actions.EDTOverloadReporter;
import com.salas.bb.domain.prefs.UserPreferences;
import com.salas.bb.installation.Installer;
import com.salas.bb.installation.wizard.WelcomePage;
import com.salas.bb.persistence.IPersistenceManager;
import com.salas.bb.persistence.PersistenceException;
import com.salas.bb.persistence.PersistenceManagerConfig;
import com.salas.bb.plugins.Manager;
import com.salas.bb.service.ServicePreferences;
import com.salas.bb.utils.*;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.ipc.IPC;
import com.salas.bb.utils.locker.Locker;
import com.salas.bb.utils.net.auth.CachingAuthenticator;
import com.salas.bb.utils.net.auth.IPasswordsRepository;
import com.salas.bb.utils.osx.OSXSupport;
import com.salas.bb.utils.uif.NoFlickerSplashWrapper;
import com.salas.bb.utils.uif.TipOfTheDay;
import com.salas.bb.utils.uif.UifUtilities;
import com.salas.bb.utils.uif.images.Cache;
import com.salas.bb.utils.uif.images.ImageFetcher;
import com.salas.bb.utils.watchdogs.EventQueueWithWD;
import com.salas.bb.views.mainframe.MainFrame;
import com.salas.bb.views.stylesheets.StylesheetManager;
import com.salas.bbutilities.VersionUtils;
import org.apache.xmlrpc.XmlRpc;
import sun.net.NetworkClient;

import javax.net.ssl.*;
import javax.swing.*;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.ProxySelector;
import java.net.URL;
import java.nio.channels.FileLock;
import java.security.Permission;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

/**
 * This is the class that starts and runs everything in Blog Bridge.
 */
public class ApplicationLauncher extends DefaultApplicationStarter {
    public static boolean proVersion = false;

    private static final Logger LOG = Logger.getLogger(ApplicationLauncher.class.getName());

    private static final String MINIMAL_COMPATIBLE_VERSION = "1.9";

    /** Current version. */
    private static final String CURRENT_VERSION = "6.6.1";

    private static final int DEFAULT_ICON_WIDTH = 18;
    private static final int DEFAULT_ICON_HEIGHT = 18;
    private static final int TOOLBAR_SEP_WIDTH = 6;
    private static final int TOOLBAR_SEP_HEIGHT = 18;

    private static final int LOGGING_LIMIT = 50000;
    private static final int LOGGING_LOOP_COUNT = 5;

    /**
     * Properties key for installation ID.
     * @noinspection HardCodedStringLiteral
     */
    public static final String KEY_INSTALLATION_ID = "InstallationID";

    /**
     * Properties key for installation version.
     * @noinspection HardCodedStringLiteral
     */
    public static final String KEY_INSTALLATION_VERSION = "InstallationVersion";

    /**
     * Properties key for number of runs.
     * @noinspection HardCodedStringLiteral
     */
    private static final String KEY_RUNS = "Runs";

    /**
     * Report errors flag. System property key.
     * @noinspection HardCodedStringLiteral
     */
    private static final String KEY_REPORT_ERRORS = "report.errors";

    /**
     * Logging config file.
     * @noinspection HardCodedStringLiteral
     */
    private static final String KEY_LOGGING_CONFIG_FILE = "java.util.logging.config.file";

    /**
     * Bundled logging properties resource.
     * @noinspection HardCodedStringLiteral
     */
    private static final String RESOURCE_LOGGING_PROPERTIES = "logging.properties";

    /**
     * Weekly release type name.
     * @noinspection HardCodedStringLiteral
     */
    private static final String RELEASE_TYPE_WEEKLY = "weekly";

    /** Name of backups directory within user's BB directory. */
    private static final String BACKUPS_DIR_NAME = "backups";

    private static final String MSG_SPLASH_ENSURE_ERROR = "Wasn't able to manipulate splash screen visiblity.";
    private static final String MSG_PARENT_APP_LAUNCHER_ERROR = "Failed to call parent app launcher.";

    private static ApplicationLauncher globalAppLauncher;

    private static String releaseType;
    private static String prefix;
    private static boolean usingFinalData;

    private static ApplicationDescription description;
    private static ApplicationConfiguration configuration;

    private static ConnectionState connectionState;
    private static String contextPath;

    private static FileLock instanceLock;
    private static IPC ipc;
    private static File socketFile;
    private static String urlToOpen;

    static {
        connectionState = new ConnectionState();
    }

    /**
     * Returns connection state object.
     *
     * @return connection state object.
     */
    public static ConnectionState getConnectionState() {
        return connectionState;
    }

    /**
     * Returns the URL to open when the initialization is finished.
     *
     * @return URL specified in the command-line or <code>NULL</code>.
     */
    public static String getURLToOpen() {
        return urlToOpen;
    }

    /**
     * Configure LOG handling to send all Log messages of Warning or worse to the default LOG file.
     * 1. if -D java.util.logging.config.file is set, use that as the log configuration
     * 2. if not then locate logging.properties in classpath otherwise error.
     * <p/>
     * Keep LOGGING_LOOP_COUNT console.* files around
     */
    protected void configureLogging() {
        // Read configuration from specified file or default resource
        LogManager lm = LogManager.getLogManager();
        String fname = System.getProperty(KEY_LOGGING_CONFIG_FILE);

        boolean inited = false;
        if (fname != null && new File(fname).exists()) {
            try {
                // default configuration reader will examine the property and load configuration
                lm.readConfiguration();
                inited = true;
            } catch (IOException e) {
                System.err.println(Strings.error("logging.using.overriden.configuration.errored"));
                e.printStackTrace();
            }
        }

        // If configuration still not read use internal production configuration
        if (!inited) {
            final InputStream url = this.getClass().getClassLoader()
                    .getResourceAsStream(RESOURCE_LOGGING_PROPERTIES);
            try {
                lm.readConfiguration(url);
            } catch (IOException e) {
                System.err.println(Strings.error("logging.unable.to.use.production.configuration"));
                e.printStackTrace();
            }
        }

        Logger aLogger = Logger.getLogger("");

        // Setup file logger
        try {
            String pattern = getDefaultLogFilePattern();
            ensureParentDirectoryExists(pattern);
            FileHandler handler = new FileHandler(pattern, LOGGING_LIMIT, LOGGING_LOOP_COUNT);
            handler.setFormatter(new TinyFormatter());
            aLogger.addHandler(handler);
        } catch (IOException e) {
            System.err.println(
                    MessageFormat.format(Strings.error("logging.failed.to.configure"), e.getLocalizedMessage()));
        }

        // Setup reporting logger (can't use logging.properties as this handler is not
        // on the system path under JWS) :(
        if (System.getProperty(KEY_REPORT_ERRORS) != null) {
            ReportingLogHandler reportingHandler = new ReportingLogHandler();
            reportingHandler.setLevel(Level.SEVERE);
            aLogger.addHandler(reportingHandler);
        }
    }

    /**
     * Returns <code>TRUE</code> if release type is weekly.
     *
     * @return <code>TRUE</code> if release type is weekly.
     */
    public static boolean isWeekly() {
        return RELEASE_TYPE_WEEKLY.equalsIgnoreCase(releaseType);
    }

    /**
     * Returns <code>TRUE</code> if this build uses final data directory.
     *
     * @return <code>TRUE</code> if this build uses final data directory.
     */
    public static boolean isUsingFinalData() {
        return usingFinalData;
    }

    /**
     * Returns <code>TRUE</code> if auto-updates features should be enabled.
     *
     * @return <code>TRUE</code> if auto-updates features should be enabled.
     */
    public static boolean isAutoUpdatesEnabled() {
        return usingFinalData && !BrowserLauncher.isRunningUnderJWS();
    }

    /**
     * Returns the type of release (weekly, stable, or final).
     *
     * @return the type of release.
     */
    public static String getReleaseType() {
        return releaseType;
    }

    /**
     * Returns prefix (working folder path relative to user home).
     *
     * @return prefix.
     */
    public static String getPrefix() {
        return prefix;
    }

    /**
     * Configure Log Handling so that we never display a Message Box via logger calls.
     */
    protected void addMessageHandler() {
    }

    /**
     * UIF Framework call to create Main window frame during initialization.
     *
     * @return Main Window Frame.
     */
    protected AbstractFrame createMainFrame() {
        MainFrame mainFrame = new MainFrame(connectionState);

        Application.setDefaultParentFrame(mainFrame);
        GlobalController.SINGLETON.setMainFrame(mainFrame);

        return mainFrame;
    }

    /**
     * Once only initialization for the whole application.
     */
    protected void initializeActions() {
        Splash.setNote(Strings.message("startup.registering.actions"), 60);
        ActionsTable.getInstance().registerActions(connectionState);
    }

    /**
     * Configuring general networking.
     */
    private void configureNetwork() {
        // Configure XML-RPC package to use Unicode
        XmlRpc.setEncoding("UTF-8");

        ApplicationDescription description = Application.getDescription();
        System.setProperty("http.agent", description.getProductText() + " (" + description.getVendorURL() + ") "
                + System.getProperty("java.version"));
        System.setProperty("http.agent.discoverer", description.getProductText() + " Discoverer" + " ("
                + description.getVendorURL() + ") " + System.getProperty("java.version"));

        // Create and initialize caching authenticator
        IPersistenceManager persistenceManager = PersistenceManagerConfig.getManager();
        IPasswordsRepository passwordsRepository = persistenceManager.getPasswordsRepository();
        Authenticator.setDefault(new CachingAuthenticator(passwordsRepository));

        // This is a very kludgy way to propagate default reading and connection timeouts.
        // It's the only way for now (JRE 1.4.x) to let this values be used when running under JWS.
        // NOTE: NetworkClient is not part of public API and can be removed/changed in a future.
        new NetworkClient() {
            {
                Integer readTimeoutInt = Integer.getInteger("sun.net.client.defaultReadTimeout");
                defaultSoTimeout = (readTimeoutInt == null) ? -1 : readTimeoutInt;

                Integer connectTimeoutInt = Integer.getInteger("sun.net.client.defaultConnectTimeout");
                defaultConnectTimeout = (connectTimeoutInt == null) ? -1 : connectTimeoutInt;
            }
        };

        // Sets proxy selector to use for communication
        ProxySelector.setDefault(CustomProxySelector.INSTANCE);
    }

    /**
     * Disables verification of host name.
     */
    private void disableSSLHostNameVerification() {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String string, SSLSession sslSession) {
                return true;
            }
        });
    }

    /**
     * Disables verification of SSL certificate.
     */
    private void disableSSLCertificates() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }
        } };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            if (LOG.isLoggable(Level.WARNING)) {
                LOG.log(Level.WARNING, Strings.error("ssl.probably.certificates.validation.was.not.disabled"), e);
            }
        }
    }

    // Install alternative event queue and necessary performance timers
    private void installWatchdogs() {
        final String watchdogsDisable = System.getProperty("watchdogs.disable");
        if (watchdogsDisable == null || !watchdogsDisable.equalsIgnoreCase("true")) {
            EventQueueWithWD queue = EventQueueWithWD.install();

            queue.addWatchdog(2000, new EDTOverloadReporter(Level.WARNING), false);
            queue.addWatchdog(3 * 60 * 1000, new EDTLockupHandler(), true);
        }
    }

    /** Install our custom security manager. */
    private void configureSecurityManager() {
        try {
            System.setSecurityManager(new SecurityManager() {
                /**
                 * Throws a <code>SecurityException</code> if the requested
                 * access, specified by the given permission, is not permitted based
                 * on the security policy currently in effect.
                 *
                 * @param perm the requested permission.
                 */
                public void checkPermission(Permission perm) {
                    // This implementation allows all connections to any hosts.
                    // It's necessary to fix the unknown problem with SecurityException
                    // raising when trying to fetch some images (see BT #247 GUI: Gizmodo images)
                }

                /**
                 * Throws a <code>SecurityException</code> if the
                 * specified security context is denied access to the resource
                 * specified by the given permission.
                 *
                 * @param perm    the specified permission
                 * @param context a system-dependent security context.
                 */
                public void checkPermission(Permission perm, Object context) {
                    // This implementation allows all connections to any hosts.
                    // It's necessary to fix the unknown problem with SecurityException
                    // raising when trying to fetch some images (see BT #247 GUI: Gizmodo images)
                }
            });
        } catch (SecurityException e) {
            // If we are unable to reinstall the SecurityManager, then it's better to report it
            LOG.log(Level.SEVERE, "Unable to install the security manager.", e);
        }
    }

    /** Configures GUI part. */
    protected void configureUI() {
        if (LOG.isLoggable(Level.FINE))
            LOG.fine("configureUI starting...");

        // Configure displaying anti-aliased text (SHOULD BE RUN BEFORE SWING INIT)
        configureAntiAliasing();

        // Configure Mac OS X dock icon (we had problems with JWS dock icon: ugly and cannot be badged)
        OSXSupport.setApplicationIcon();

        // Images cache
        Cache imagesCache = new Cache(new File(getContextPath() + "cache"), 20000000);
        ImageFetcher.setCache(imagesCache);

        // Stylesheets
        try {
            URL baseStylesheetURL = new URL("http://www.blogbridge.com/bbstyles/");
            StylesheetManager.init(new File(getContextPath() + "stylesheets"), baseStylesheetURL);
        } catch (MalformedURLException e) {
            // Impossible to have
        }

        Options.setDefaultIconSize(new Dimension(DEFAULT_ICON_WIDTH, DEFAULT_ICON_HEIGHT));
        UIManager.put("ToolBar.separatorSize", new DimensionUIResource(TOOLBAR_SEP_WIDTH, TOOLBAR_SEP_HEIGHT));
        Options.setPopupDropShadowEnabled(true);

        // In Java Web Start, indicate where to find the l&f classes. Set the Swing class loader.
        UIManager.put("ClassLoader", LookUtils.class.getClassLoader());

        // Make Swing update everything dynamically during resize operations.
        Toolkit.getDefaultToolkit().setDynamicLayout(true);
        super.configureUI();
        configureFonts();

        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("configureUI done...");
        }

        OSXSupport.setupLAF();

        // Custom icons
        UIManager.getLookAndFeelDefaults().put("html.missingImage", new UIDefaults.LazyValue() {
            /**
             * Creates the actual value retrieved from the <code>UIDefaults</code> table. When an object that implements
             * this interface is retrieved from the table, this method is used to create the real value, which is then
             * stored in the table and returned to the calling method.
             *
             * @param table a <code>UIDefaults</code> table
             *
             * @return the created <code>Object</code>
             */
            public Object createValue(UIDefaults table) {
                return ResourceUtils.getIcon("application.icon");
            }
        });
    }

    /**
     * Configures fonts.
     */
    private void configureFonts() {
        Options.setUseSystemFonts(true);

        if (SystemUtils.IS_OS_LINUX)
            PlasticLookAndFeel.setFontPolicy(new SmallPlasticFontPolicy());
    }

    /**
     * Configures the splash component: reads the splash image, then opens an ImageSplash.
     */
    protected void configureSplash() {
        // Create image splash
        Image image = ResourceUtils.getIcon(ResourceIDs.SPLASH_IMAGE).getImage();
        ImageSplash imageSplash = new ImageSplash(image, true);
        imageSplash.setNoteEnabled(true);

        // Wrap with de-flicker "filter"
        SplashProvider splashWrapper = new NoFlickerSplashWrapper(imageSplash);
        Splash.setProvider(splashWrapper);
    }

    private static boolean isMac() {
        String osName = System.getProperty("os.name");
        return osName != null && osName.startsWith("Mac OS");
    }

    /**
     * Use the lovely JGoodies booting mechanism to get the thing launched.
     *
     * @param arguments standard main parameters.
     */
    public static void main(String[] arguments) {
        if (isMac())
            GURLHandler.getInstance().register();

        initReleaseTypeAndPrefix(arguments, System.getProperty("release.type"),
                System.getProperty("working.folder"));

        overrideLocale();

        String urlToOpen = checkCommandLineForURL(arguments);

        socketFile = new File(getContextPath() + "/application.sock");

        File lockerFile = new File(getContextPath() + "/application.lock");
        instanceLock = Locker.tryLocking(lockerFile);

        if (instanceLock != null) {
            proVersion = System.getProperty("pro") != null;
            globalAppLauncher = new ApplicationLauncher();
            globalAppLauncher.boot(getDescription(), getConfiguration());

            // A few Mac Specific things that have to be done really early
            OSXSupport.setAboutMenuName("BlogBridge");
            ApplicationLauncher.urlToOpen = urlToOpen;
        } else if (urlToOpen != null) {
            IPC.sendCommand(socketFile, "subscribe", new String[] { urlToOpen });
            System.exit(0);
        } else {
            JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
                    Strings.message("startup.only.one.instance.allowed"), "BlogBridge",
                    JOptionPane.WARNING_MESSAGE);
            System.exit(1);
        }
    }

    /**
     * Overrides system locale with English if the flag is set to use English.
     */
    private static void overrideLocale() {
        boolean alwaysUseEnglish = Preferences.userRoot().node(prefix)
                .getBoolean(UserPreferences.PROP_ALWAYS_USE_ENGLISH, UserPreferences.DEFAULT_ALWAYS_USE_ENGLISH);

        if (alwaysUseEnglish)
            Locale.setDefault(new Locale("en"));
    }

    /**
     * Gets URL from the command line.
     *
     * @param arguments arguments.
     *
     * @return URL or <code>NULL</code>.
     */
    static String checkCommandLineForURL(String[] arguments) {
        String urlStr = null;

        for (int i = 0; urlStr == null && i < arguments.length; i++) {
            String arg = arguments[i];
            if ("-open".equals(arg) && arguments.length > i) {
                arg = arguments[++i];
            }

            URL url = IPC.argToURL(StringUtils.cleanDraggedURL(arg));
            if (url != null)
                urlStr = url.toString();
        }

        return urlStr;
    }

    static void initReleaseTypeAndPrefix(String[] arguments, String propReleaseType, String propWorkingFolder) {
        String relType = propReleaseType;
        String pref = propWorkingFolder;

        if (pref != null) {
            pref = "bb/" + pref;
            if (relType == null)
                relType = pref;
        } else {
            if (arguments.length > 0) {
                pref = arguments[0];

                int i = pref.indexOf('/');
                if (i > -1)
                    relType = pref.substring(i + 1).trim();

                if (relType == null || relType.length() == 0)
                    relType = "Final";
            } else {
                pref = "bb/final";
                relType = "Final";
            }
        }

        releaseType = StringUtils.capitalise(relType);
        prefix = pref;
        usingFinalData = "bb/final".equalsIgnoreCase(prefix);
    }

    /**
     * Transfers anti-aliasing property from user preferences to the Swing-known place.
     */
    private static void configureAntiAliasing() {
        Preferences prefs = Application.getUserPreferences();
        boolean aaText = prefs.getBoolean(UserPreferences.PROP_AA_TEXT, false);
        System.getProperties().put("swing.aatext", Boolean.toString(aaText));
    }

    /**
     * Creates application description object.
     *
     * @return application description.
     */
    private static synchronized ApplicationDescription getDescription() {
        if (description == null) {
            description = new ApplicationDescription("BlogBridge", // Application short name
                    "BlogBridge", // Application long name
                    CURRENT_VERSION, // Version
                    CURRENT_VERSION + " - " + getMonthYear(), // Full version
                    Strings.message("appdescriptor.description"), // Description
                    "\u00a9 " + getYear() + " Salas Associates, All Rights Reserved.", // Copyright
                    "Salas Associates, Inc.", // Vendor
                    "http://www.blogbridge.com/", // Vendor URL
                    "info@blogbridge.com"); // Vendor email
        }

        return description;
    }

    /**
     * Returns current month and year. For example, "Aug 2005".
     *
     * @return month and year.
     */
    private static String getMonthYear() {
        return new SimpleDateFormat("MMM yyyy").format(new Date());
    }

    /**
     * Returns current year.
     *
     * @return year.
     */
    private static String getYear() {
        return new SimpleDateFormat("yyyy").format(new Date());
    }

    /**
     * Return JGoodies UIF ApplicationConfiguration for specified prefix. The prefix is used as a
     * subdirectory under the .bb directory to have separate persistent areas, and is also used as
     * a prefix node in the Preferences (registery) to keep those separate as well.
     *
     * @return application configuration object.
     */
    public static synchronized ApplicationConfiguration getConfiguration() {
        if (configuration == null) {
            configuration = new ApplicationConfiguration(prefix, // Root node for prefs and logs
                    "", // resource.properties URL
                    "docs/Help.hs", // Helpset URL
                    "docs/tips/index.txt"); // Tips index path
        }

        return configuration;
    }

    /**
     * Configures the <code>SetupManager</code>. The default does nothing. Sublcasses can, for
     * example modify the welcome panel.
     */
    protected void configureSetupManager() {
        SetupManager.setWelcomePanel(new WelcomePage(null, null));
    }

    /**
     * Works that needs to be done *after* MainFrame has been displayed.
     */
    protected void launchApplication() {
        incrementInstallationRuns();

        com.salas.bbutilities.opml.export.Exporter
                .setGenerator("OPML generated by BlogBridge " + CURRENT_VERSION + " (http://www.blogbridge.com/)");

        configureSecurityManager();

        // Start plug-ins
        Splash.setNote(Strings.message("startup.loading.plugins"), 2);
        File f = new File(getContextPath(), "plugins");
        Manager.initialize(f, Application.getUserPreferences());
        Manager.loadPackages();

        disableSSLHostNameVerification();
        disableSSLCertificates();

        Splash.setNote(Strings.message("startup.starting.ipc"), 10);
        configureIPC();

        Splash.setNote(Strings.message("startup.configuring.network"), 12);
        configureNetwork();

        Splash.setNote(Strings.message("startup.installing.performance.timers"), 15);
        installWatchdogs();

        Splash.setNote(Strings.message("startup.checking.model"), 20);
        GlobalModel newVersionModel = checkForNewVersion();

        GlobalController.SINGLETON.restorePreferences();

        UifUtilities.invokeAndWait(new Runnable() {
            public void run() {
                ApplicationLauncher.super.launchApplication();
            }
        }, MSG_PARENT_APP_LAUNCHER_ERROR, Level.SEVERE);

        Splash.setNote(Strings.message("startup.opening.database"), 40);
        IPersistenceManager manager = PersistenceManagerConfig.getManager();
        try {
            manager.init();
        } catch (PersistenceException e) {
            LOG.log(Level.SEVERE, Strings.error("failed.to.initialize.database"), e);

            // It's not possible to continue working with database failing.
            System.exit(1);
        }

        configureShutdownHook();

        Splash.setNote(Strings.message("startup.restoring.state.from.database"), 80);
        // Restore last saved persistent state. If none, then gen fake data.
        GlobalController.SINGLETON.restorePersistentState(newVersionModel);

        Splash.setNote(Strings.message("startup.starting.connection.checking"), 85);
        startConnectionChecker();

        Splash.setNote(Strings.message("startup.loading.tip.of.the.day"), 90);
        checkForOpenTipOfTheDayDailog();

        splashEnsureOpenClosed(false);
        Splash.setProvider(null);
    }

    /**
     * Enabled IPC by subscribing the model to the handler and enabling GURL handler on Mac.
     * The handler is registered during the very start and may be already
     * reported some URL. Now, when it's enabled, it will pass it through to
     * the controller.
     */
    public static void enableIPC() {
        ipc.addListener(GlobalController.SINGLETON);

        // Enable GURL listener on Mac
        if (isMac())
            GURLHandler.getInstance().enable();
    }

    /**
     * Configures IPC.
     */
    private void configureIPC() {
        try {
            ipc = new IPC(socketFile);
        } catch (IOException e) {
            LOG.log(Level.WARNING, "Failed to initialize IPC", e);
        }

        if (SystemUtils.IS_OS_MAC)
            OSXSupport.setupIPC(ipc);
    }

    /**
     * Configures hook for application shutdown.
     */
    private void configureShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            /**
             * Invoked when shutdown of application is in progress.
             */
            public void run() {
                // Closing database before termination
                PersistenceManagerConfig.getManager().shutdown();

                try {
                    instanceLock.release();
                } catch (IOException e) {
                    // Not a big deal
                }

                // Close IPC channel
                ipc.close();
            }
        });
    }

    private void checkForOpenTipOfTheDayDailog() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                TipOfTheDay.showRandomTip();
            }
        });
    }

    /**
     * Initializes and starts connection checker.
     */
    private void startConnectionChecker() {
        GlobalModel model = GlobalController.SINGLETON.getModel();
        ServicePreferences prefs = model.getServicePreferences();

        String version = globalAppLauncher.getApplicationDescription().getVersion();
        long installationId = getInstallationId();
        int runs = getInstallationRuns();
        String accountEmail = prefs.getEmail();
        String accountPassword = prefs.getPassword();

        ConnectionChecker connectionChecker = new ConnectionChecker(version, installationId, runs, accountEmail,
                accountPassword, connectionState);
        connectionChecker.start();
    }

    /**
     * Returns number of runs for current installation.
     *
     * @return runs count.
     */
    public static int getInstallationRuns() {
        return Application.getUserPreferences().getInt(KEY_RUNS, 0);
    }

    /**
     * Clears number of runs for installation.
     */
    static void clearInstallationRuns() {
        Application.getUserPreferences().putInt(KEY_RUNS, 0);
    }

    /**
     * Increments number of runs for installation.
     */
    static void incrementInstallationRuns() {
        Application.getUserPreferences().putInt(KEY_RUNS, getInstallationRuns() + 1);
    }

    /**
     * Returns installation ID. If ID is not currently defined defines it and stores.
     *
     * @return installation ID.
     */
    public static long getInstallationId() {
        return Application.getUserPreferences().getLong(KEY_INSTALLATION_ID, -1);
    }

    /**
     * Checks if migration procedures are necessary.
     *
     * @return newly created model in case of new installation or <code>null</code>.
     */
    private GlobalModel checkForNewVersion() {
        long installationId = getInstallationId();

        String forceInstallationVal = System.getProperty("force.installation");
        String installationVersion = forceInstallationVal != null ? Constants.EMPTY_STRING
                : Application.getUserPreferences().get(KEY_INSTALLATION_VERSION, Constants.EMPTY_STRING);

        GlobalModel model = null;
        if (installationId == -1
                || !globalAppLauncher.getApplicationDescription().getVersion().equals(installationVersion)) {
            // If previous version is less than minimal version compatible with current
            // then continue with full installation wizard.
            if (VersionUtils.versionCompare(installationVersion, MINIMAL_COMPATIBLE_VERSION) < 0) {
                model = resetIncompatibleDatabase();
                if (model == null)
                    System.exit(0);
            }

            installationId = generateInstallationId();
            Application.getUserPreferences().putLong(KEY_INSTALLATION_ID, installationId);
            Application.getUserPreferences().put(KEY_INSTALLATION_VERSION,
                    globalAppLauncher.getApplicationDescription().getVersion());

            clearInstallationRuns();
        }

        return model;
    }

    /**
     * Calls installer in EDT and waits for it to finish.
     *
     * @return new model or <code>NULL</code> in case of failure.
     */
    private GlobalModel resetIncompatibleDatabase() {
        splashEnsureOpenClosed(false);

        Installer installer = new Installer();
        GlobalModel model = installer.perform(getContextPath());

        splashEnsureOpenClosed(true);

        return model;
    }

    /**
     * Hides / shows splash screen.
     *
     * @param open <code>TRUE</code> to ensure that the splash screen is shown.
     */
    private static void splashEnsureOpenClosed(final boolean open) {
        UifUtilities.invokeAndWait(new Runnable() {
            public void run() {
                if (open)
                    Splash.ensureOpen();
                else
                    Splash.ensureClosed();
            }
        }, MSG_SPLASH_ENSURE_ERROR, Level.WARNING);
    }

    /**
     * Generates installation ID with high level of uniqueness.
     *
     * @return installation ID.
     */
    private long generateInstallationId() {
        // Using current time in millis here as it has high level enough.
        return System.currentTimeMillis();
    }

    /**
     * Computes where on the user's machine we will place various state files.
     *
     * @return context path.
     */
    public synchronized static String getContextPath() {
        if (contextPath == null) {
            String userHomePath = System.getProperty("user.home");
            String nodePath = '.' + getConfiguration().getPreferencesRootName();
            contextPath = userHomePath + File.separatorChar + nodePath + File.separatorChar;
        }

        return contextPath;
    }

    /**
     * Returns path to backups directory.
     *
     * @return backups directory path.
     */
    public static String getBackupsPath() {
        return getContextPath() + BACKUPS_DIR_NAME;
    }

    /**
     * Returns current application version.
     * 
     * @return current version.
     */
    public static String getCurrentVersion() {
        return CURRENT_VERSION;
    }

    /**
     * Small font plastic policy.
     */
    private static class SmallPlasticFontPolicy implements FontPolicy {
        /**
         * Returns fonts for a given LAF with given defaults.
         *
         * @param laf           LAF name.
         * @param uiDefaults    defaults.
         *
         * @return fonts set.
         */
        public FontSet getFontSet(String laf, UIDefaults uiDefaults) {
            FontPolicy policy = FontPolicies.getDefaultPlasticPolicy();
            int size = LookUtils.IS_LOW_RESOLUTION ? 11 : 12;

            FontSet set = policy.getFontSet(laf, uiDefaults);
            set = FontSets.createDefaultFontSet(ensureSize(set.getControlFont(), size),
                    ensureSize(set.getMenuFont(), size), ensureSize(set.getTitleFont(), size));

            return set;
        }

        /**
         * Ensures that the size of the font equals to the given or derives the font.
         *
         * @param font  font to check.
         * @param size  desired size.
         *
         * @return font.
         */
        private static Font ensureSize(FontUIResource font, int size) {
            return font.getSize() == size ? font : font.deriveFont((float) size);
        }
    }
}