/* * org.openmicroscopy.shoola.env.Container * *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 University of Dundee. All rights reserved. * * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.env; import java.io.File; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.io.FilenameUtils; import org.openmicroscopy.shoola.util.CommonsLangUtils; import org.openmicroscopy.shoola.env.config.AgentInfo; import org.openmicroscopy.shoola.env.config.Registry; import org.openmicroscopy.shoola.env.config.RegistryFactory; import org.openmicroscopy.shoola.env.data.DataServicesFactory; import org.openmicroscopy.shoola.env.data.events.ActivateAgents; import org.openmicroscopy.shoola.env.data.events.ConnectedEvent; import org.openmicroscopy.shoola.env.data.login.LoginService; import org.openmicroscopy.shoola.env.data.login.UserCredentials; import org.openmicroscopy.shoola.env.event.AgentEvent; import org.openmicroscopy.shoola.env.event.AgentEventListener; import org.openmicroscopy.shoola.env.init.Initializer; import org.openmicroscopy.shoola.env.init.StartupException; import org.openmicroscopy.shoola.util.file.IOUtil; import org.openmicroscopy.shoola.util.ui.UIUtilities; /** * Oversees the functioning of the whole container, holds the container's * configuration and manages agents life-cycle. * Also delegates initialization tasks to * {@link org.openmicroscopy.shoola.env.init.Initializer}. * * <p>This class is a Singleton. The singleton object can't be retrieved by * arbitrary classes, it's only meant to be used during initialization and * linked to some of the container's services.</p> * <p>Initialization tasks use the singleton to access the registry and the * agents' pool, so that these may be properly initialized. Initialization tasks * that bring up the container's services also link the singleton to the service * implementation where needed.</p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * <small> * (<b>Internal version:</b> $Revision$ $Date$) * </small> * @since OME2.2 */ public final class Container { /** The title of the splash screens. */ public static final String TITLE = "Open Microscopy Environment"; /** * Points to the configuration directory. * The path is relative to the installation directory. */ public static final String CONFIG_DIR = "config"; /** * Points to the libs directory. * The path is relative to the installation directory. */ public static final String LIBS_DIR = "libs"; /** The name of the container's configuration file. */ public static final String CONFIG_FILE = "container.xml"; /** * Points to the documentation directory. * The path is relative to the installation directory. */ public static final String DOC_DIR = "docs"; /** * The sole instance. * This object is passed around at initialization so that services' * initialization tasks may link it to the service implementation. */ private static Container singleton; /** * Performs the start up procedure. * * @param home Path to the installation directory. If <code>null<code> or * empty, then the user directory is assumed. * @param configFile The configuration file. */ private static void runStartupProcedure(String home, String configFile) { AbnormalExitHandler.configure(); Initializer initManager = null; try { singleton = new Container(home, configFile); initManager = new Initializer(singleton); initManager.configure(); initManager.doInit(); initManager.notifyEnd(); //startService() called by Initializer at end of doInit(). } catch (StartupException se) { if (initManager != null) initManager.rollback(); AbnormalExitHandler.terminate(se); } //Any other exception will be handled automatically by //AbnormalExitHandler. In this case, we don't rollback, //as something completely unforeseen happened, so it's //better not to make assumptions on the state of the //initialization manager. } /** * Returns the singleton instance. * Only used by the {@link AbnormalExitHandler}. * * @return See above. */ static Container getInstance() { return singleton; } /** * Entry point to launch the container and bring up the whole client. * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent — in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * <p>This method rolls back all executed tasks and terminates the program * if an error occurs during the initialization procedure.</p> * * @param home Path to the installation directory. * If <code>null<code> or * empty, then the user directory is assumed. * @param configFile The configuration file. */ public static void startup(final String home, final String configFile) { if (singleton != null) return; ThreadGroup root = new RootThreadGroup(); Runnable r = new Runnable() { public void run() { runStartupProcedure(home, configFile); } }; Thread t = new Thread(root, r, "Initializer"); t.start(); //Now the main thread exits and the initialization procedure is run //within the Initializer thread which belongs to root. As a consequence //of this, any other thread created thereafter will belong to root or //a subgroup of root. } /** The configuration file. */ private String configFile; /** Absolute path to the installation directory. */ private String homeDir; /** The container's registry. */ private Registry registry; /** All managed agents. */ private Set<Agent> agentsPool; /** * Initializes the member fields. * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent -- in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * * @param home Path to the installation directory. If <code>null</code> or * empty, then the user directory is assumed. * @param configFile The configuration file. * @throws StartupException If <code>home</code> can't be resolved to a * valid and existing directory. */ private Container(String home, String configFile) throws StartupException { if (CommonsLangUtils.isBlank(configFile) || !FilenameUtils.isExtension(configFile, "xml")) configFile = CONFIG_FILE; this.configFile = configFile; if (CommonsLangUtils.isBlank(FilenameUtils.getPath(home))) home = System.getProperty("user.dir"); File f = new File(home); //Now make it absolute. If the original path wasn't absolute, then //translation is system dependent. f = f.getAbsoluteFile(); homeDir = f.getAbsolutePath(); //Make sure that what we've got is a directory. if (!f.exists() || !f.isDirectory()) throw new StartupException("Can't locate home dir: "+homeDir); agentsPool = new HashSet<Agent>(); registry = RegistryFactory.makeNew(); } /** * Returns the absolute path to the installation directory. * * @return See above. */ public String getHomeDir() { return homeDir; } /** * Returns the relative path to the container's configuration file. * * @return See above. */ public String getConfigFileRelative() { return getConfigFileRelative(configFile); } /** * Returns the relative path to the container's configuration file. * * @param file The configuration file. * @return See above. */ public String getConfigFileRelative(String file) { return getFileRelative(CONFIG_DIR, file); } /** * Returns the relative path to the container's configuration file or * libs folder. * * @param file The configuration file. * @return See above. */ public String getFileRelative(String directory, String file) { return resolveFilePath(file, directory); } /** * Resolves <code>fileName</code> against the configuration directory. * * @param fileName The name of a configuration file. * @param directory The directory of reference. * @return Returns the absolute path to the specified file. */ public String resolveFilePath(String fileName, String directory) { //if (fileName == null) throw new NullPointerException(); StringBuffer relPath = new StringBuffer(directory); relPath.append(File.separatorChar); relPath.append(fileName); File f = new File(homeDir, relPath.toString()); return f.getAbsolutePath(); } /** * Returns the container's registry. * * @return See above. */ public Registry getRegistry() { return registry; } /** * Adds the specified agent to the pool of managed agents. * * @param a The agent. * @return <code>true</code> if <code>a</code> is not already in the pool, * <code>false</code> otherwise. * @throws NullPointerException If <code>null</code>is passed in. */ public boolean addAgent(Agent a) { if (a == null) throw new NullPointerException(); return agentsPool.add(a); } /** Activates the agents.*/ private void activateAgents() { Integer v = (Integer) singleton.registry.lookup( LookupNames.ENTRY_POINT); int value = LookupNames.INSIGHT_ENTRY; Integer plugin = (Integer) singleton.registry.lookup(LookupNames.PLUGIN); if (v != null) { switch (v.intValue()) { case LookupNames.IMPORTER_ENTRY: case LookupNames.INSIGHT_ENTRY: value = v.intValue(); } } List<AgentInfo> agents = (List<AgentInfo>) singleton.registry.lookup(LookupNames.AGENTS); Iterator<AgentInfo> i = agents.iterator(); AgentInfo agentInfo; Agent a; int n; while (i.hasNext()) { agentInfo = i.next(); n = agentInfo.getNumber(); if (agentInfo.isActive() && (n == value)) { a = agentInfo.getAgent(); a.activate(true); } } } /** * Activates all services, all agents and starts interacting with the * user. */ public void startService() { if (singleton == null) return; List<AgentInfo> agents = (List<AgentInfo>) singleton.registry.lookup(LookupNames.AGENTS); Iterator<AgentInfo> i = agents.iterator(); AgentInfo agentInfo; Agent a; Registry r; //Agents linking phase. Environment env = new Environment(this); singleton.registry.bind(LookupNames.ENV, env); while (i.hasNext()) { agentInfo = i.next(); if (agentInfo.isActive()) { a = agentInfo.getAgent(); r = agentInfo.getRegistry(); r.bind(LookupNames.ENV, env); a.setContext(r); } } //Agents activation phase. activateAgents(); //TODO: activate services (EventBus, what else?). //Get ready to interact with the user. //TaskBar tb = singleton.registry.getTaskBar(); //tb.open(); } /** * Shuts down all agents, all services and exits the JVM process. */ public void exit() { Integer v = (Integer) getRegistry().lookup(LookupNames.PLUGIN); int value = -1; if (v != null) value = v.intValue(); if (value <= 0) { System.exit(0); } else { getRegistry().getEventBus().post(new ConnectedEvent(false)); try { DataServicesFactory.getInstance(singleton).shutdown(null); } catch (Exception e) {} } } /** * Entry point to launch the container and bring up the whole client * in the same thread as the caller's. * * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent — in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * <p>This method rolls back all executed tasks and terminates the program * if an error occurs during the initialization procedure.</p> * * @param home Path to the installation directory. If <code>null<code> or * empty, then the user directory is assumed. * @param configFile The configuration file. * @param plugin Pass positive value. See {@link LookupNames} for supported * plug-in. Those plug-in will have an UI entry. * @return A reference to the newly created singleton Container. * @throws Throws a startup exception if the application cannot be used as * a plugin due to missing dependencies. */ public static Container startupInPluginMode(String home, String configFile, int plugin) throws StartupException { return startupInPluginMode(home, configFile, plugin, null); } /** * Entry point to launch the container and bring up the whole client * in the same thread as the caller's. * * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent — in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * <p>This method rolls back all executed tasks and terminates the program * if an error occurs during the initialization procedure.</p> * * @param home Path to the installation directory. If <code>null<code> or * empty, then the user directory is assumed. * @param configFile The configuration file. * @param plugin Pass positive value. See {@link LookupNames} for supported * plug-in. Those plug-in will have an UI entry. * @param listener Listens to <code>ConnectedEvent</code>. * @return A reference to the newly created singleton Container. * @throws Throws a startup exception if the application cannot be used as * a plugin due to missing dependencies. */ public static Container startupInPluginMode(String home, String configFile, int plugin, AgentEventListener listener) throws StartupException { if (Container.getInstance() != null) { //reconnect. singleton.registry.bind(LookupNames.PLUGIN, plugin); LoginService loginSvc = (LoginService) singleton.registry.lookup( LookupNames.LOGIN); UserCredentials uc = null; if (singleton.registry.lookup(LookupNames.USER_CREDENTIALS) != null) { uc = (UserCredentials) singleton.registry.lookup( LookupNames.USER_CREDENTIALS); } if (uc != null) { int v = loginSvc.login(uc); boolean r = true; if (v == LoginService.CONNECTED) { singleton.activateAgents(); } else { //Check if the splashscreen is up. Boolean b = (Boolean) singleton.registry.lookup( LookupNames.LOGIN_SPLASHSCREEN); if (b != null && !b.booleanValue()) { r = false; } } if (r) { return Container.getInstance(); } singleton = null; } } //Initialize services as usual though. Initializer initManager = null; try { singleton = new Container(home, configFile); singleton.registry.bind(LookupNames.PLUGIN, plugin); initManager = new Initializer(singleton); initManager.configure(); initManager.doInit(); if (singleton == null) { //Quit during the initialization. throw new RuntimeException( "Plugin shuts down during initialization."); } if (listener != null) singleton.registry.getEventBus().register(listener, ConnectedEvent.class); initManager.notifyEnd();//wait to collect credentials } catch (StartupException se) { if (initManager != null) initManager.rollback(); singleton = null; if (se.getPlugin() != null) throw se; throw new RuntimeException( "Failed to intialize the Container in plugin mode.", se); } return singleton; } /** * Entry point to launch the container and bring up the whole client * in the same thread as the caller's. * * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent — in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * <p>This method rolls back all executed tasks and terminates the program * if an error occurs during the initialization procedure.</p> * * @param home Path to the installation directory. If <code>null<code> or * empty, then the user directory is assumed. * @param configFile The configuration file. * @param plugin Pass positive value. See {@link LookupNames} for supported * plug-in. Those plug-in will have an UI entry. * @return A reference to the newly created singleton Container. * @throws Throws a startup exception if the application cannot be used as * a plugin due to missing dependencies. */ public static Container startupInHeadlessMode(String home, String configFile, int plugin) throws StartupException { if (Container.getInstance() != null) { return Container.getInstance(); } //Initialize services as usual though. Initializer initManager = null; try { singleton = new Container(home, configFile); if (plugin >= 0) singleton.registry.bind(LookupNames.PLUGIN, plugin); singleton.registry.bind(LookupNames.HEADLESS, Boolean.valueOf(true)); initManager = new Initializer(singleton, true); initManager.configure(); initManager.doInit(); initManager.notifyEnd(); //startService() called by Initializer at end of doInit(). //Listen to the activate agents event singleton.registry.getEventBus().register(new AgentEventListener() { public void eventFired(AgentEvent e) { singleton.activateAgents(); } }, ActivateAgents.class); } catch (StartupException se) { if (initManager != null) initManager.rollback(); singleton = null; if (se.getPlugin() != null) throw se; throw new RuntimeException( "Failed to intialize the Container in headless mode.", se); } return singleton; } /** * Entry point to launch the container and bring up the whole client * in the same thread as the caller's. * * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent — in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * <p>This method rolls back all executed tasks and terminates the program * if an error occurs during the initialization procedure.</p> * * @param home Path to the installation directory. If <code>null<code> or * empty, then the user directory is assumed. * @param configFile The configuration file. * @return A reference to the newly created singleton Container. * @throws Throws a startup exception if the application cannot be used as * a plugin due to missing dependencies. */ public static Container startupInHeadlessMode(String home, String configFile ) throws StartupException { return startupInHeadlessMode(home, configFile, -1); } /* * ============================================================== * Follows code to enable testing. * ============================================================== */ /** * Entry point to launch the container and bring up the whole client * in the same thread as the caller's. * <p>This method should only be used in a test environment — we * use the caller's thread to avoid regular unit tests having to deal * with subtle concurrency issues.</p> * <p>The absolute path to the installation directory is obtained from * <code>home</code>. If this parameter doesn't specify an absolute path, * then it'll be translated into an absolute path. Translation is system * dependent — in many cases, the path is resolved against the user * directory (typically the directory in which the JVM was invoked).</p> * <p>This method rolls back all executed tasks and terminates the program * if an error occurs during the initialization procedure.</p> * * @param home Path to the installation directory. If <code>null<code> or * empty, then the user directory is assumed. * @return A reference to the newly created singleton Container. */ public static Container startupInTestMode(String home) { if (Container.getInstance() != null) return Container.getInstance(); //Don't use the AbnormalExitHandler, let the test environment deal //with exceptions instead. Initialize services as usual though. Initializer initManager = null; try { singleton = new Container(home, CONFIG_FILE); initManager = new Initializer(singleton); initManager.configure(); initManager.doInit(); initManager.notifyEnd(); //startService() called by Initializer at end of doInit(). } catch (StartupException se) { if (initManager != null) initManager.rollback(); singleton = null; throw new RuntimeException( "Failed to intialize the Container in test mode.", se); } return singleton; } }