/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, 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.impl.base.server;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationBroadcasterSupport;
import org.jboss.bootstrap.api.config.InvalidConfigurationException;
import org.jboss.bootstrap.api.config.ServerConfig;
import org.jboss.bootstrap.api.lifecycle.LifecycleEventException;
import org.jboss.bootstrap.api.lifecycle.LifecycleEventHandler;
import org.jboss.bootstrap.api.lifecycle.LifecycleState;
import org.jboss.bootstrap.api.server.Server;
import org.jboss.bootstrap.spi.Bootstrap;
import org.jboss.bootstrap.spi.config.ConfigurationInitializer;
import org.jboss.bootstrap.spi.config.ConfigurationValidator;
import org.jboss.bootstrap.spi.server.ServerInitializer;
import org.jboss.bootstrap.spi.server.ServerProvider;
import org.jboss.logging.Logger;
import org.jboss.util.StopWatch;
/**
* AbstractServer
*
* Generic support for Server implementations
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
public abstract class AbstractServer<K extends Server<K, T>, T extends ServerConfig<T>>
extends
NotificationBroadcasterSupport implements ServerProvider<K, T>, NotificationBroadcaster
{
//-------------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
private static final Logger log = Logger.getLogger(AbstractServer.class);
//-------------------------------------------------------------------------------------||
// Instance Members -------------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/**
* Actual class used in casting to type K
*/
private Class<K> actualClass;
/**
* Current state of the server. Synchronized on "this".
* Volatile so we can return the current state without blocking.
*/
private volatile LifecycleState state;
/**
* Underlying configuration. Must be a Thread-safe implementation
* as it's exported
*/
private volatile T configuration;
/**
* Validator for the configuration. Synchronized on "this". Requires
* Thread-safe impl (as it's exported)
*/
private ConfigurationValidator<T> validator;
/**
* Initializer for the configuration. Synchronized on "this". Requires
* Thread-safe impl (as it's exported). Volatile for immediate
* accessor return.
*/
private volatile ConfigurationInitializer<T> configInitializer;
/**
* Server initializer. Synchronized on "this". Requires
* Thread-safe impl (as it's exported). Volatile for immediate
* accessor return.
*/
private volatile ServerInitializer<K, T> serverInitializer;
/**
* The list of bootstraps to run upon start, requires Thread-safe impl.
*/
private final List<Bootstrap<K, T>> bootstraps = new CopyOnWriteArrayList<Bootstrap<K, T>>();
/**
* The bootstraps that have been started, requires Thread-safe impl.
*/
private final List<Bootstrap<K, T>> startedBootstraps = new CopyOnWriteArrayList<Bootstrap<K, T>>();
/**
* Event handlers for each lifecycle state change
*/
private final Map<LifecycleState, Set<LifecycleEventHandler>> eventHandlers = new ConcurrentHashMap<LifecycleState, Set<LifecycleEventHandler>>();
/**
* Thread used for startup. Part of the server's internal state so we can interrupt
* this if necessary. Volatile so we don't need to block in order to
* get the correct view.
*/
private volatile Thread startupThread = null;
//-------------------------------------------------------------------------------------||
// Constructors -----------------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/**
* Constructor
*
* Creates a new Server using an uninitialized, default configuration
* (which is an instance of the Class specified by
* {@link AbstractServer#getDefaultServerConfigClass()}
* @throws IllegalArgumentException If the actual class was not supplied
*/
protected AbstractServer(final Class<K> actualClass) throws IllegalArgumentException
{
// Pass it to the other ctor
this(actualClass, null);
}
/**
* Constructor
*
* Creates a new Server, specifying the configuration to be used.
*
* @param configuration The configuration to set. If null a new default
* configuration will be made (which is an instance of the Class specified by
* {@link AbstractServer#getDefaultServerConfigClass()}
* @throws IllegalArgumentException If the configuration or actual class has not been supplied
*/
protected AbstractServer(final Class<K> actualClass, final T configuration) throws IllegalArgumentException
{
// Precondition check
if (actualClass == null)
{
throw new IllegalArgumentException("Actual class must be specified");
}
// Initialize
T configToSet = configuration;
// Check that a configuration has been supplied
if (configToSet == null)
{
/*
* Make a new Configuration
*/
log.debug("No configuration has been supplied, so making a default one");
T newConfiguration = null;
// Get the default config class
final Class<? extends T> configClass = this.getDefaultServerConfigClass();
try
{
// Create
newConfiguration = SecurityActions.newInstance(configClass);
log.debug("Created new default configuration: " + newConfiguration);
}
catch (final Throwable t)
{
throw new RuntimeException("Could not create default configuration of type " + configClass, t);
}
// Set
configToSet = newConfiguration;
}
// Set properties
this.setActualClass(actualClass);
this.setConfiguration(configToSet);
// Set the state directly (ie. bypass callback contract of setState()),
// this isn't *really* a state change so much as an initialization
this.state = LifecycleState.INSTANCIATED;
}
//-------------------------------------------------------------------------------------||
// Required Implementations -----------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#getConfiguration()
*/
public T getConfiguration()
{
return this.configuration;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#setConfiguration(org.jboss.bootstrap.spi.config.ServerConfig)
*/
// Synchronized on "this" to ensure we don't set the config in the middle of some
// other lifecycle operation
public synchronized void setConfiguration(final T configuration)
{
// Log and set
log.tracef("Set configuration: %s", configuration);
this.configuration = configuration;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#getState()
*/
public final LifecycleState getState()
{
final LifecycleState state = this.state;
if (state == null)
{
throw new IllegalStateException("null state");
}
return state;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#stop()
*/
public void stop() throws IllegalStateException, Exception
{
// Alias to shutdown()
this.shutdown();
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#shutdown()
*/
public void shutdown() throws IllegalStateException, Exception
{
// Log
log.trace("Received request to shutdown");
// If we're in startup
final Thread startupThread = this.startupThread;
if (startupThread != null)
{
// Interrupt startup
startupThread.interrupt();
// Wait until it's done
try
{
startupThread.join();
}
catch (final InterruptedException ie)
{
// Warn and clear the flag
log.warn("Interrupted while shutdown is waiting for server startup to complete");
Thread.interrupted();
}
}
// Synchronized for atomicity
synchronized (this)
{
// Ensure running
final LifecycleState required = LifecycleState.STARTED;
final LifecycleState actual = this.getState();
this.checkState(required, actual);
// Initiate shutdown sequence
log.info("Stopping: " + this);
final StopWatch watch = new StopWatch(true);
this.setState(LifecycleState.STOPPING);
// Send JMX Notification
this.sendStopJmxNotification();
// Shutdown the Bootstraps
log.trace("Shutting down bootstraps");
this.shutdownBootstraps();
// Shutdown
log.trace("Calling implementation class shutdown...");
this.doShutdown();
// Let the initializer clean up
final ServerInitializer<K, T> serverInitializer = this.getServerInitializer();
if (serverInitializer != null)
{
log.tracef("Calling to clean up for shutdown: %s", serverInitializer);
serverInitializer.cleanup(this.covarientReturn());
}
// Done and set states
log.infof("Stopped: %s in %s", this, watch);
// So we fire "stopped" events and draw the difference
// between IDLE (which may also designate pre-start)
this.setState(LifecycleState.STOPPED);
this.setState(LifecycleState.IDLE);
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#start()
*/
public void start() throws IllegalStateException, Exception
{
// Log
log.trace("Received request to start");
// Invoke init() if necessary
if (getState().equals(LifecycleState.INSTANCIATED))
{
log.debug("Invoking implicit initialization from start()");
AbstractServer.this.initialize();
}
// Make a new Thread to run startup, so we may interrupt it
// if necessary for shutdown
final StartServerTask task = new StartServerTask();
final Thread thread = new Thread(task);
this.startupThread = thread;
thread.start();
// Wait on startup to complete
try
{
thread.join();
}
catch (final InterruptedException ie)
{
// Clear the flag
Thread.interrupted();
}
// Check for Exceptions encountered during run()
final Exception exceptionOnStart = task.getException();
if (exceptionOnStart != null)
{
// Throw up the stack
throw exceptionOnStart;
}
}
/**
* StartServerTask
*
* Task to start the server in a new Thread of execution. This will
* allow the calling Thread to interrupt the startup process
* in the case the server is requested to shutdown during start.
*
* JBBOOT-75
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
class StartServerTask implements Runnable
{
/**
* Any exception encountered while
* the task was run
*/
private Exception exception;
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run()
{
// Get a reference to this of the outer class
final AbstractServer<?, ?> thisRef = AbstractServer.this;
/*
* Synchronized for atomicity
*/
synchronized (thisRef)
{
try
{
// Ensure idle
final LifecycleState required = LifecycleState.IDLE;
final LifecycleState actual = getState();
checkState(required, actual);
// Initiate start sequence
log.infof("Starting: %s", thisRef);
final StopWatch watch = new StopWatch(true);
setState(LifecycleState.STARTING);
// Start
log.trace("Entering implementation class start...");
doStart();
// Run the bootstraps
log.trace("Starting bootstraps...");
startBootstraps();
// Send JMX Start Notification
sendStartJmxNotification();
// Done
log.infof("%s Started in %s", thisRef, watch);
setState(LifecycleState.STARTED);
}
catch (final Exception e)
{
log.debugf("Encountered exception while starting, caching it to be thrown later: %s", e);
this.exception = e;
return;
}
finally
{
// Clear the startup Thread
AbstractServer.this.startupThread = null;
}
}
}
/**
* Obtains the exception encountered during start, or null
* if none was thrown
*
* @return
*/
Exception getException()
{
return this.exception;
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#addBootstrap(org.jboss.bootstrap.spi.Bootstrap)
*/
public void addBootstrap(final Bootstrap<K, T> bootstrap) throws IllegalStateException, IllegalArgumentException
{
// Precondition check
if (bootstrap == null)
{
throw new IllegalArgumentException("Supplied bootstrap was null");
}
// Add
this.getBootstraps().add(bootstrap);
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#removeBootstrap(org.jboss.bootstrap.spi.Bootstrap)
*/
public void removeBootstrap(final Bootstrap<K, T> bootstrap) throws IllegalStateException, IllegalArgumentException
{
// Remove
boolean removed = bootstraps.remove(bootstrap);
// Postcondition check
if (!removed)
{
throw new IllegalArgumentException(
"Specified bootstrap could not be removed because it is not present in the list");
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#getValidator()
*/
public ConfigurationValidator<T> getValidator()
{
return this.validator;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#initialize()
*/
// Synchronized for atomicity
public final synchronized void initialize() throws IllegalStateException, InvalidConfigurationException,
LifecycleEventException
{
/*
* Precondition checks
*/
// Config must be in place
final T config = this.getConfiguration();
if (config == null)
{
throw new IllegalStateException("Configuration must be supplied before server is initialized");
}
// State must be instanciated
final LifecycleState state = this.getState();
if (!state.equals(LifecycleState.INSTANCIATED))
{
throw new IllegalStateException("Cannot initialize an already initialized server, state is: " + state);
}
// Set state to pre-init to fire lifecycle callbacks
this.setState(LifecycleState.PRE_INIT);
// Log
log.debugf("Initializing server: %s", this);
// Do the actual initialization logic
this.doInitialize();
// Freeze config
config.freeze();
// Set state (firing lifecycle callbacks along the way)
this.setState(LifecycleState.INITIALIZED);
this.setState(LifecycleState.IDLE);
}
/**
* Implementation-specific initialization logic; invoked by
* {@link AbstractServer#initialize()}
*
* @throws IllegalStateException
* @throws InvalidConfigurationException
* @throws LifecycleEventException
*/
protected void doInitialize() throws IllegalStateException, InvalidConfigurationException, LifecycleEventException
{
// Config must be in place
final T config = this.getConfiguration();
if (config == null)
{
throw new IllegalStateException("Configuration must be supplied before server is initialized");
}
// If there's a configuration initializer, use it
final ConfigurationInitializer<T> configInitializer = this.getConfigInitializer();
if (configInitializer != null)
{
log.trace("Performing configuration initialization...");
configInitializer.initialize(config);
}
else
{
log.trace("No configuration initializer supplied, skipping");
}
// Validate
log.trace("Validating config...");
this.validate(config);
/*
* If there's an initializer, use it
*/
final ServerInitializer<K, T> serverInitializer = this.getServerInitializer();
if (serverInitializer != null)
{
serverInitializer.initialize(this.covarientReturn());
}
else
{
log.debugf("No initializer defined, skipping initialization of %s", this);
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#getInitializer()
*/
public final ServerInitializer<K, T> getServerInitializer()
{
return this.serverInitializer;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#setInitializer(org.jboss.bootstrap.spi.server.ServerInitializer)
*/
public synchronized final void setServerInitializer(final ServerInitializer<K, T> serverInitializer)
throws IllegalStateException
{
// Precondition check
this.checkMutable(this.getState());
this.serverInitializer = serverInitializer;
log.debugf("Set server initializer to %s", serverInitializer);
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#getConfigInitializer()
*/
public final ConfigurationInitializer<T> getConfigInitializer()
{
return this.configInitializer;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#setConfigInitializer(org.jboss.bootstrap.spi.config.ConfigurationInitializer)
*/
public synchronized final void setConfigInitializer(ConfigurationInitializer<T> configInitializer)
{
// Precondition check
this.checkMutable(this.getState());
this.configInitializer = configInitializer;
log.debugf("Set config initializer to %s", configInitializer);
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#setConfigurationValidator(org.jboss.bootstrap.spi.config.ConfigurationValidator)
*/
public synchronized final void setValidator(final ConfigurationValidator<T> validator)
{
// Precondition check
this.checkMutable(this.getState());
log.debugf("Setting validator to: %s", validator);
this.validator = validator;
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#registerEventHandlers(org.jboss.bootstrap.spi.lifecycle.LifecycleState, org.jboss.bootstrap.spi.lifecycle.LifecycleEventHandler)
*/
public void registerEventHandler(final LifecycleState state, final LifecycleEventHandler handler)
throws IllegalArgumentException
{
// Delegate
this.registerEventHandlers(state, handler);
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#registerEventHandler(org.jboss.bootstrap.spi.lifecycle.LifecycleEventHandler, java.util.EnumSet)
*/
public void registerEventHandler(final LifecycleEventHandler handler, final EnumSet<LifecycleState> states)
throws IllegalArgumentException
{
// Precondition checks
if (handler == null)
{
throw new IllegalArgumentException("handler is required");
}
if (states == null)
{
throw new IllegalArgumentException("states is required");
}
// Delegate
for (final LifecycleState state : states)
{
this.registerEventHandler(state, handler);
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#registerEventHandler(org.jboss.bootstrap.spi.lifecycle.LifecycleEventHandler, org.jboss.bootstrap.spi.lifecycle.LifecycleState[])
*/
public void registerEventHandler(final LifecycleEventHandler handler, final LifecycleState... states)
throws IllegalArgumentException
{
// Precondition checks
if (handler == null)
{
throw new IllegalArgumentException("handler is required");
}
if (states == null)
{
throw new IllegalArgumentException("states is required");
}
// Delegate
for (final LifecycleState state : states)
{
this.registerEventHandler(state, handler);
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#registerEventHandler(org.jboss.bootstrap.spi.lifecycle.LifecycleState, org.jboss.bootstrap.spi.lifecycle.LifecycleEventHandler)
*/
public void registerEventHandlers(final LifecycleState state, final LifecycleEventHandler... handlers)
throws IllegalArgumentException
{
// Precondition checks
if (handlers == null)
{
throw new IllegalArgumentException("At least one handler is required");
}
if (state == null)
{
throw new IllegalArgumentException("state is required");
}
// Get existing handlers for this state change
final Set<LifecycleEventHandler> handlersForEvent = this.getHandlersForEvent(state);
// Add the state change
for (final LifecycleEventHandler handler : handlers)
{
handlersForEvent.add(handler);
log.debugf("Added lifecycle handler %s to fire upon state change to %s for %s", handler, state, this);
}
}
/* (non-Javadoc)
* @see org.jboss.bootstrap.spi.server.Server#unregisterEventHandler(org.jboss.bootstrap.spi.lifecycle.LifecycleEventHandler,org.jboss.bootstrap.spi.lifecycle.LifecycleState)
*/
public boolean unregisterEventHandler(final LifecycleEventHandler handler, final LifecycleState state)
throws IllegalArgumentException
{
// Get all handlers for this state change
final Set<LifecycleEventHandler> handlers = this.getHandlersForEvent(state);
// Remove and return
final boolean removed = handlers.remove(handler);
log.debugf("Removed lifecycle handler %s from firing upon state change to %s for %s", handler, state, this);
return removed;
}
//-------------------------------------------------------------------------------------||
// Contracts --------------------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/**
* Implementation-specific start hook
*
* @throws Exception
*/
protected abstract void doStart() throws Exception;
/**
* Implementation-specific shutdown hook
*
* @throws Exception
*/
protected abstract void doShutdown() throws Exception;
/**
* Obtains the Class used in constructing a new default
* Server Configuration if one is not supplied to the constructor
*
* @return
*/
protected abstract Class<? extends T> getDefaultServerConfigClass();
//-------------------------------------------------------------------------------------||
// Functional Methods -----------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/**
* Sends the JMX Notification with type signaling the start of the server
*/
protected void sendStartJmxNotification()
{
this.sendNotificationWithCurrentTimeUserData(START_NOTIFICATION_TYPE, 1);
}
/**
* Sends the JMX Notification with type signaling the stop of the server
*/
protected void sendStopJmxNotification()
{
this.sendNotificationWithCurrentTimeUserData(STOP_NOTIFICATION_TYPE, 2);
}
/**
* Sends a JMX Notification with specified type and sequence number,
* setting user data to the number of milliseconds since the epoch.
*
* @param type
* @param sequenceNumber
*/
protected final void sendNotificationWithCurrentTimeUserData(final String type, final int sequenceNumber)
{
// Send JMX Notification
final Notification startNotification = new Notification(type, this, sequenceNumber);
startNotification.setUserData(System.currentTimeMillis());
this.sendNotification(startNotification);
log.debugf("Sent JMX Notification: %s", type);
}
/**
* Starts the bootstraps
*
* @throws Exception
*/
protected void startBootstraps() throws Exception
{
// Get the proper type for this server
final K typedServer = this.covarientReturn();
// Run the bootstraps
for (Bootstrap<K, T> bootstrap : this.getBootstraps())
{
// Add in reverse order
this.getStartedBootstraps().add(0, bootstrap);
// Remember TCCL
@Deprecated
final ClassLoader oldCl = SecurityActions.getThreadContextClassLoader();
// Get new TCCL
@Deprecated
final ClassLoader bootstrapCl = bootstrap.getClass().getClassLoader();
try
{
// Set the bootstrap CL
// FIXME This is a carryover hack from the legacy implementation,
// still required for proper visibility
SecurityActions.setThreadContextClassLoader(bootstrapCl);
// Start
bootstrap.start(typedServer);
}
finally
{
// Reset the TCCL
SecurityActions.setThreadContextClassLoader(oldCl);
}
}
}
/**
* Shuts down the bootstraps that have been started
*/
protected void shutdownBootstraps()
{
// Initialize
final List<Bootstrap<K, T>> startedBootstraps = this.getStartedBootstraps();
// Get the proper type for this server
final K typedServer = this.covarientReturn();
// Signal
for (final Bootstrap<K, T> bootstrap : startedBootstraps)
{
bootstrap.prepareShutdown(typedServer);
}
// Do the bootstraps in reverse order (they were added in reverse order)
for (final Bootstrap<K, T> bootstrap : startedBootstraps)
{
try
{
bootstrap.shutdown(typedServer);
// Remove bootstrap from started list
if (!startedBootstraps.remove(bootstrap))
{
log.warnf("Failed to remove bootstrap after shutdown: %s", bootstrap);
}
}
catch (final Throwable t)
{
log.warn("Error shutting down bootstrap: " + bootstrap, t);
}
}
}
//-------------------------------------------------------------------------------------||
// Internal Helper Methods ------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/**
* Returns this instance as a typed server (as specified by K),
* throwing a descriptive error message in case the server is not assignable
*/
protected final K covarientReturn()
{
// Get the actual class
final Class<K> actualClass = this.getActualClass();
// Cast
try
{
return actualClass.cast(this);
}
catch (ClassCastException cce)
{
throw new RuntimeException("Actual class is incorrect and " + actualClass
+ " was not assignable to this instance: " + this, cce);
}
}
/**
* Obtains a list of handlers for a lifecycle event change into the specified state.
* No nulls will be returned, but rather an empty List in the case no events are
* registered. All requests to the event handlers should be proxied through here
* to prevent encountering a null backing List.
*
* @param state The target state change for the event
* @return
* @throws IllegalArgumentException If the state was not specified
*/
private Set<LifecycleEventHandler> getHandlersForEvent(final LifecycleState state) throws IllegalArgumentException
{
// Precondition check
if (state == null)
{
throw new IllegalArgumentException("state is required");
}
// Initialize
Set<LifecycleEventHandler> handlers = this.eventHandlers.get(state);
// Adjust to empty List if null
if (handlers == null)
{
handlers = new CopyOnWriteArraySet<LifecycleEventHandler>();
// Put this new list into the backing Map to prevent null access
this.eventHandlers.put(state, handlers);
log.tracef("Placed empty backing map for lifecycle event handers upon state change into %s for: %s", state,
this);
}
// Return
return handlers;
}
/**
* Ensures the actual state matches the required, throwing {@link IllegalStateException}
* if not
*
* @param required
* @param actual
* @throws IllegalStateException If the actual state does not match required
*/
private void checkState(final LifecycleState required, final LifecycleState actual) throws IllegalStateException
{
// Check State
if (required != actual)
{
throw new IllegalStateException("Server must be in " + LifecycleState.class.getSimpleName() + " " + required
+ "; is instead: " + actual);
}
}
/**
* Ensures the actual state matches at least one of the required, throwing {@link IllegalStateException}
* if not
*
* @param required
* @param actual
* @throws IllegalStateException If the actual state does not match one of those required
*/
private void checkState(final LifecycleState[] required, final LifecycleState actual) throws IllegalStateException
{
for (final LifecycleState current : required)
{
if (current.equals(actual))
{
// Match, so return OK
return;
}
}
// No match found
throw new IllegalStateException("Server state must be in one of " + Arrays.asList(required) + "; is instead: "
+ actual);
}
/**
* Ensures the server state is mutable, defined by being either in {@link LifecycleState#INSTANCIATED}
* or {@link LifecycleState#PRE_INIT}
*
* @throws IllegalStateException If the specified state does not allow for mutable operations
*/
private void checkMutable(final LifecycleState state)
{
this.checkState(new LifecycleState[]
{LifecycleState.INSTANCIATED, LifecycleState.PRE_INIT}, state);
}
/**
* If {@link Server#getValidator()} is non-null, will
* assert the configuration is valid using the supplied
* validator
*
* @param configuration
* @throws InvalidConfigurationException If the configuration is invalid
* @throws IllegalArgumentException If the confirguation has not been set
*/
private void validate(final T configuration) throws InvalidConfigurationException, IllegalArgumentException
{
// Precondition check
if (configuration == null)
{
throw new IllegalArgumentException("Configuration was not specified");
}
// Get the validator
final ConfigurationValidator<T> validator = this.getValidator();
// Is specified, validate
if (validator != null)
{
log.debugf("Validating configuration using: %s", validator);
validator.validate(this.getConfiguration());
}
else
{
log.trace("No validator defined, skipping validation upon configuration");
}
}
//-------------------------------------------------------------------------------------||
// Accessors / Mutators ---------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
/**
* @return the bootstraps
*/
private List<Bootstrap<K, T>> getBootstraps()
{
return bootstraps;
}
/**
* @return the startedBootstraps
*/
List<Bootstrap<K, T>> getStartedBootstraps()
{
return startedBootstraps;
}
/**
* @return the actualClass
*/
protected final Class<K> getActualClass()
{
return actualClass;
}
/**
* @param actualClass the actualClass to set
*/
private void setActualClass(final Class<K> actualClass)
{
this.actualClass = actualClass;
}
/**
* Sets the state of the server, firing the appropriate lifecycle
* events to the registered handlers
*
* @param state the state to set
* @throws Exception If some error was encountered while firing the handlers
* @throws IllegalArgumentException If no state was specified
*/
private synchronized final void setState(final LifecycleState state) throws LifecycleEventException,
IllegalArgumentException
{
// Precondition check
if (state == null)
{
throw new IllegalArgumentException("State was not specified");
}
// Log and set
log.tracef("Setting %s to: %s", LifecycleState.class.getSimpleName(), state);
this.state = state;
// Fire handlers registered for event changes to this state
final Set<LifecycleEventHandler> handlers = this.getHandlersForEvent(state);
for (final LifecycleEventHandler handler : handlers)
{
try
{
// Log
log.tracef("Firing event handler for state change to %s: %s", state, handler);
// Fire
handler.handleEvent(state);
}
catch (final LifecycleEventException t)
{
// Log and rethrow
log.error("Encountered problem in firing event handler " + handler + " for state change to " + state
+ " in " + this, t);
throw t;
}
}
}
}