Java tutorial
/***************************************************************************** * Java Plug-in Framework (JPF) * Copyright (C) 2004-2007 Dmitry Olshansky * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *****************************************************************************/ package org.java.plugin.boot; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; import org.apache.commons.logging.LogFactory; import org.java.plugin.PluginManager; import org.java.plugin.util.ExtendedProperties; import org.java.plugin.util.IoUtil; import org.java.plugin.util.ResourceManager; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Main class to get JPF based application running in different modes. * Application mode may be specified as <code>jpf.boot.mode</code> configuration * parameter or System property (via <code>-Djpf.boot.mode=</code> command line * argument). Supported values are: * <dl> * <dt>start</dt> * <dd>Runs application in "background" ("service") mode.</dd> * <dt>stop</dt> * <dd>Stops application, running in "background" mode.</dd> * <dt>restart</dt> * <dd>Restarts application, running in "background" mode. If it is not * started, the action is the same as just starting application.</dd> * <dt>shell</dt> * <dd>Runs application in "shell" (or "interactive") mode. It is possible to * control "service" style application from command line. Note, that * already running application will be stopped first.</dd> * <dt>load</dt> * <dd>Only loads application but not starts it as in other modes. This mode * is useful when doing application unit testing or when you only need to * get initialized and ready to be started JPF environment.</dd> * </dl> * The "shell" mode is default. Application will be started in this mode if no * <code>jpf.boot.mode</code> configuration parameter can be found. * <p> * Application configuration is expected to be in Java properties format file. * File look-up procedure is the following: * </p> * <ul> * <li>Check <code>jpf.boot.config</code> System property, if present, load * configuration from that location</li> * <li>Look for <code>boot.properties</code> file in the current folder.</li> * <li>Look for <code>boot.properties</code> resource in classpath (using * <code>Boot.class.getClassLoader().getResource("boot.properties")</code> * and <code>Boot.class.getResource("boot.properties")</code> methods).</li> * </ul> * <p> * If configuration could not be found, a warning will be printed to console. * It is generally not an error to not use configuration file, you may provide * JPF configuration parameters as System properties. They are always used as * defaults for configuration properties. * </p> * <p> * Note that configuration properties will be loaded using * {@link org.java.plugin.util.ExtendedProperties specially extended} * version of {@link java.util.Properties} class, which supports parameters * substitution. If there is no <code>applicationRoot</code> property available * in the given configuration, the current folder will be published as default * value. * </p> * <p> * Standard configuration parameters are (all are optional when application is * running in "shell" mode): * <dl> * <dt>jpf.boot.mode</dt> * <dd>Application boot mode. Always available as System property also. * Default value is <code>shell</code>.</dd> * <dt>org.java.plugin.boot.appInitializer</dt> * <dd>Application initializer class, for details see * {@link org.java.plugin.boot.ApplicationInitializer}. Default is * {@link org.java.plugin.boot.DefaultApplicationInitializer}.</dd> * <dt>org.java.plugin.boot.errorHandler</dt> * <dd>Error handler class, for details see * {@link org.java.plugin.boot.BootErrorHandler}. Default is * {@link org.java.plugin.boot.BootErrorHandlerConsole} for "service" style * applications and {@link org.java.plugin.boot.BootErrorHandlerGui} for * "interactive" applications.</dd> * <dt>org.java.plugin.boot.controlHost</dt> * <dd>Host to be used by background control service, no default values.</dd> * <dt>org.java.plugin.boot.controlPort</dt> * <dd>Port number to be used by background control service, no default * values.</dd> * <dt>org.java.plugin.boot.splashHandler</dt> * <dd>Splash screen handler class, for details see * {@link org.java.plugin.boot.SplashHandler}. Default is simple splash * handler that can only display an image.</dd> * <dt>org.java.plugin.boot.splashImage</dt> * <dd>Path to an image file to be shown as splash screen. This may be any * valid URL. If no file and no handler given, the splash screen will not * be shown.</dd> * <dt>org.java.plugin.boot.splashLeaveVisible</dt> * <dd>If set to <code>true</code>, the Boot class will not hide splash screen * at the end of boot procedure but delegate this function to application * code. Default value is <code>false</code>.</dd> * <dt>org.java.plugin.boot.splashDisposeOnHide</dt> * <dd>If set to <code>false</code>, the Boot class will not dispose splash * screen handler when hiding it. This allows you to reuse handler and show * splash screen back after it was hidden. Default value is * <code>true</code>.</dd> * </dl> * * @version $Id$ */ public final class Boot { /** * Name of the file, where to put boot error details. */ public static final String BOOT_ERROR_FILE_NAME = "jpf-boot-error.txt"; //$NON-NLS-1$ /** * Boot configuration file location System property name. */ public static final String PROP_BOOT_CONFIG = "jpf.boot.config"; //$NON-NLS-1$ /** * Boot mode System property name. */ public static final String PROP_BOOT_MODE = "jpf.boot.mode"; //$NON-NLS-1$ /** * "shell" mode boot command value. */ public static final String BOOT_MODE_SHELL = "shell"; //$NON-NLS-1$ /** * "start" mode boot command value. */ public static final String BOOT_MODE_START = "start"; //$NON-NLS-1$ /** * "stop" mode boot command value. */ public static final String BOOT_MODE_STOP = "stop"; //$NON-NLS-1$ /** * "restart" mode boot command value. */ public static final String BOOT_MODE_RESTART = "restart"; //$NON-NLS-1$ /** * "load" mode boot command value. */ public static final String BOOT_MODE_LOAD = "load"; //$NON-NLS-1$ // This is for ResourceManager to look up resources. static final String PACKAGE_NAME = "org.java.plugin.boot"; //$NON-NLS-1$ // Application bootstrap configuration parameter names goes here private static final String PARAM_CONTROL_HOST = "org.java.plugin.boot.controlHost"; //$NON-NLS-1$ private static final String PARAM_CONTROL_PORT = "org.java.plugin.boot.controlPort"; //$NON-NLS-1$ private static final String PARAM_ERROR_HANDLER = "org.java.plugin.boot.errorHandler"; //$NON-NLS-1$ private static final String PARAM_APP_INITIALIZER = "org.java.plugin.boot.appInitializer"; //$NON-NLS-1$ private static final String PARAM_SPLASH_HANDLER = "org.java.plugin.boot.splashHandler"; //$NON-NLS-1$ private static final String PARAM_SPLASH_IMAGE = "org.java.plugin.boot.splashImage"; //$NON-NLS-1$ private static final String PARAM_SPLASH_LEAVE_VISIBLE = "org.java.plugin.boot.splashLeaveVisible"; //$NON-NLS-1$ private static final String PARAM_SPLASH_DISPOSE_ON_HIDE = "org.java.plugin.boot.splashDisposeOnHide"; //$NON-NLS-1$ private static final String PARAM_SPLASH_CONFIG_PREFIX = "org.java.plugin.boot.splash."; //$NON-NLS-1$ static SplashHandler splashHandler = null; /** * Call this method to start/stop application. * @param args command line arguments, not interpreted by this method but * passed to * {@link ApplicationPlugin#initApplication(ExtendedProperties, String[])} * method */ public static void main(final String[] args) { clearBootLog(); // Load start-up configuration ExtendedProperties props = new ExtendedProperties(System.getProperties()); try { InputStream strm = lookupConfig(); try { props.load(strm); } finally { strm.close(); } } catch (IOException ioe) { ioe.printStackTrace(); } String mode = props.getProperty(PROP_BOOT_MODE); if (mode != null) { mode = mode.trim().toLowerCase(Locale.ENGLISH); } else { // set SHELL mode by default mode = BOOT_MODE_SHELL; } props.setProperty(PROP_BOOT_MODE, mode); // Make sure that boot mode is always available as System property: System.setProperty(PROP_BOOT_MODE, mode); boolean useControlService = props.containsKey(PARAM_CONTROL_HOST) && props.containsKey(PARAM_CONTROL_PORT); BootErrorHandler errorHandler = getErrorHandlerInstance(props.getProperty(PARAM_ERROR_HANDLER), useControlService); try { if (props.getProperty("applicationRoot") == null) { //$NON-NLS-1$ // Publish current folder as configuration parameter // to get it available as ${applicationRoot} variable // in extended properties syntax. String applicationRoot = new File(".").getCanonicalPath(); //$NON-NLS-1$ props.put("applicationRoot", applicationRoot); //$NON-NLS-1$ } boot(props, useControlService, mode, errorHandler, args); } catch (Throwable t) { if (splashHandler != null) { splashHandler.setVisible(false); splashHandler = null; } bootLog(t); errorHandler.handleFatalError(ResourceManager.getMessage(Boot.PACKAGE_NAME, "bootFailed"), t); //$NON-NLS-1$ System.exit(1); } } /** * Boots application according to given configuration data. * @param config boot configuration data * @param useControlService if <code>true</code>, the control service will * started to allow handling application instance * from another process * @param mode application run mode * @param errorHandler boot errors handler instance * @param args command line arguments, not interpreted by this method but * passed to * {@link ApplicationPlugin#initApplication(ExtendedProperties, String[])} * method * @return initialized application instance or <code>null</code> * @throws Exception if any un-handled error has occurred */ public static Application boot(final ExtendedProperties config, final boolean useControlService, final String mode, final BootErrorHandler errorHandler, final String[] args) throws Exception { InetAddress controlHost = useControlService ? InetAddress.getByName(config.getProperty(PARAM_CONTROL_HOST)) : null; int controlPort = useControlService ? Integer.parseInt(config.getProperty(PARAM_CONTROL_PORT), 10) : 0; // handle given command if (useControlService && BOOT_MODE_STOP.equals(mode)) { if (!ControlThread.stopRunningApplication(controlHost, controlPort)) { System.out.println("application not running"); //$NON-NLS-1$ } else { System.out.println("application stopped"); //$NON-NLS-1$ } return null; } if (useControlService && BOOT_MODE_START.equals(mode)) { if (ControlThread.isApplicationRunning(controlHost, controlPort)) { errorHandler.handleFatalError("Application already running."); //$NON-NLS-1$ return null; } Application application = initApplication(errorHandler, config, args); if (!(application instanceof ServiceApplication)) { errorHandler.handleFatalError("Application is not a service."); //$NON-NLS-1$ return null; } ControlThread controlThread = new ControlThread(controlHost, controlPort, (ServiceApplication) application); application.startApplication(); controlThread.start(); System.out.println("application started in BACKGROUND mode"); //$NON-NLS-1$ return application; } if (useControlService && BOOT_MODE_RESTART.equals(mode)) { if (ControlThread.stopRunningApplication(controlHost, controlPort)) { System.out.println("another instance of application stopped"); //$NON-NLS-1$ } Application application = initApplication(errorHandler, config, args); if (!(application instanceof ServiceApplication)) { errorHandler.handleFatalError("Application is not a service."); //$NON-NLS-1$ return null; } ControlThread controlThread = new ControlThread(controlHost, controlPort, (ServiceApplication) application); application.startApplication(); controlThread.start(); System.out.println("application started in BACKGROUND mode"); //$NON-NLS-1$ return application; } // SHELL or LOAD or an unknown modes if (useControlService && ControlThread.stopRunningApplication(controlHost, controlPort)) { System.out.println("another instance of application stopped"); //$NON-NLS-1$ } if (!BOOT_MODE_LOAD.equals(mode)) { initSplashHandler(config); if (splashHandler != null) { splashHandler.setVisible(true); } } Application application = initApplication(errorHandler, config, args); if (!BOOT_MODE_LOAD.equals(mode)) { application.startApplication(); if ((splashHandler != null) && !"true".equalsIgnoreCase(config.getProperty( //$NON-NLS-1$ PARAM_SPLASH_LEAVE_VISIBLE, "false"))) { //$NON-NLS-1$ splashHandler.setVisible(false); } if ((application instanceof ServiceApplication) && BOOT_MODE_SHELL.equals(mode)) { System.out.println("application started in SHELL mode"); //$NON-NLS-1$ runShell(); stopApplication(application); } } return application; } /** * Stops the application, shuts down plug-in manager and disposes log * service. Call this method before exiting interactive application. For * service applications this method will be called automatically by control * service or from shell. * @param application application instance being stopped * @throws Exception if any error has occurred during application stopping */ public static void stopApplication(final Application application) throws Exception { if (application instanceof ServiceApplication) { ((ServiceApplication) application).stopApplication(); } PluginManager pluginManager = PluginManager.lookup(application); if (pluginManager != null) { pluginManager.shutdown(); } LogFactory.getLog(Boot.class).info("logging system finalized"); //$NON-NLS-1$ LogFactory.getLog(Boot.class).info("---------------------------------"); //$NON-NLS-1$ LogFactory.releaseAll(); } /** * Returns current instance of splash screen handler if it is available or * <code>null</code>. * @return instance of splash handler or <code>null</code> if no active * instance available */ public static SplashHandler getSplashHandler() { return splashHandler; } /** * @param handler the new splash handler instance to set or * <code>null</code> to dispose current handler directly */ public static void setSplashHandler(final SplashHandler handler) { if ((handler == null) && (splashHandler != null)) { splashHandler.setVisible(false); } splashHandler = handler; } private static InputStream lookupConfig() throws IOException { String property = System.getProperty(PROP_BOOT_CONFIG); if (property != null) { return IoUtil.getResourceInputStream(str2url(property)); } File file = new File("boot.properties"); //$NON-NLS-1$ if (file.isFile()) { return new FileInputStream(file); } URL url = Boot.class.getClassLoader().getResource("boot.properties"); //$NON-NLS-1$ if (url != null) { return IoUtil.getResourceInputStream(url); } url = Boot.class.getResource("boot.properties"); //$NON-NLS-1$ if (url != null) { return IoUtil.getResourceInputStream(url); } throw new IOException("configuration file boot.properties not found"); //$NON-NLS-1$ } private static URL str2url(final String str) throws MalformedURLException { int p = str.indexOf("!/"); //$NON-NLS-1$ if (p == -1) { try { return new URL(str); } catch (MalformedURLException mue) { return IoUtil.file2url(new File(str)); } } if (str.startsWith("jar:")) { //$NON-NLS-1$ return new URL(str); } File file = new File(str.substring(0, p)); if (file.isFile()) { return new URL("jar:" + IoUtil.file2url(file) + str.substring(p)); //$NON-NLS-1$ } return new URL("jar:" + str); //$NON-NLS-1$ } private static BootErrorHandler getErrorHandlerInstance(final String handler, final boolean isServiceApp) { if (handler != null) { try { return (BootErrorHandler) Class.forName(handler).newInstance(); } catch (InstantiationException ie) { System.err.println("failed instantiating error handler " //$NON-NLS-1$ + handler); ie.printStackTrace(); } catch (IllegalAccessException iae) { System.err.println("failed instantiating error handler " //$NON-NLS-1$ + handler); iae.printStackTrace(); } catch (ClassNotFoundException cnfe) { System.err.println("failed instantiating error handler " //$NON-NLS-1$ + handler); cnfe.printStackTrace(); } } return isServiceApp ? new BootErrorHandlerConsole() : (BootErrorHandler) new BootErrorHandlerGui(); } private static void initSplashHandler(final ExtendedProperties config) throws Exception { String handlerClass = config.getProperty(PARAM_SPLASH_HANDLER); String splashImage = config.getProperty(PARAM_SPLASH_IMAGE); URL url = null; if ((splashImage != null) && (splashImage.length() > 0)) { try { url = new URL(splashImage); } catch (MalformedURLException mue) { // ignore } if (url == null) { File splashFile = new File(splashImage); if (splashFile.isFile()) { url = IoUtil.file2url(splashFile); } else { throw new FileNotFoundException("splash image file " //$NON-NLS-1$ + splashFile + " not found"); //$NON-NLS-1$ } } } boolean disposeOnHide = !"false".equalsIgnoreCase( //$NON-NLS-1$ config.getProperty(PARAM_SPLASH_DISPOSE_ON_HIDE, "true")); //$NON-NLS-1$ if (handlerClass != null) { splashHandler = new SplashHandlerWrapper(disposeOnHide, (SplashHandler) Class.forName(handlerClass).newInstance()); } if ((splashHandler == null) && (url != null)) { splashHandler = new SplashHandlerWrapper(disposeOnHide, new SimpleSplashHandler()); } if (splashHandler != null) { if (url != null) { splashHandler.setImage(url); } splashHandler.configure(config.getSubset(PARAM_SPLASH_CONFIG_PREFIX)); } } private static Application initApplication(final BootErrorHandler errorHandler, final ExtendedProperties props, final String[] args) throws Exception { ApplicationInitializer appInitializer = null; String className = props.getProperty(PARAM_APP_INITIALIZER); if (className != null) { try { appInitializer = (ApplicationInitializer) Class.forName(className).newInstance(); } catch (InstantiationException ie) { System.err.println("failed instantiating application initializer " //$NON-NLS-1$ + className); ie.printStackTrace(); } catch (IllegalAccessException iae) { System.err.println("failed instantiating application initializer " //$NON-NLS-1$ + className); iae.printStackTrace(); } catch (ClassNotFoundException cnfe) { System.err.println("failed instantiating application initializer " //$NON-NLS-1$ + className); cnfe.printStackTrace(); } } if (appInitializer == null) { appInitializer = new DefaultApplicationInitializer(); } appInitializer.configure(props); Application result = appInitializer.initApplication(errorHandler, args); if (result == null) { throw new Exception(ResourceManager.getMessage(Boot.PACKAGE_NAME, "bootAppInitFailed")); //$NON-NLS-1$ } return result; } private static void runShell() { System.out.println("Press 'q' key to exit."); //$NON-NLS-1$ do { int c; try { c = System.in.read(); } catch (IOException ioe) { break; } if (('q' == (char) c) || ('Q' == (char) c)) { break; } } while (true); } private static void clearBootLog() { File file = new File(BOOT_ERROR_FILE_NAME); if (file.isFile()) { file.delete(); } } private static void bootLog(final Throwable t) { try { Writer writer = new OutputStreamWriter(new FileOutputStream(BOOT_ERROR_FILE_NAME, false), "UTF-8"); //$NON-NLS-1$ try { writer.write("JPF Application boot failed."); //$NON-NLS-1$ writer.write(System.getProperty("line.separator")); //$NON-NLS-1$ writer.write(ErrorDialog.getErrorDetails(t)); } finally { writer.close(); } } catch (Throwable t2) { throw new Error("boot failed", t); //$NON-NLS-1$ } } private Boot() { // no-op } } final class SimpleSplashHandler implements SplashHandler { private float progress; private String text; private URL image; private boolean isVisible; /** * @see org.java.plugin.boot.SplashHandler#configure( * org.java.plugin.util.ExtendedProperties) */ public void configure(final ExtendedProperties config) { // no-op } /** * @see org.java.plugin.boot.SplashHandler#getProgress() */ public float getProgress() { return progress; } /** * @see org.java.plugin.boot.SplashHandler#setProgress(float) */ public void setProgress(final float value) { if ((value < 0) || (value > 1)) { throw new IllegalArgumentException("invalid progress value " + value); //$NON-NLS-1$ } progress = value; } /** * @see org.java.plugin.boot.SplashHandler#getText() */ public String getText() { return text; } /** * @see org.java.plugin.boot.SplashHandler#setText(java.lang.String) */ public void setText(final String value) { text = value; } /** * @see org.java.plugin.boot.SplashHandler#getImage() */ public URL getImage() { return image; } /** * @see org.java.plugin.boot.SplashHandler#setImage(java.net.URL) */ public void setImage(final URL value) { image = value; } /** * @see org.java.plugin.boot.SplashHandler#isVisible() */ public boolean isVisible() { return isVisible; } /** * @see org.java.plugin.boot.SplashHandler#setVisible(boolean) */ public void setVisible(final boolean value) { if (isVisible == value) { return; } if (value) { SplashWindow.splash(image); isVisible = true; return; } SplashWindow.disposeSplash(); isVisible = false; } /** * @see org.java.plugin.boot.SplashHandler#getImplementation() */ public Object getImplementation() { return this; } } final class SplashHandlerWrapper implements SplashHandler { private final SplashHandler delegate; private final boolean isDisposeOnHide; SplashHandlerWrapper(final boolean disposeOnHide, final SplashHandler other) { isDisposeOnHide = disposeOnHide; delegate = other; } /** * @see org.java.plugin.boot.SplashHandler#configure( * org.java.plugin.util.ExtendedProperties) */ public void configure(final ExtendedProperties config) { delegate.configure(config); } /** * @see org.java.plugin.boot.SplashHandler#getProgress() */ public float getProgress() { return delegate.getProgress(); } /** * @see org.java.plugin.boot.SplashHandler#setProgress(float) */ public void setProgress(float value) { delegate.setProgress(value); } /** * @see org.java.plugin.boot.SplashHandler#getText() */ public String getText() { return delegate.getText(); } /** * @see org.java.plugin.boot.SplashHandler#setText(java.lang.String) */ public void setText(String value) { delegate.setText(value); } /** * @see org.java.plugin.boot.SplashHandler#getImage() */ public URL getImage() { return delegate.getImage(); } /** * @see org.java.plugin.boot.SplashHandler#setImage(java.net.URL) */ public void setImage(URL value) { delegate.setImage(value); } /** * @see org.java.plugin.boot.SplashHandler#isVisible() */ public boolean isVisible() { return delegate.isVisible(); } /** * @see org.java.plugin.boot.SplashHandler#setVisible(boolean) */ public void setVisible(boolean value) { delegate.setVisible(value); if (isDisposeOnHide && !delegate.isVisible()) { Boot.splashHandler = null; } } /** * @see org.java.plugin.boot.SplashHandler#getImplementation() */ public Object getImplementation() { return delegate.getImplementation(); } }