Java tutorial
/******************************************************************************* * Copyright (c) 2008, 2012 Gunnar Wagenknecht and others. * All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. * * Contributors: * Gunnar Wagenknecht - initial API and implementation *******************************************************************************/ package org.eclipse.gyrex.boot.internal.app; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; import javax.security.auth.login.Configuration; import org.eclipse.gyrex.boot.internal.BootActivator; import org.eclipse.gyrex.boot.internal.BootDebug; import org.eclipse.gyrex.boot.internal.jmx.JettyJmxConnector; import org.eclipse.gyrex.common.internal.applications.BaseApplication; import org.eclipse.gyrex.server.Platform; import org.eclipse.gyrex.server.internal.roles.LocalRolesManager; import org.eclipse.gyrex.server.internal.roles.ServerRoleDefaultStartOption; import org.eclipse.gyrex.server.internal.roles.ServerRolesRegistry; import org.eclipse.osgi.service.datalocation.Location; import org.eclipse.osgi.service.environment.EnvironmentInfo; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.UnhandledException; import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The server application which starts Gyrex. */ public class ServerApplication extends BaseApplication { private static final String BSN_EQUINOX_CONSOLE_SSH = "org.eclipse.equinox.console.ssh"; private static final String BSN_EQUINOX_DS = "org.eclipse.equinox.ds"; private static final Logger LOG = LoggerFactory.getLogger(ServerApplication.class); private static final Thread shutdownHook = new Thread("Shutdown Hook") { @Override public void run() { try { LOG.info("Shutting down..."); shutdown(null); } catch (final Exception e) { // ignore } }; }; private static final AtomicReference<ServerApplication> singletonInstance = new AtomicReference<ServerApplication>(); /** * Indicates if the server is running. * <p> * This method returns <code>true</code> if the server finished starting and * all boot roles were activated and the shutdown has not been initiated. * </p> * * @return <code>true</code> if running, <code>false</code> otherwise */ public static boolean isRunning() { final ServerApplication application = singletonInstance.get(); return (null != application) && application.running; } private static void printError(final String message, final Throwable cause) { System.err.println(); System.err.println(); System.err.println(StringUtils.leftPad("", 72, '*')); System.err.println(StringUtils.center(" Server Startup Error ", 72)); System.err.println(StringUtils.leftPad("", 72, '*')); System.err.println(); System.err.println(WordUtils.wrap(message, 72)); System.err.println(); if (cause != null) { System.err.println("Reported error:"); System.err.print(" "); System.err.println(WordUtils.wrap(cause.getMessage(), 70, String.format("%n "), false)); final List throwables = ExceptionUtils.getThrowableList(cause); if (throwables.size() > 1) { System.err.println(); System.err.println("Caused by:"); for (int i = 1; i < throwables.size(); i++) { System.err.print(" "); System.err.println(WordUtils.wrap(ExceptionUtils.getMessage((Throwable) throwables.get(i)), 70, String.format("%n "), false)); } } System.err.println(); } System.err.println(StringUtils.leftPad("", 72, '*')); System.err.println(); System.err.println(); } /** * Restarts a running server. */ public static void restart() { final ServerApplication application = singletonInstance.get(); if (null == application) throw new IllegalStateException("Platform not started."); if (BootDebug.debug) { LOG.debug("Relaunch request received!"); } // set restart flag application.restart = true; application.shutdownReason = null; // stop application application.stop(); } /** * Shutdown a running server because of an error. */ public static void shutdown(final Throwable cause) { final ServerApplication application = singletonInstance.get(); if (null == application) throw new IllegalStateException("Platform not started."); // don't restart application.restart = false; // set shutdown reason application.shutdownReason = cause; // stop application application.stop(); } /** running state */ private volatile boolean running; /** a flag indicating if the application should restart upon shutdown */ private volatile boolean restart; /** optional shutdown reason */ private volatile Throwable shutdownReason; /** the instance location */ private Location instanceLocation; /** * Creates a new instance. */ public ServerApplication() { debug = BootDebug.debug; } private void bootstrap() throws Exception { if (BootDebug.debug) { LOG.debug("Bootstrapping platform."); } // make sure that the declarative services are initialized startBundle(BSN_EQUINOX_DS, true); } private void checkInstanceLocation(final Location instanceLocation) { // check if a valid location is set // note, just checking Location#isSet isn't enough, we need to check the full URL // we also allow a default to apply, thus #getURL is better then just #isSet to initialize with a default // (we might later set our own here if we want this) if ((instanceLocation == null) || (null == instanceLocation.getURL()) || !instanceLocation.getURL().toExternalForm().startsWith("file:") || instanceLocation.isReadOnly()) { printError( "Gyrex needs a valid local instance location (aka. 'workspace'). Please start with the -data option pointing to a valid, writable file system path.", null); throw new StartAbortedException(); } // lock the location try { if (instanceLocation.lock()) // great return; // we failed to create the directory. // Two possibilities: // 1. directory is already in use // 2. directory could not be created final File workspaceDirectory = new File(instanceLocation.getURL().getFile()); if (workspaceDirectory.exists()) { printError("Could not launch the server because the associated workspace '" + workspaceDirectory.getAbsolutePath() + "' is currently in use by another Eclipse application.", null); } else { printError( "Could not launch the server because the specified workspace cannot be created. The specified workspace directory '" + workspaceDirectory.getAbsolutePath() + "' is either invalid or read-only.", null); } } catch (final IOException e) { printError("Unable to verify the specified workspace directory " + instanceLocation.getURL().toExternalForm() + ".", e); } throw new StartAbortedException(); } @Override protected void doCleanup() { // reset running flag running = false; // release instance location lock if (null != instanceLocation) { instanceLocation.release(); instanceLocation = null; } // reset shutdown reason shutdownReason = null; // remove shutdown hook try { Runtime.getRuntime().removeShutdownHook(shutdownHook); } catch (final Exception e) { // ignore } } @Override protected void doStart(final Map arguments) throws Exception { final String[] args = getApplicationArguments(arguments); if (!singletonInstance.compareAndSet(null, this)) throw new IllegalStateException("server application already running"); // get instance location try { instanceLocation = BootActivator.getInstance().getInstanceLocation(); } catch (final Exception e) { printError( "An error occurred reading the instance location (aka. 'workspace'). Please verify that the installation is correct and all required components are available.", e); throw new StartAbortedException(); } // check the instance location checkInstanceLocation(instanceLocation); try { // install shutdown hook Runtime.getRuntime().addShutdownHook(shutdownHook); // bootstrap the platform bootstrap(); // enable jxm jmxOn(); // start the Equinox SSH Console if available if (isConsoleEnabled()) { startConsole(); } // set the platform running state early to allow server roles // use Platform#isRunning in their activation logic running = true; // read enabled server roles from configuration final List<String> roles = getEnabledServerRoles(args); // activate server roles LocalRolesManager.activateRoles(roles, true); } catch (final Exception e) { if (BootDebug.debug) { LOG.debug("Platform start failed!", e); } // TODO should evaluate and suggest solution to Ops printError( "Unable to start server. Please verify the installation is correct and all required components are available.", e); throw new StartAbortedException(); } } @Override protected Object doStop() { // reset running flag running = false; // deactivate all roles try { LocalRolesManager.deactivateAllRoles(); } catch (final Exception ignore) { // ignore } // shutdown JMX connector jmxOff(); // print error final Throwable reason = shutdownReason; if (reason != null) { printError( "Server shutdown forced due to error in underlying system. Please verify the installation is correct and all required components are available. ", reason); return EXIT_ERROR; } // return result return restart ? EXIT_RESTART : EXIT_OK; } /** * Reads the enabled server roles from the preferences as well as from the * arguments passed when starting the server. * <p> * Possible arguments: * <ul> * <li><code>-roles <role1,role2,...,roleN></code> ... roles to start</li> * <li><code>-skipDefaultRolesAtBoot</code> ... skip start of default roles * at boot</li> * </ul> * </p> * * @param arguments * the arguments * @return the enabled roles */ private List<String> getEnabledServerRoles(final String[] arguments) { boolean ignoreDefaultRoles = false; // scan arguments for submitted roles final List<String> roleIds = new ArrayList<String>(); for (int i = 0; i < arguments.length; i++) { final String arg = arguments[i]; if ("-skipDefaultRolesAtBoot".equalsIgnoreCase(arg)) { ignoreDefaultRoles = true; } else if ("-roles".equalsIgnoreCase(arg)) { if (++i >= arguments.length) throw new IllegalArgumentException( "The argument '-roles' requires a following argument with the server roles to start."); final String[] specifiedRoles = StringUtils.split(arguments[i], ','); if ((null == specifiedRoles) || (specifiedRoles.length == 0)) throw new IllegalArgumentException( "The specified server roles could not be identified. Please specify at least one role. You may specify multiple rows using a comma separated list."); for (final String role : specifiedRoles) { if (StringUtils.isNotBlank(role)) { if (!roleIds.contains(role)) { if (BootDebug.roles) { LOG.debug("Role submitted via command line: " + role); } roleIds.add(role); } } } } } // add default start roles if (!ignoreDefaultRoles) { final Collection<String> defaultRoles = ServerRolesRegistry.getDefault() .getRolesToStartByDefault(ServerRoleDefaultStartOption.Trigger.ON_BOOT); for (final String role : defaultRoles) { if (!roleIds.contains(role)) { if (BootDebug.roles) { LOG.debug("Default start boot role: " + role); } roleIds.add(role); } } } return roleIds; } @Override protected Logger getLogger() { return LOG; } private boolean isConsoleEnabled() { // check framework arguments final String[] args = BootActivator.getEnvironmentInfo().getFrameworkArgs(); for (int i = 0; i < args.length; i++) { if (args[i].equals("-console")) { if (((i + 1) < args.length) && args[i + 1].equals("none")) return false; else return true; } } return false; } private void jmxOff() { try { JettyJmxConnector.stop(); } catch (final ClassNotFoundException e) { LOG.debug("Jetty JMX is not available. Ignoring error during shutdown. ({})", e.getMessage()); } catch (final LinkageError e) { LOG.debug("Jetty JMX is not available. Ignoring error during shutdown. ({})", e.getMessage()); } catch (final Exception e) { LOG.warn("Error while stopping Jetty JMX. {}", ExceptionUtils.getRootCauseMessage(e), e); } } private void jmxOn() { try { JettyJmxConnector.start(); } catch (final ClassNotFoundException e) { LOG.warn("Jetty JMX is not available. Please configure JMX support manually. ({})", e.getMessage()); } catch (final LinkageError e) { LOG.warn("Jetty JMX is not available. Please configure JMX support manually. ({})", e.getMessage()); } catch (final Exception e) { throw new UnhandledException( "An error occured while starting the embedded JMX server. Please verify the port/host configuration is correct and no other server is running. JMX can also be disabled by setting system property 'gyrex.jmxrmi.skip'.", e); } } private boolean startBundle(final String symbolicName, final boolean required) throws BundleException { final Bundle bundle = BootActivator.getInstance().getBundle(symbolicName); if (null != bundle) { bundle.start(Bundle.START_TRANSIENT); return true; } else if (required) { printError(String.format("Required bundle '%s' not available. Your system will not function properly.", symbolicName), null); throw new StartAbortedException(); } return false; } private void startConsole() throws BundleException { // enable SSH console // TODO: might want to use ConfigAdmin? final EnvironmentInfo environmentInfo = BootActivator.getEnvironmentInfo(); if (null == environmentInfo.getProperty("osgi.console.ssh")) { // set default ssh port environmentInfo.setProperty("osgi.console.ssh", String.valueOf(Platform.getInstancePort(3122))); } if (null == environmentInfo.getProperty("ssh.custom.publickeys.auth")) { // enable custom ssh authentication environmentInfo.setProperty("ssh.custom.publickeys.auth", "true"); } if (startBundle(BSN_EQUINOX_CONSOLE_SSH, false)) { try { final Object authenticator = BootActivator.getInstance().getBundle().loadClass( "org.eclipse.gyrex.boot.internal.ssh.InstanceLocationAuthorizedKeysFileAuthenticator") .newInstance(); BootActivator.getInstance().getServiceHelper().registerService( "org.apache.sshd.server.PublickeyAuthenticator", authenticator, "Eclipse Gyrex", "Equionx SSH Console authorized_keys support for Gyrex.", null, Integer.MAX_VALUE); } catch (final ClassNotFoundException e) { // ignore } catch (final LinkageError e) { // ignore } catch (final Exception e) { // error (but do not fail) LOG.warn("Unable to register authorized_keys file support for Equinox SSH Console. ", e); } // allow any combination of username/password in development mode if (Platform.inDevelopmentMode()) { final AppConfigurationEntry[] allowAny = new AppConfigurationEntry[] { new AppConfigurationEntry("org.eclipse.gyrex.boot.console.jaas.AllowAnyUserLoginModule", LoginModuleControlFlag.SUFFICIENT, new HashMap<String, Object>()) }; final Configuration configuration = Configuration.getConfiguration(); Configuration.setConfiguration(new Configuration() { @Override public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { final AppConfigurationEntry[] entry = configuration.getAppConfigurationEntry(name); if (((entry == null) || (entry.length == 0)) && "equinox_console".equals(name)) return allowAny; return entry; } }); } } } }