/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.bootstrap;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.LogManager;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import org.jboss.Version;
import org.jboss.bootstrap.spi.Bootstrap;
import org.jboss.bootstrap.spi.ServerConfig;
import org.jboss.bootstrap.spi.ServerProcess;
import org.jboss.logging.Logger;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.net.protocol.URLStreamHandlerFactory;
import org.jboss.util.StopWatch;
/**
* A Server implementation that uses the deployer-beans.xml and ProfileService
* to boot the server.
*
* @author Scott.Stark@jboss.org
* @author Dimitris.Andreadis@jboss.org
* @author adrian@jboss.org
* @version $Revision: 85188 $
*/
public abstract class AbstractServerImpl extends NotificationBroadcasterSupport
implements ServerProcess, NotificationEmitter
{
/** Instance logger. */
protected Logger log;
/** Container for version information. */
private final Version version = Version.getInstance();
/** Package information for org.jboss */
private final Package jbossPackage = Package.getPackage("org.jboss");
/** The basic configuration for the server. */
private BaseServerConfig config;
/** The optional configuration metadata for the server */
private Map<String, Object> metadata;
/** When the server was started. */
private Date startDate;
/** Flag to indicate if we are started. */
private boolean started;
/** A flag indicating if start has been called */
private boolean isInStart;
/** A flag indicating if shutdown has been called */
private boolean isInShutdown;
/** A flag indicating if shutdownServer has been called */
private boolean isInternalShutdown;
/** The JVM shutdown hook */
private ShutdownHook shutdownHook;
/** The JBoss Life Thread */
private LifeThread lifeThread;
/** The bootstraps */
private List<Bootstrap> bootstraps = new CopyOnWriteArrayList<Bootstrap>();
/** The started bootstraps */
private List<Bootstrap> startedBootstraps = new CopyOnWriteArrayList<Bootstrap>();
/**
* No-arg constructor for ServerImpl
*/
public AbstractServerImpl()
{
}
/**
* Add a bootstrap
*
* @param bootstrap the bootstrap
* @throws IllegalArgumentException for a null bootstrap
*/
public void addBootstrap(Bootstrap bootstrap)
{
if (bootstrap == null)
throw new IllegalArgumentException("Null bootstrap");
bootstraps.add(bootstrap);
}
/**
* Remove a bootstrap
*
* @param bootstrap the bootstrap
* @throws IllegalArgumentException for a null bootstrap
*/
public void removeBootstrap(Bootstrap bootstrap)
{
if (bootstrap == null)
throw new IllegalArgumentException("Null bootstrap");
bootstraps.remove(bootstrap);
}
/**
* Initialize the server by calling init(props, null);
*/
public void init(final Properties props)
throws IllegalStateException, Exception
{
init(props, null);
}
/**
* Initialize the Server instance.
*
* @param props - The configuration properties for the server.
* @param metadata configuration metadata for the server
*
* @throws IllegalStateException Already initialized.
* @throws Exception Failed to initialize.
*/
public void init(final Properties props, final Map<String, Object> metadata)
throws IllegalStateException, Exception
{
if (props == null)
throw new IllegalArgumentException("props is null");
if (config != null)
throw new IllegalStateException("already initialized");
if (metadata == null)
this.metadata = Collections.emptyMap();
else
this.metadata = Collections.unmodifiableMap(metadata);
ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
doInit(props);
}
finally
{
Thread.currentThread().setContextClassLoader(oldCL);
}
}
/**
* Initialize server configuration and jvm settings.
*
* @param props
* @throws Exception
*/
private void doInit(final Properties props) throws Exception
{
// Create a new config object from the give properties
this.config = new BaseServerConfig(props);
// Set the VM temp directory to the server tmp dir
boolean overrideTmpDir = Boolean.getBoolean("jboss.server.temp.dir.overrideJavaTmpDir");
if (overrideTmpDir)
{
File serverTmpDir = config.getServerTempDir();
System.setProperty("java.io.tmpdir", serverTmpDir.toString());
}
// Initialize the logging layer using the ServerImpl class. The server log
// directory is initialized prior to this to ensure the jboss.server.log.dir
//system property is set in case its used by the logging configs.
config.getServerLogDir();
log = Logger.getLogger(getClass());
// Setup URL handlers - do this before initializing the ServerConfig
initURLHandlers();
config.initURLs();
log.info("Starting JBoss (Microcontainer)...");
if (jbossPackage != null)
{
// Show what release this is...
log.info("Release ID: " +
jbossPackage.getImplementationTitle() + " " +
jbossPackage.getImplementationVersion());
}
else
{
log.warn("could not get package info to display release, either the " +
"jar manifest in jboss-system.jar has been mangled or you're " +
"running unit tests from ant outside of JBoss itself.");
}
log.debug("Using config: " + config);
// make sure our impl type is exposed
log.debug("Server type: " + getClass());
// log the boot classloader
ClassLoader cl = getClass().getClassLoader();
log.debug("Server loaded through: " + cl.getClass().getName());
// Log the basic configuration elements
log.info("Bootstrap URL: " + config.getBootstrapURL());
log.info("Home Dir: " + config.getHomeDir());
log.info("Home URL: " + config.getHomeURL());
log.info("Library URL: " + config.getLibraryURL());
log.info("Patch URL: " + config.getPatchURL());
log.info("Common Base URL: " + config.getCommonBaseURL());
log.info("Common Library URL: " + config.getCommonLibraryURL());
log.info("Server Name: " + config.getServerName());
log.info("Server Base Dir: " + config.getServerBaseDir());
log.info("Server Base URL: " + config.getServerBaseURL());
log.info("Server Config URL: " + config.getServerConfigURL());
log.info("Server Home Dir: " + config.getServerHomeDir());
log.info("Server Home URL: " + config.getServerHomeURL());
log.info("Server Data Dir: " + config.getServerDataDir());
log.info("Server Library URL: " + config.getServerLibraryURL());
log.info("Server Log Dir: " + config.getServerLogDir());
log.info("Server Native Dir: " + config.getServerNativeDir());
log.info("Server Temp Dir: " + config.getServerTempDir());
log.info("Server Temp Deploy Dir: " + config.getServerTempDeployDir());
}
/**
* The <code>initURLHandlers</code> method calls
* internalInitURLHandlers. if requireJBossURLStreamHandlers is
* false, any exceptions are logged and ignored.
*
* TODO move to the common project alongside URLStreamHandlerFactory
*/
private void initURLHandlers()
{
if (config.getRequireJBossURLStreamHandlerFactory())
{
internalInitURLHandlers();
}
else
{
try
{
internalInitURLHandlers();
}
catch (SecurityException e)
{
log.warn("You do not have permissions to set URLStreamHandlerFactory", e);
}
catch (Error e)
{
log.warn("URLStreamHandlerFactory already set", e);
}
}
}
/**
* Set up our only URLStreamHandlerFactory.
* This is needed to ensure Sun's version is not used (as it leaves files
* locked on Win2K/WinXP platforms.
*/
private void internalInitURLHandlers()
{
try
{
// Install a URLStreamHandlerFactory that uses the TCL
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory());
// Preload JBoss URL handlers
URLStreamHandlerFactory.preload();
}
catch (Error error)
{ // very naughty but we HAVE to do this or
// we'll fail if we ever try to do this again
log.warn("Caught Throwable Error, this probably means " +
"we've already set the URLStreamHAndlerFactory before");
}
// Include the default JBoss protocol handler package
String handlerPkgs = System.getProperty("java.protocol.handler.pkgs");
if (handlerPkgs != null)
{
handlerPkgs += "|org.jboss.net.protocol";
}
else
{
handlerPkgs = "org.jboss.net.protocol";
}
System.setProperty("java.protocol.handler.pkgs", handlerPkgs);
}
/**
* Get the typed server configuration object which the
* server has been initalized to use.
*
* @return Typed server configuration object.
* @throws IllegalStateException Not initialized.
*/
@ManagementProperty(managed=true)
public ServerConfig getConfig() throws IllegalStateException
{
if (config == null)
throw new IllegalStateException("not initialized");
return config;
}
/**
* Get the optional server configuration metadata
* @return a possibly empty map of configuration metadata.
*/
public Map<String, Object> getMetaData()
{
return metadata;
}
/**
* Set the server configuration metadata
* @param metadata
*/
public void setMetaData(Map<String, Object> metadata)
{
if (metadata == null)
this.metadata = Collections.emptyMap();
else
this.metadata = Collections.unmodifiableMap(metadata);
}
/**
* Sets the Server Config
*
* Package access for use in testing only
*
* @param config
*/
void setConfig(BaseServerConfig config)
{
assert config!=null:"Specified " + ServerConfig.class.getSimpleName() + " was null";
this.config = config;
}
/**
* Check if the server is started.
*
* @return True if the server is started, else false.
*/
public boolean isStarted()
{
return started;
}
/**
* Check if the shutdown operation has been called/is in progress.
*
* @return true if shutdown has been called, false otherwise
*/
public boolean isInShutdown()
{
return isInShutdown;
}
/**
* Start the Server instance.
*
* @throws IllegalStateException Already started or not initialized.
* @throws Exception Failed to start.
*/
public void start() throws IllegalStateException, Exception
{
synchronized (this)
{
if (isInStart == false)
{
isInStart = true;
}
else
{
log.debug("Already in start, ignoring duplicate start");
return;
}
}
// make sure we are initialized
ServerConfig config = getConfig();
// make sure we aren't started yet
if (started)
throw new IllegalStateException("already started");
ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
try
{
ClassLoader myCL = getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(myCL);
// See how long it takes us to start up
StopWatch watch = new StopWatch(true);
// Remember when we we started
startDate = new Date();
// Install the shutdown hook
shutdownHook = new ShutdownHook();
shutdownHook.setDaemon(true);
try
{
Runtime.getRuntime().addShutdownHook(shutdownHook);
if (log != null && log.isDebugEnabled())
{
log.debug("Shutdown hook added " + shutdownHook);
}
}
catch (Exception e)
{
log.warn("Failed to add shutdown hook; ignoring", e);
}
// Do the main start
doStart(watch);
// TODO Fix the TCL hack used here!
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try
{
// Run the bootstraps
for (Bootstrap bootstrap : bootstraps)
{
Thread.currentThread().setContextClassLoader(bootstrap.getClass().getClassLoader());
startedBootstraps.add(0, bootstrap);
bootstrap.start(this);
}
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
if (config.isInstallLifeThread())
{
lifeThread = new LifeThread();
if (log != null && log.isDebugEnabled())
{
log.debug("Installing life thread " + lifeThread);
}
lifeThread.start();
}
started = true;
// Send a notification that the startup is complete
Notification msg = new Notification(START_NOTIFICATION_TYPE, this, 1);
msg.setUserData(new Long(watch.getLapTime()));
sendNotification(msg);
watch.stop();
if (jbossPackage != null)
{
// Tell the world how fast it was =)
if (log != null)
{
log.info("JBoss (Microcontainer) [" + jbossPackage.getImplementationVersion() + "] Started in " + watch);
}
}
else
{
if (log != null)
{
log.info("JBoss (Microcontainer) [unknown version] Started in " + watch);
}
}
}
catch (Throwable t)
{
if (log != null && log.isDebugEnabled())
{
log.debug("Failed to start", t);
}
if (t instanceof Exception)
throw (Exception)t;
if (t instanceof Error)
throw (Error)t;
throw new RuntimeException("Unexpected error", t);
}
finally
{
Thread.currentThread().setContextClassLoader(oldCL);
isInStart = false;
}
}
/**
* Override to perform the start operations
*
* @param watch the stop watch
* @throws Throwable for any error
*/
protected abstract void doStart(StopWatch watch) throws Throwable;
/**
* Override to perform the shutdown
*/
protected abstract void doShutdown();
/**
* Shutdown the server
*/
protected void shutdownServer()
{
if (log != null && log.isTraceEnabled())
log.trace("Shutdown caller:", new Throwable("Here"));
// avoid entering twice; may happen when called directly
// from AbstractServerImpl.shutdown(), then called again when all
// non-daemon threads have exited and the ShutdownHook runs.
if (isInternalShutdown)
return;
else
isInternalShutdown = true;
// Send a notification that server stop is initiated
Notification msg = new Notification(STOP_NOTIFICATION_TYPE, this, 2);
sendNotification(msg);
// TODO Fix the TCL hack used here!
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try
{
for (Bootstrap bootstrap : startedBootstraps)
bootstrap.prepareShutdown(this);
// Do the bootstraps in reverse order
for (Bootstrap bootstrap : startedBootstraps)
{
Thread.currentThread().setContextClassLoader(bootstrap.getClass().getClassLoader());
try
{
bootstrap.shutdown(this);
}
catch (Throwable t)
{
if (log != null)
{
log.warn("Error shutting down bootstrap: " + bootstrap, t);
}
}
}
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
try
{
doShutdown();
}
finally
{
// Done
if (log != null)
{
log.info("Shutdown complete");
}
System.out.println("Shutdown complete");
}
}
/**
* Shutdown the Server instance and run shutdown hooks.
*
* <p>If the exit on shutdown flag is true, then {@link #exit()}
* is called, else only the shutdown hook is run.
*
* @throws IllegalStateException No started.
*/
public void shutdown() throws IllegalStateException
{
if (log != null && log.isTraceEnabled())
log.trace("Shutdown caller:", new Throwable("Here"));
if (!started)
throw new IllegalStateException("not started");
if (isInShutdown)
throw new IllegalStateException("already in shutdown mode");
isInShutdown = true;
boolean exitOnShutdown = config.getExitOnShutdown();
boolean blockingShutdown = config.getBlockingShutdown();
if (log != null)
{
log.info("Shutting down the server, blockingShutdown: " + blockingShutdown);
if (log.isDebugEnabled())
{
log.debug("exitOnShutdown: " + exitOnShutdown);
log.debug("blockingShutdown: " + blockingShutdown);
}
}
if (exitOnShutdown)
{
exit(0);
}
else
{
// signal lifethread to exit; if no non-daemon threads
// remain, the JVM will eventually exit
lifeThread.interrupt();
if (blockingShutdown)
{
shutdownServer();
}
else
{
// start in new thread to give positive
// feedback to requesting client of success.
new Thread()
{
public void run()
{
// just run the hook, don't call System.exit, as we may
// be embeded in a vm that would not like that very much
shutdownServer();
}
}.start();
}
}
}
/**
* Exit the JVM, run shutdown hooks, shutdown the server.
*
* @param exitcode The exit code returned to the operating system.
*/
public void exit(final int exitcode)
{
// exit() in new thread so that we might have a chance to give positive
// feed back to requesting client of success.
new Thread()
{
public void run()
{
log.info("Server exit(" + exitcode + ") called");
// Set exit code in the shutdown hook, in case halt is enabled
shutdownHook.setHaltExitCode(exitcode);
// Initiate exiting, shutdown hook will be called
Runtime.getRuntime().exit(exitcode);
}
}.start();
}
/**
* Exit the JVM with code 1, run shutdown hooks, shutdown the server.
*/
public void exit()
{
exit(1);
}
/**
* Forcibly terminates the currently running Java virtual machine.
*
* @param exitcode The exit code returned to the operating system.
*/
public void halt(final int exitcode)
{
// halt() in new thread so that we might have a chance to give positive
// feed back to requesting client of success.
new Thread()
{
public void run()
{
System.err.println("Server halt(" + exitcode + ") called, halting the JVM now!");
Runtime.getRuntime().halt(exitcode);
}
}.start();
}
/**
* Forcibly terminates the currently running Java virtual machine.
* Halts with code 1.
*/
public void halt()
{
halt(1);
}
////////////////////////////////////////////////////////////////////////// /
// Runtime Access //
////////////////////////////////////////////////////////////////////////// /
/**
* A simple helper used to log the Runtime memory information.
*
* @param rt the runtime
*/
private void logMemoryUsage(final Runtime rt)
{
log.info("Total/free memory: " + rt.totalMemory() + "/" + rt.freeMemory());
}
/**
* Hint to the JVM to run the garbage collector.
*/
public void runGarbageCollector()
{
Runtime rt = Runtime.getRuntime();
logMemoryUsage(rt);
rt.gc();
log.info("Hinted to the JVM to run garbage collection");
logMemoryUsage(rt);
}
/**
* Hint to the JVM to run any pending object finalizations.
*/
public void runFinalization()
{
Runtime.getRuntime().runFinalization();
log.info("Hinted to the JVM to run any pending object finalizations");
}
/**
* Enable or disable tracing method calls at the Runtime level.
*
* @param flag whether to enable trace
*/
public void traceMethodCalls(final Boolean flag)
{
Runtime.getRuntime().traceMethodCalls(flag.booleanValue());
}
/**
* Enable or disable tracing instructions the Runtime level.
*
* @param flag whether to enable trace
*/
public void traceInstructions(final Boolean flag)
{
Runtime.getRuntime().traceInstructions(flag.booleanValue());
}
///////////////////////////////////////////////////////////////////////////
// Server Information //
///////////////////////////////////////////////////////////////////////////
@ManagementProperty(description="The server start time")
public Date getStartDate()
{
return startDate;
}
@ManagementProperty(description="The server version string")
public String getVersion()
{
return version.toString();
}
@ManagementProperty(description="The server version name")
public String getVersionName()
{
return version.getName();
}
@ManagementProperty(description="The server version number string")
public String getVersionNumber()
{
return version.getVersionNumber();
}
@ManagementProperty(description="The server build number")
public String getBuildNumber()
{
return version.getBuildNumber();
}
@ManagementProperty(description="The server build JVM")
public String getBuildJVM()
{
return version.getBuildJVM();
}
@ManagementProperty(description="The server build OS")
public String getBuildOS()
{
return version.getBuildOS();
}
@ManagementProperty(description="The server build ID")
public String getBuildID()
{
return version.getBuildID();
}
/**
* The server build date
* @return server build date
*/
@ManagementProperty(description="The server build date")
public String getBuildDate()
{
return version.getBuildDate();
}
///////////////////////////////////////////////////////////////////////////
// Lifecycle Thread //
///////////////////////////////////////////////////////////////////////////
/** A simple thread that keeps the vm alive in the event there are no
* other threads started.
*/
private class LifeThread extends Thread
{
Object lock = new Object();
LifeThread()
{
super("JBossLifeThread");
}
public void run()
{
synchronized (lock)
{
try
{
lock.wait();
}
catch (InterruptedException ignore)
{
}
}
log.info("LifeThread.run() exits!");
}
}
///////////////////////////////////////////////////////////////////////////
// Shutdown Hook //
///////////////////////////////////////////////////////////////////////////
private class ShutdownHook extends Thread
{
/** Whether to halt the JMV at the end of the shutdown hook */
private boolean forceHalt = true;
/** The exit code to use if forceHalt is enabled */
private int haltExitCode;
public ShutdownHook()
{
super("JBoss Shutdown Hook");
String value = SecurityActions.getSystemProperty("jboss.shutdown.forceHalt", null);
if (value != null)
{
forceHalt = Boolean.valueOf(value).booleanValue();
}
}
public void setHaltExitCode(int haltExitCode)
{
this.haltExitCode = haltExitCode;
}
public void run()
{
log.info("Runtime shutdown hook called, forceHalt: " + forceHalt);
// shutdown the server
shutdownServer();
// Execute the jdk JBossJDKLogManager doReset via reflection
LogManager lm = LogManager.getLogManager();
try
{
Class<?>[] sig = {};
Method doReset = lm.getClass().getDeclaredMethod("doReset", sig);
Object[] args = {};
doReset.invoke(lm, args);
}
catch(Exception e)
{
if (log.isTraceEnabled())
log.trace("No doReset found?", e);
}
// later bitch - other shutdown hooks may be killed
if (forceHalt)
{
System.out.println("Halting VM");
Runtime.getRuntime().halt(haltExitCode);
}
}
}
}