/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.impl.kernel;
import com.sun.sgs.kernel.NodeType;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.util.ManagedSerializable;
import com.sun.sgs.internal.InternalContext;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.auth.IdentityAuthenticator;
import com.sun.sgs.auth.IdentityCoordinator;
import com.sun.sgs.impl.auth.IdentityImpl;
import com.sun.sgs.impl.kernel.StandardProperties.StandardService;
import com.sun.sgs.impl.kernel.logging.TransactionAwareLogManager;
import com.sun.sgs.impl.profile.ProfileCollectorHandle;
import com.sun.sgs.impl.profile.ProfileCollectorHandleImpl;
import com.sun.sgs.impl.profile.ProfileCollectorImpl;
import com.sun.sgs.impl.service.transaction.TransactionCoordinator;
import com.sun.sgs.impl.service.transaction.TransactionCoordinatorImpl;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.impl.sharedutil.PropertiesWrapper;
import com.sun.sgs.impl.util.AbstractKernelRunnable;
import com.sun.sgs.impl.util.BindingKeyedCollections;
import com.sun.sgs.impl.util.BindingKeyedCollectionsImpl;
import com.sun.sgs.impl.util.Version;
import com.sun.sgs.kernel.ComponentRegistry;
import com.sun.sgs.profile.ProfileCollector;
import com.sun.sgs.profile.ProfileCollector.ProfileLevel;
import com.sun.sgs.profile.ProfileListener;
import com.sun.sgs.service.DataService;
import com.sun.sgs.service.Service;
import com.sun.sgs.service.TransactionProxy;
import com.sun.sgs.service.WatchdogService;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogManager;
import javax.management.JMException;
/**
* This is the core class for the server. It is the first class that is
* created, and represents the kernel of the runtime. It is responsible
* for creating and initializing all components of the system and the
* applications configured to run in this system.
* <p>
* The kernel must be configured with certain <a
* href="../../impl/kernel/doc-files/config-properties.html#RequiredProperties">
* required properties</a> and supports other <a
* href="../../impl/kernel/doc-files/config-properties.html#System">public
* properties</a>. It can also be configured with any of the properties
* specified in the {@link StandardProperties} class, and supports
* the following additional configuration properties:
*
* <dl style="margin-left: 1em">
*
* <dt> <i>Property:</i> <code><b>{@value #PROFILE_LEVEL_PROPERTY}
* </b></code> <br>
* <i>Default:</i> <code>MIN</code>
*
* <dd style="padding-top: .5em">By default, the minimal amount of profiling
* which is used internally by the system is enabled. To enable more
* profiling, this property must be set to a valid level for {@link
* ProfileCollector#setDefaultProfileLevel(ProfileLevel)}. <p>
*
* <dt> <i>Property:</i> <code><b>{@value #PROFILE_LISTENERS}
* </b></code> <br>
* <i>Default:</i> No Default
*
* <dd style="padding-top: .5em">By default, no profile listeners are enabled.
* To enable a set of listeners, set this property to a colon-separated
* list of fully-qualified class
* names, each of which implements {@link ProfileListener}. A number
* of listeners are provided with the system in the
* {@link com.sun.sgs.impl.profile.listener} package.<p>
*
* <dt> <i>Property:</i> <code><b>{@value #ACCESS_COORDINATOR_PROPERTY}
* </b></code> <br>
* <i>Default:</i> <code>{@link TrackingAccessCoordinator}</code>
*
* <dd style="padding-top: .5em">The implementation class used to track
* access to shared objects. The value of this property should be the
* name of a public, non-abstract class that implements the
* {@link AccessCoordinatorHandle} interface, and that provides a public
* constructor with the three parameters {@link Properties},
* {@link TransactionProxy}, and {@link ProfileCollectorHandle}.<p>
*
*
* </dl>
*/
class Kernel {
// logger for this class
private static final LoggerWrapper logger =
new LoggerWrapper(Logger.getLogger(Kernel.class.getName()));
// the property for setting profiling levels
public static final String PROFILE_LEVEL_PROPERTY =
"com.sun.sgs.impl.kernel.profile.level";
// the property for setting the profile listeners
public static final String PROFILE_LISTENERS =
"com.sun.sgs.impl.kernel.profile.listeners";
// The property for specifying the access coordinator
public static final String ACCESS_COORDINATOR_PROPERTY =
"com.sun.sgs.impl.kernel.access.coordinator";
// the default authenticator
private static final String DEFAULT_IDENTITY_AUTHENTICATOR =
"com.sun.sgs.impl.auth.NullAuthenticator";
// the default services
private static final String DEFAULT_CHANNEL_SERVICE =
"com.sun.sgs.impl.service.channel.ChannelServiceImpl";
private static final String DEFAULT_CLIENT_SESSION_SERVICE =
"com.sun.sgs.impl.service.session.ClientSessionServiceImpl";
private static final String DEFAULT_DATA_SERVICE =
"com.sun.sgs.impl.service.data.DataServiceImpl";
private static final String DEFAULT_TASK_SERVICE =
"com.sun.sgs.impl.service.task.TaskServiceImpl";
private static final String DEFAULT_WATCHDOG_SERVICE =
"com.sun.sgs.impl.service.watchdog.WatchdogServiceImpl";
private static final String DEFAULT_NODE_MAPPING_SERVICE =
"com.sun.sgs.impl.service.nodemap.NodeMappingServiceImpl";
// the default managers
private static final String DEFAULT_CHANNEL_MANAGER =
"com.sun.sgs.impl.app.profile.ProfileChannelManager";
private static final String DEFAULT_DATA_MANAGER =
"com.sun.sgs.impl.app.profile.ProfileDataManager";
private static final String DEFAULT_TASK_MANAGER =
"com.sun.sgs.impl.app.profile.ProfileTaskManager";
// default timeout the kernel's shutdown method (15 minutes)
private static final int DEFAULT_SHUTDOWN_TIMEOUT = 15 * 600000;
// the proxy used by all transactional components
private static final TransactionProxy proxy = new TransactionProxyImpl();
// the properties used to start the application
private final Properties appProperties;
// the schedulers used for transactional and non-transactional tasks
private final TransactionSchedulerImpl transactionScheduler;
private final TaskSchedulerImpl taskScheduler;
// the application that is running in this kernel
private KernelContext application;
// The system registry which contains all shared system components
private final ComponentRegistryImpl systemRegistry;
// collector of profile information
private final ProfileCollectorImpl profileCollector;
// shutdown controller that can be passed to components who need to be able
// to issue a kernel shutdown. the watchdog also constains a reference for
// services to call shutdown.
private final KernelShutdownControllerImpl shutdownCtrl =
new KernelShutdownControllerImpl();
// specifies whether this node has already been shutdown
private boolean isShutdown = false;
/**
* Creates an instance of <code>Kernel</code>. Once this is created
* the code components of the system are running and ready. Creating
* a <code>Kernel</code> will also result in initializing and starting
* the application and its associated services.
*
* @param appProperties application properties
*
* @throws Exception if for any reason the kernel cannot be started
*/
protected Kernel(Properties appProperties)
throws Exception
{
logger.log(Level.CONFIG, "Booting the Kernel");
// filter the properties with appropriate defaults
filterProperties(appProperties);
// check the standard properties
checkProperties(appProperties);
this.appProperties = appProperties;
try {
// See if we're doing any profiling.
String level = appProperties.getProperty(PROFILE_LEVEL_PROPERTY,
ProfileLevel.MIN.name());
ProfileLevel profileLevel;
try {
profileLevel =
ProfileLevel.valueOf(level.toUpperCase());
if (logger.isLoggable(Level.CONFIG)) {
logger.log(Level.CONFIG, "Profiling level is {0}", level);
}
} catch (IllegalArgumentException iae) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Unknown profile level {0}",
level);
}
throw iae;
}
// Create the system registry
systemRegistry = new ComponentRegistryImpl();
profileCollector = new ProfileCollectorImpl(profileLevel,
appProperties, systemRegistry);
ProfileCollectorHandle profileCollectorHandle =
new ProfileCollectorHandleImpl(profileCollector);
// Create the configuration MBean and register it. This is
// used during the construction of later components.
ConfigManager config = new ConfigManager(appProperties);
try {
profileCollector.registerMBean(config,
ConfigManager.MXBEAN_NAME);
} catch (JMException e) {
logger.logThrow(Level.WARNING, e, "Could not register MBean");
// Stop bringing up the kernel - the ConfigManager is used
// by other parts of the system, who rely on it being
// successfully registered.
throw e;
}
// create the authenticators and identity coordinator
ArrayList<IdentityAuthenticator> authenticators =
new ArrayList<IdentityAuthenticator>();
String [] authenticatorClassNames =
appProperties.getProperty(StandardProperties.AUTHENTICATORS,
DEFAULT_IDENTITY_AUTHENTICATOR).split(":");
for (String authenticatorClassName : authenticatorClassNames) {
authenticators.add(getAuthenticator(authenticatorClassName,
appProperties));
}
IdentityCoordinator identityCoordinator =
new IdentityCoordinatorImpl(authenticators);
// initialize the transaction coordinator
TransactionCoordinator transactionCoordinator =
new TransactionCoordinatorImpl(appProperties,
profileCollectorHandle);
// possibly upgrade loggers to be transactional
LogManager logManager = LogManager.getLogManager();
// if the logging system has been configured to be
// transactional, the LogManager installed should be an
// instance of TransactionAwareLogManager
if (logManager instanceof TransactionAwareLogManager) {
TransactionAwareLogManager txnAwareLogManager =
(TransactionAwareLogManager) logManager;
txnAwareLogManager.configure(appProperties, proxy);
}
// create the access coordinator
AccessCoordinatorHandle accessCoordinator =
new PropertiesWrapper(appProperties).getClassInstanceProperty(
ACCESS_COORDINATOR_PROPERTY,
AccessCoordinatorHandle.class,
new Class[] {
Properties.class,
TransactionProxy.class,
ProfileCollectorHandle.class
},
appProperties, proxy, profileCollectorHandle);
if (accessCoordinator == null) {
accessCoordinator = new TrackingAccessCoordinator(
appProperties, proxy, profileCollectorHandle);
}
// create the schedulers, and provide an empty context in case
// any profiling components try to do transactional work
transactionScheduler =
new TransactionSchedulerImpl(appProperties,
transactionCoordinator,
profileCollectorHandle,
accessCoordinator);
taskScheduler =
new TaskSchedulerImpl(appProperties, profileCollectorHandle);
BindingKeyedCollections collectionsFactory =
new BindingKeyedCollectionsImpl(proxy);
KernelContext ctx = new StartupKernelContext("Kernel");
transactionScheduler.setContext(ctx);
taskScheduler.setContext(ctx);
// collect the shared system components into a registry
systemRegistry.addComponent(accessCoordinator);
systemRegistry.addComponent(transactionScheduler);
systemRegistry.addComponent(taskScheduler);
systemRegistry.addComponent(identityCoordinator);
systemRegistry.addComponent(profileCollector);
systemRegistry.addComponent(collectionsFactory);
// create the profiling listeners. It is important to not
// do this until we've finished adding components to the
// system registry, as some listeners use those components.
loadProfileListeners(profileCollector);
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO, "The Kernel is ready, version: {0}",
Version.getVersion());
}
// the core system is ready, so start up the application
createAndStartApplication();
} catch (Exception e) {
if (logger.isLoggable(Level.SEVERE)) {
logger.logThrow(Level.SEVERE, e, "Failed on Kernel boot");
}
// shut down whatever we've started
shutdown();
throw e;
}
}
/**
* Private helper routine that loads all of the requested listeners
* for profiling data.
*/
private void loadProfileListeners(ProfileCollector profileCollector) {
String listenerList = appProperties.getProperty(PROFILE_LISTENERS);
if (listenerList != null) {
for (String listenerClassName : listenerList.split(":")) {
try {
profileCollector.addListener(listenerClassName);
} catch (InvocationTargetException e) {
// Strip off exceptions found via reflection
if (logger.isLoggable(Level.WARNING)) {
logger.logThrow(Level.WARNING, e.getCause(),
"Failed to load ProfileListener {0} ... " +
"it will not be available for profiling",
listenerClassName);
}
} catch (Exception e) {
if (logger.isLoggable(Level.WARNING)) {
logger.logThrow(Level.WARNING, e,
"Failed to load ProfileListener {0} ... " +
"it will not be available for profiling",
listenerClassName);
}
}
}
}
// finally, register the scheduler as a listener too
// NOTE: if we make the schedulers pluggable, or add other components
// that are listeners, then we should scan through all of the system
// components and check if they are listeners
profileCollector.addListener(transactionScheduler, false);
}
/**
* Helper that starts an application. This method
* configures the <code>Service</code>s associated with the
* application and then starts the application.
*
* @throws Exception if there is any error in startup
*/
private void createAndStartApplication() throws Exception {
String appName = appProperties.getProperty(StandardProperties.APP_NAME);
if (logger.isLoggable(Level.CONFIG)) {
logger.log(Level.CONFIG, "{0}: starting application", appName);
}
// start the service creation
IdentityImpl owner = new IdentityImpl("app:" + appName);
createServices(appName, owner);
startApplication(appName, owner);
}
/**
* Creates each of the <code>Service</code>s and their corresponding
* <code>Manager</code>s (if any) in order, in preparation for starting
* up an application.
*/
private void createServices(String appName, Identity owner)
throws Exception
{
if (logger.isLoggable(Level.CONFIG)) {
logger.log(Level.CONFIG, "{0}: starting services", appName);
}
// create and install a temporary context to use during startup
application = new StartupKernelContext(appName);
transactionScheduler.setContext(application);
taskScheduler.setContext(application);
ContextResolver.setTaskState(application, owner);
// tell the AppContext how to find the managers
InternalContext.setManagerLocator(new ManagerLocatorImpl());
try {
fetchServices((StartupKernelContext) application);
} catch (Exception e) {
if (logger.isLoggable(Level.SEVERE)) {
logger.logThrow(Level.SEVERE, e, "{0}: failed to create " +
"services", appName);
}
throw e;
}
// with the managers fully created, swap in a permanent context
application = new KernelContext(application);
transactionScheduler.setContext(application);
taskScheduler.setContext(application);
ContextResolver.setTaskState(application, owner);
// notify all of the services that the application state is ready
try {
application.notifyReady();
} catch (Exception e) {
if (logger.isLoggable(Level.SEVERE)) {
logger.logThrow(Level.SEVERE, e, "{0}: failed when notifying " +
"services that application is ready", appName);
}
throw e;
}
// enable the shutdown controller once the components and services
// are setup to allow a node shutdown call from either of them.
shutdownCtrl.setReady();
}
/**
* Private helper that creates the services and their associated managers,
* taking care to call out the standard services first, because we need
* to get the ordering constant and make sure that they're all present.
*/
private void fetchServices(StartupKernelContext startupContext)
throws Exception
{
// before we start, figure out if we're running with only a sub-set
// of services, in which case there should be no external services
NodeType type =
NodeType.valueOf(
appProperties.getProperty(StandardProperties.NODE_TYPE));
StandardService finalStandardService = null;
String externalServices =
appProperties.getProperty(StandardProperties.SERVICES);
String externalManagers =
appProperties.getProperty(StandardProperties.MANAGERS);
switch (type) {
case appNode:
case singleNode:
default:
finalStandardService = StandardService.LAST_SERVICE;
break;
case coreServerNode:
if ((externalServices != null) || (externalManagers != null)) {
throw new IllegalArgumentException(
"Cannot specify external services for the core server");
}
finalStandardService = StandardService.TaskService;
break;
}
final int finalServiceOrdinal = finalStandardService.ordinal();
// load the data service
String dataServiceClass =
appProperties.getProperty(StandardProperties.DATA_SERVICE,
DEFAULT_DATA_SERVICE);
String dataManagerClass =
appProperties.getProperty(StandardProperties.DATA_MANAGER,
DEFAULT_DATA_MANAGER);
setupService(dataServiceClass, dataManagerClass, startupContext);
// load the watch-dog service, which has no associated manager
if (StandardService.WatchdogService.ordinal() > finalServiceOrdinal) {
return;
}
String watchdogServiceClass =
appProperties.getProperty(StandardProperties.WATCHDOG_SERVICE,
DEFAULT_WATCHDOG_SERVICE);
setupServiceNoManager(watchdogServiceClass, startupContext);
// provide a handle to the watchdog service for the shutdown controller
shutdownCtrl.setWatchdogHandle(
startupContext.getService(WatchdogService.class));
// load the node mapping service, which has no associated manager
if (StandardService.NodeMappingService.ordinal() > finalServiceOrdinal)
{
return;
}
String nodemapServiceClass =
appProperties.getProperty(StandardProperties.NODE_MAPPING_SERVICE,
DEFAULT_NODE_MAPPING_SERVICE);
setupServiceNoManager(nodemapServiceClass, startupContext);
// load the task service
if (StandardService.TaskService.ordinal() > finalServiceOrdinal) {
return;
}
String taskServiceClass =
appProperties.getProperty(StandardProperties.TASK_SERVICE,
DEFAULT_TASK_SERVICE);
String taskManagerClass =
appProperties.getProperty(StandardProperties.TASK_MANAGER,
DEFAULT_TASK_MANAGER);
setupService(taskServiceClass, taskManagerClass, startupContext);
// load the client session service, which has no associated manager
if (StandardService.ClientSessionService.ordinal() >
finalServiceOrdinal)
{
return;
}
String clientSessionServiceClass =
appProperties.getProperty(StandardProperties.
CLIENT_SESSION_SERVICE,
DEFAULT_CLIENT_SESSION_SERVICE);
setupServiceNoManager(clientSessionServiceClass, startupContext);
// load the channel service
if (StandardService.ChannelService.ordinal() > finalServiceOrdinal) {
return;
}
String channelServiceClass =
appProperties.getProperty(StandardProperties.CHANNEL_SERVICE,
DEFAULT_CHANNEL_SERVICE);
String channelManagerClass =
appProperties.getProperty(StandardProperties.CHANNEL_MANAGER,
DEFAULT_CHANNEL_MANAGER);
setupService(channelServiceClass, channelManagerClass, startupContext);
// finally, load any external services and their associated managers
if ((externalServices != null) && (externalManagers != null)) {
String [] serviceClassNames = externalServices.split(":", -1);
String [] managerClassNames = externalManagers.split(":", -1);
if (serviceClassNames.length != managerClassNames.length) {
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, "External service count " +
"({0}) does not match manager count ({1}).",
serviceClassNames.length,
managerClassNames.length);
}
throw new IllegalArgumentException("Mis-matched service " +
"and manager count");
}
for (int i = 0; i < serviceClassNames.length; i++) {
if (!managerClassNames[i].equals("")) {
setupService(serviceClassNames[i], managerClassNames[i],
startupContext);
} else {
setupServiceNoManager(serviceClassNames[i], startupContext);
}
}
}
}
/**
* Sets up a service with no manager based on fully qualified class name.
*/
private void setupServiceNoManager(String className,
StartupKernelContext startupContext)
throws Exception
{
Class<?> serviceClass = Class.forName(className);
Service service = createService(serviceClass);
startupContext.addService(service);
}
/**
* Creates a service and its associated manager based on fully qualified
* class names.
*/
private void setupService(String serviceName, String managerName,
StartupKernelContext startupContext)
throws Exception
{
// get the service class and instance
Class<?> serviceClass = Class.forName(serviceName);
Service service = createService(serviceClass);
// resolve the class and the constructor, checking for constructors
// by type since they likely take a super-type of Service
Class<?> managerClass = Class.forName(managerName);
Constructor<?> [] constructors = managerClass.getConstructors();
Constructor<?> managerConstructor = null;
for (int i = 0; i < constructors.length; i++) {
Class<?> [] types = constructors[i].getParameterTypes();
if (types.length == 1) {
if (types[0].isAssignableFrom(serviceClass)) {
managerConstructor = constructors[i];
break;
}
}
}
// if we didn't find a matching manager constructor, it's an error
if (managerConstructor == null) {
throw new NoSuchMethodException("Could not find a constructor " +
"that accepted the Service");
}
// create the manager and put it and the service in the collections
// and the temporary startup context
Object manager = managerConstructor.newInstance(service);
startupContext.addService(service);
startupContext.addManager(manager);
}
/**
* Private helper that creates an instance of a <code>service</code> with
* no manager, based on fully qualified class names.
*/
private Service createService(Class<?> serviceClass) throws Exception {
Constructor<?> serviceConstructor;
try {
// find the appropriate constructor
serviceConstructor =
serviceClass.getConstructor(Properties.class,
ComponentRegistry.class, TransactionProxy.class);
// return a new instance
return (Service) (serviceConstructor.newInstance(appProperties,
systemRegistry, proxy));
} catch (NoSuchMethodException e) {
// instead, look for a constructor with 4 parameters which is for
// services with shutdown privileges.
serviceConstructor =
serviceClass.getConstructor(Properties.class,
ComponentRegistry.class, TransactionProxy.class,
KernelShutdownController.class);
// return a new instance
return (Service) (serviceConstructor.newInstance(appProperties,
systemRegistry, proxy, shutdownCtrl));
}
}
/** Start the application, throwing an exception if there is a problem. */
private void startApplication(String appName, Identity owner)
throws Exception
{
// at this point the services are ready, so the final step
// is to initialize the application by running a special
// KernelRunnable in an unbounded transaction, unless we're
// running without an application
NodeType type =
NodeType.valueOf(
appProperties.getProperty(StandardProperties.NODE_TYPE));
if (!type.equals(NodeType.coreServerNode)) {
try {
if (logger.isLoggable(Level.CONFIG)) {
logger.log(Level.CONFIG, "{0}: starting application",
appName);
}
transactionScheduler.
runUnboundedTask(new AppStartupRunner(appProperties),
owner);
logger.log(Level.INFO,
"{0}: application is ready", application);
} catch (Exception e) {
if (logger.isLoggable(Level.CONFIG)) {
logger.logThrow(Level.CONFIG, e, "{0}: failed to " +
"start application", appName);
}
throw e;
}
} else {
// we're running without an application, so we're finished
logger.log(Level.INFO, "{0}: non-application context is ready",
application);
}
}
/**
* Timer that will call {@link System#exit System.exit} after a timeout
* period to force the process to quit if the node shutdown process takes
* too long. The timer is started as a daemon so the task won't be run if
* a shutdown completes successfully.
*/
private void startShutdownTimeout(final int timeout) {
new Timer(true).schedule(new TimerTask() {
public void run() {
System.exit(1);
}
}, timeout);
}
/**
* Shut down all services (in reverse order) and the schedulers.
*/
synchronized void shutdown() {
if (isShutdown) {
return;
}
startShutdownTimeout(DEFAULT_SHUTDOWN_TIMEOUT);
logger.log(Level.FINE, "Kernel.shutdown() called.");
if (application != null) {
application.shutdownServices();
}
if (profileCollector != null) {
profileCollector.shutdown();
}
// The schedulers must be shut down last.
if (transactionScheduler != null) {
transactionScheduler.shutdown();
}
if (taskScheduler != null) {
taskScheduler.shutdown();
}
logger.log(Level.FINE, "Node is shut down.");
isShutdown = true;
}
/**
* Creates a new identity authenticator.
*/
private IdentityAuthenticator getAuthenticator(String className,
Properties properties)
throws Exception
{
Class<?> authenticatorClass = Class.forName(className);
Constructor<?> authenticatorConstructor =
authenticatorClass.getConstructor(Properties.class);
return (IdentityAuthenticator) (authenticatorConstructor.
newInstance(properties));
}
/**
* Helper method for loading properties files with backing properties.
*/
private static Properties loadProperties(URL resource,
Properties backingProperties)
throws Exception
{
InputStream in = null;
try {
Properties properties;
if (backingProperties == null) {
properties = new Properties();
} else {
properties = new Properties(backingProperties);
}
in = resource.openStream();
properties.load(in);
return properties;
} catch (IOException ioe) {
if (logger.isLoggable(Level.SEVERE)) {
logger.logThrow(Level.SEVERE, ioe, "Unable to load " +
"from resource {0}: ", resource);
}
throw ioe;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
if (logger.isLoggable(Level.CONFIG)) {
logger.logThrow(Level.CONFIG, e, "failed to close " +
"resource {0}", resource);
}
}
}
}
}
/**
* Helper method that filters properties, loading appropriate defaults
* if necessary.
*/
private static Properties filterProperties(Properties properties)
throws Exception
{
try {
// Expand properties as needed.
String value = properties.getProperty(StandardProperties.NODE_TYPE);
if (value == null) {
// Default is single node
value = NodeType.singleNode.name();
properties.setProperty(StandardProperties.NODE_TYPE, value);
}
// Throws IllegalArgumentException if not one of the enum types
// but let's improve the error message
try {
NodeType.valueOf(value);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Illegal value for " +
StandardProperties.NODE_TYPE);
}
return properties;
} catch (IllegalArgumentException iae) {
if (logger.isLoggable(Level.SEVERE)) {
logger.logThrow(Level.SEVERE, iae, "Illegal data in " +
"properties");
}
throw iae;
}
}
/**
* Check for obvious errors in the properties file, logging and
* throwing an {@code IllegalArgumentException} if there is a problem.
*/
private static void checkProperties(Properties appProperties)
{
String appName =
appProperties.getProperty(StandardProperties.APP_NAME);
// make sure that at least the required keys are present, and if
// they are then start the application
if (appName == null) {
logger.log(Level.SEVERE, "Missing required property " +
StandardProperties.APP_NAME);
throw new IllegalArgumentException("Missing required property " +
StandardProperties.APP_NAME);
}
if (appProperties.getProperty(StandardProperties.APP_ROOT) == null) {
logger.log(Level.SEVERE, "Missing required property " +
StandardProperties.APP_ROOT + " for application: " +
appName);
throw new IllegalArgumentException("Missing required property " +
StandardProperties.APP_ROOT + " for application: " +
appName);
}
NodeType type =
NodeType.valueOf(
appProperties.getProperty(StandardProperties.NODE_TYPE));
if (!type.equals(NodeType.coreServerNode)) {
if (appProperties.getProperty(StandardProperties.APP_LISTENER) ==
null)
{
logger.log(Level.SEVERE, "Missing required property " +
StandardProperties.APP_LISTENER +
" for application: " + appName);
throw new IllegalArgumentException(
"Missing required property " +
StandardProperties.APP_LISTENER +
" for application: " + appName);
}
}
}
/**
* This runnable calls the application's <code>initialize</code> method,
* if it hasn't been called before, to start the application for the
* first time. This runnable must be called in the context of an
* unbounded transaction.
*/
private static final class AppStartupRunner extends AbstractKernelRunnable {
// the properties for the application
private final Properties properties;
/** Creates an instance of <code>AppStartupRunner</code>. */
AppStartupRunner(Properties properties) {
super(null);
this.properties = properties;
}
/** Starts the application, throwing an exception on failure. */
public void run() throws Exception {
DataService dataService =
Kernel.proxy.getService(DataService.class);
try {
// test to see if this name if the listener is already bound...
dataService.getServiceBinding(StandardProperties.APP_LISTENER);
} catch (NameNotBoundException nnbe) {
// ...if it's not, create and then bind the listener
AppListener listener =
(new PropertiesWrapper(properties)).
getClassInstanceProperty(StandardProperties.APP_LISTENER,
AppListener.class, new Class[] {});
if (listener instanceof ManagedObject) {
dataService.setServiceBinding(
StandardProperties.APP_LISTENER, listener);
} else {
dataService.setServiceBinding(
StandardProperties.APP_LISTENER,
new ManagedSerializable<AppListener>(listener));
}
// since we created the listener, we're the first one to
// start the app, so we also need to start it up
listener.initialize(properties);
}
}
}
/**
* This is an object created by the {@code Kernel} and passed to the
* services and components which are given shutdown privileges. This object
* allows the {@code Kernel} to be referenced when a shutdown of the node is
* necessary, such as when a service on the node has failed or has become
* inconsistent. This class can only be instantiated by the {@code Kernel}.
*/
private final class KernelShutdownControllerImpl implements
KernelShutdownController
{
private WatchdogService watchdogSvc = null;
private boolean shutdownQueued = false;
private boolean isReady = false;
private final Object shutdownQueueLock = new Object();
/**
* This method gives the shutdown controller a handle to the
* {@code WatchdogService}. Components will use this handle to report a
* failure to the watchdog service instead of shutting down directly.
* This which ensures that the server is properly notified when a node
* needs to be shut down. This handle can only be set once, any call
* after that will be ignored.
*/
public void setWatchdogHandle(WatchdogService watchdogSvc) {
if (this.watchdogSvc != null) {
return; // do not allow overwriting the watchdog once it's set
}
this.watchdogSvc = watchdogSvc;
}
/**
* This method flags the controller as being ready to issue shutdowns.
* If a shutdown was previously queued, then shutdown the node now.
*/
public void setReady() {
synchronized (shutdownQueueLock) {
isReady = true;
if (shutdownQueued) {
shutdownNode(this);
}
}
}
/**
* {@inheritDoc}
*/
public void shutdownNode(Object caller) {
synchronized (shutdownQueueLock) {
if (isReady) {
// service shutdown; we have already gone through notifying
// the server, so shutdown the node right now
if (caller instanceof WatchdogService) {
runShutdown();
} else {
// component shutdown; we go through the watchdog to
// cleanup and notify the server first
if (watchdogSvc != null) {
watchdogSvc.reportFailure(watchdogSvc.
getLocalNodeId(),
caller.getClass().toString());
} else {
// shutdown directly if watchdog has not been setup
runShutdown();
}
}
} else {
// queue the request if the Kernel is not ready
shutdownQueued = true;
}
}
}
/**
* Shutdown the node. This is run in a different thread to prevent a
* possible deadlock due to a service or component's doShutdown()
* method waiting for the thread it was issued from to shutdown.
* For example, the watchdog service's shutdown method would block if
* a Kernel shutdown was called from RenewThread.
*/
private void runShutdown() {
logger.log(Level.WARNING, "Controller issued node shutdown.");
new Thread(new Runnable() {
public void run() {
shutdown();
}
}).start();
}
}
/**
* This method is used to automatically determine an application's set
* of configuration properties.
*/
private static Properties findProperties(String propLoc) throws Exception {
// load the application specific configuration file
// as the default set of options if it exists
Properties baseProperties = new Properties();
URL propsIn = ClassLoader.getSystemResource(
BootProperties.DEFAULT_APP_PROPERTIES);
if (propsIn != null) {
baseProperties = loadProperties(propsIn, null);
}
// load the overriding set of configuration properties from the
// file indicated by the filename argument
Properties fileProperties = baseProperties;
if (propLoc != null && !propLoc.equals("")) {
File propFile = new File(propLoc);
if (!propFile.isFile() || !propFile.canRead()) {
logger.log(Level.SEVERE, "can't access file : " + propFile);
throw new IllegalArgumentException("can't access file " +
propFile);
}
fileProperties = loadProperties(propFile.toURI().toURL(),
baseProperties);
}
// if a properties file exists in the user's home directory, use
// it to override any properties
Properties homeProperties = fileProperties;
File homeConfig = new File(System.getProperty("user.home") +
File.separator +
BootProperties.DEFAULT_HOME_CONFIG_FILE);
if (homeConfig.isFile() && homeConfig.canRead()) {
homeProperties = loadProperties(homeConfig.toURI().toURL(),
fileProperties);
} else if (homeConfig.isFile() && !homeConfig.canRead()) {
logger.log(Level.WARNING, "can't access file : " + homeConfig);
}
// override any properties with the values from the System properties
Properties finalProperties = new Properties(homeProperties);
finalProperties.putAll(System.getProperties());
return finalProperties;
}
/**
* Main-line method that starts the {@code Kernel}. Each kernel
* instance runs a single application.
* <p>
* If a single argument is given, the value of the argument is assumed
* to be a filename. This file
* is used in combination with additional configuration settings to
* determine an application's configuration properties.
* <p>
* The order of precedence for properties is as follows (from highest
* to lowest):
* <ol>
* <li>System properties specified on the command line using a
* "-D" flag</li>
* <li>Properties specified in the file from the user's home directory
* with the name specified by
* {@link BootProperties#DEFAULT_HOME_CONFIG_FILE}.</li>
* <li>Properties specified in the file given as a command line
* argument.</li>
* <li>Properties specified in the resource with the name
* {@link BootProperties#DEFAULT_APP_PROPERTIES}
* This file is typically included as part of the application jar file.</li>
* </ol>
*
* If no value is specified for a given
* property in any of these places, then a default is used
* or an <code>Exception</code> is thrown (depending on whether a default
* value is available). Certain properties are required to be
* specified somewhere in the application's configuration.
* See {@link StandardProperties} for the required and optional properties.
*
* @param args optional filename for <code>Properties</code> file
* associated with the application to run
*
* @throws Exception if there is any problem starting the system
*/
public static void main(String [] args) throws Exception {
// ensure we don't have too many arguments
if (args.length > 1) {
logger.log(Level.SEVERE, "Invalid number of arguments: halting");
System.exit(1);
}
// if an argument is specified on the command line, use it
// for the value of the filename
Properties appProperties;
if (args.length == 1) {
appProperties = findProperties(args[0]);
} else {
appProperties = findProperties(null);
}
// boot the kernel
new Kernel(appProperties);
}
}