/* * Copyright 2007-2010 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.ServiceNodeTypes; 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.management.KernelMXBean; 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.List; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import java.util.TreeSet; 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 * 60000; // the proxy used by all transactional components private static final TransactionProxy proxy = new TransactionProxyImpl(); // the properties used to start the application private final PropertiesWrapper wrappedProperties; // 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, and an associated handle private final ProfileCollectorImpl profileCollector; private final ProfileCollectorHandleImpl profileCollectorHandle; // 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 { // output the entire set of configuration properties to the logger if (logger.isLoggable(Level.CONFIG)) { StringBuffer propOutput = new StringBuffer( "Booting the Kernel with raw properties:"); for (String key : new TreeSet<String>( appProperties.stringPropertyNames())) { propOutput.append("\n " + key + "=" + appProperties.getProperty(key)); } logger.log(Level.CONFIG, propOutput.toString()); } // filter the properties with appropriate defaults filterProperties(appProperties); // check the standard properties checkProperties(appProperties); this.wrappedProperties = new PropertiesWrapper(appProperties); try { // See if we're doing any profiling. String level = wrappedProperties.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 = new ProfileCollectorHandleImpl(profileCollector); // with profiling setup, register all MXBeans registerMXBeans(appProperties); // create the authenticators and identity coordinator IdentityCoordinator identityCoordinator = createIdentityCoordinator(); // 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 = wrappedProperties.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 that registers MXBeans for management. */ private void registerMXBeans(Properties p) throws Exception { // Create the configuration MBean and register it. This is // used during the construction of later components. ConfigManager config = new ConfigManager(p); 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; } // install the MXBean that exports a shutdown interface KernelManager kernelManager = new KernelManager(); try { profileCollector.registerMBean(kernelManager, KernelManager.MXBEAN_NAME); } catch (JMException e) { logger.logThrow(Level.WARNING, e, "Could not register MBean"); throw e; } } /** * Private helper routine that loads all of the requested listeners * for profiling data. */ private void loadProfileListeners(ProfileCollector profileCollector) { List<String> listenerList = wrappedProperties.getListProperty( PROFILE_LISTENERS, String.class, ""); List<String> extListenerList = wrappedProperties.getListProperty( BootProperties.EXTENSION_PROFILE_LISTENERS_PROPERTY, String.class, ""); List<String> allListenerNames = extListenerList; allListenerNames.addAll(listenerList); for (String listenerClassName : allListenerNames) { 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 = wrappedProperties.getProperty( StandardProperties.APP_NAME); if (logger.isLoggable(Level.CONFIG)) { logger.log(Level.CONFIG, "{0}: starting application", appName); } // start the service creation IdentityImpl owner = new SystemIdentity("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 { // retrieve any specified external services // and fold in any extension library services List<String> externalServices = wrappedProperties.getListProperty( StandardProperties.SERVICES, String.class, ""); List<String> extensionServices = wrappedProperties.getListProperty( BootProperties.EXTENSION_SERVICES_PROPERTY, String.class, ""); List<String> externalManagers = wrappedProperties.getListProperty( StandardProperties.MANAGERS, String.class, ""); List<String> extensionManagers = wrappedProperties.getListProperty( BootProperties.EXTENSION_MANAGERS_PROPERTY, String.class, ""); List<ServiceNodeTypes> externalNodeTypes = wrappedProperties.getEnumListProperty( StandardProperties.SERVICE_NODE_TYPES, ServiceNodeTypes.class, ServiceNodeTypes.ALL); List<ServiceNodeTypes> extensionNodeTypes = wrappedProperties.getEnumListProperty( BootProperties.EXTENSION_SERVICE_NODE_TYPES_PROPERTY, ServiceNodeTypes.class, ServiceNodeTypes.ALL); if (externalServices.size() == 1 && externalManagers.size() == 0) { externalManagers.add(""); } if (extensionServices.size() == 1 && extensionManagers.size() == 0) { extensionManagers.add(""); } if (externalNodeTypes.size() == 0) { for (int i = 0; i < externalServices.size(); i++) { externalNodeTypes.add(ServiceNodeTypes.ALL); } } if (extensionNodeTypes.size() == 0) { for (int i = 0; i < extensionServices.size(); i++) { extensionNodeTypes.add(ServiceNodeTypes.ALL); } } List<String> allServices = extensionServices; allServices.addAll(externalServices); List<String> allManagers = extensionManagers; allManagers.addAll(externalManagers); List<ServiceNodeTypes> allNodeTypes = extensionNodeTypes; allNodeTypes.addAll(externalNodeTypes); NodeType type = NodeType.valueOf( wrappedProperties.getProperty(StandardProperties.NODE_TYPE)); loadCoreServices(type, startupContext); loadExternalServices(allServices, allManagers, allNodeTypes, type, startupContext); } /** Private helper used to load all core services and managers. */ private void loadCoreServices(NodeType type, StartupKernelContext startupContext) throws Exception { StandardService finalStandardService = null; switch (type) { case appNode: finalStandardService = StandardService.LAST_APP_SERVICE; break; case singleNode: finalStandardService = StandardService.LAST_SINGLE_SERVICE; break; case coreServerNode: finalStandardService = StandardService.LAST_CORE_SERVICE; break; default: throw new IllegalArgumentException("Invalid node type : " + type); } final int finalServiceOrdinal = finalStandardService.ordinal(); // load the data service String dataServiceClass = wrappedProperties.getProperty( StandardProperties.DATA_SERVICE, DEFAULT_DATA_SERVICE); String dataManagerClass = wrappedProperties.getProperty( StandardProperties.DATA_MANAGER, DEFAULT_DATA_MANAGER); setupService(dataServiceClass, dataManagerClass, startupContext); // provide the node id to the shutdown controller and profile collector long nodeId = startupContext.getService(DataService.class).getLocalNodeId(); shutdownCtrl.setNodeId(nodeId); profileCollectorHandle.notifyNodeIdAssigned(nodeId); // load the watch-dog service, which has no associated manager if (StandardService.WatchdogService.ordinal() > finalServiceOrdinal) { return; } String watchdogServiceClass = wrappedProperties.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 = wrappedProperties.getProperty( StandardProperties.NODE_MAPPING_SERVICE, DEFAULT_NODE_MAPPING_SERVICE); setupServiceNoManager(nodemapServiceClass, startupContext); // load the task service if (StandardService.TaskService.ordinal() > finalServiceOrdinal) { return; } String taskServiceClass = wrappedProperties.getProperty( StandardProperties.TASK_SERVICE, DEFAULT_TASK_SERVICE); String taskManagerClass = wrappedProperties.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 = wrappedProperties.getProperty( StandardProperties.CLIENT_SESSION_SERVICE, DEFAULT_CLIENT_SESSION_SERVICE); setupServiceNoManager(clientSessionServiceClass, startupContext); // load the channel service if (StandardService.ChannelService.ordinal() > finalServiceOrdinal) { return; } String channelServiceClass = wrappedProperties.getProperty( StandardProperties.CHANNEL_SERVICE, DEFAULT_CHANNEL_SERVICE); String channelManagerClass = wrappedProperties.getProperty( StandardProperties.CHANNEL_MANAGER, DEFAULT_CHANNEL_MANAGER); setupService(channelServiceClass, channelManagerClass, startupContext); } /** Private helper used to load all external services and managers. */ private void loadExternalServices(List<String> externalServices, List<String> externalManagers, List<ServiceNodeTypes> externalNodeTypes, NodeType type, StartupKernelContext startupContext) throws Exception { if (externalServices.size() != externalManagers.size() || externalServices.size() != externalNodeTypes.size()) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "External service count " + "({0}), manager count ({1}), and node type count " + "({2}) do not match.", externalServices.size(), externalManagers.size(), externalNodeTypes.size()); } throw new IllegalArgumentException("Mis-matched service, manager " + "and node type count"); } for (int i = 0; i < externalServices.size(); i++) { // skip this service if it should not be started on this node type if (!externalNodeTypes.get(i).shouldStart(type)) { continue; } if (!externalManagers.get(i).equals("")) { setupService(externalServices.get(i), externalManagers.get(i), startupContext); } else { setupServiceNoManager(externalServices.get(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( wrappedProperties.getProperties(), 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( wrappedProperties.getProperties(), 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( wrappedProperties.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(wrappedProperties.getProperties()), 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 the {@code IdentityCoordinator} system component used to * authenticate identities. This method builds a collection of * {@link IdentityAuthenticator}s specified in the application's properties * with the {@link StandardProperties#AUTHENTICATORS} and * {@link BootProperties#EXTENSION_AUTHENTICATORS_PROPERTY} properties. * The returned {@code IdentityCoordinator} is then generated with this * as a backing collection of authenticators. */ private IdentityCoordinator createIdentityCoordinator() throws Exception { List<String> authClassNameList = wrappedProperties.getListProperty( StandardProperties.AUTHENTICATORS, String.class, ""); List<String> extAuthClassNameList = wrappedProperties.getListProperty( BootProperties.EXTENSION_AUTHENTICATORS_PROPERTY, String.class, ""); List<String> allAuthClassNames = extAuthClassNameList; allAuthClassNames.addAll(authClassNameList); if (allAuthClassNames.isEmpty()) { allAuthClassNames.add(DEFAULT_IDENTITY_AUTHENTICATOR); } ArrayList<IdentityAuthenticator> authenticators = new ArrayList<IdentityAuthenticator>(); for (String authClassName : allAuthClassNames) { authenticators.add(getAuthenticator( authClassName, wrappedProperties.getProperties())); } return new IdentityCoordinatorImpl(authenticators); } /** * Creates a new identity authenticator. */ private IdentityAuthenticator getAuthenticator(String authClassName, Properties properties) throws Exception { Class<?> authenticatorClass = Class.forName(authClassName); 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 volatile long nodeId = -1; private WatchdogService watchdogSvc = null; private boolean shutdownQueued = false; private boolean isReady = false; private final Object shutdownQueueLock = new Object(); /** Provides the shutdown controller with the local node id. */ public void setNodeId(long id) { nodeId = id; } /** * 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 (nodeId != -1 && watchdogSvc != null) { watchdogSvc. reportFailure(nodeId, caller.getClass().toString()); } else { // shutdown directly if data service and watchdog // have 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(); } } /** Basic implementation of the kernel management interface. */ public class KernelManager implements KernelMXBean { /** {@inheritDoc} */ public void requestShutdown() { shutdownCtrl.shutdownNode(this); } } /** * This method is used to automatically determine an application's set * of configuration properties. */ private static Properties findProperties(String propLoc) throws Exception { // load the extension configuration file as the default set of // properties if it exists Properties baseProperties = new Properties(); String extPropFile = System.getProperty(BootProperties.EXTENSION_FILE_PROPERTY); if (extPropFile != null) { File extFile = new File(extPropFile); if (extFile.isFile() && extFile.canRead()) { baseProperties = loadProperties(extFile.toURI().toURL(), null); } else { logger.log(Level.SEVERE, "can't access file: " + extPropFile); throw new IllegalArgumentException("can't access file " + extPropFile); } } // next load the application specific property file, if this exists URL propsIn = ClassLoader.getSystemResource( BootProperties.DEFAULT_APP_PROPERTIES); Properties appProperties = baseProperties; if (propsIn != null) { appProperties = loadProperties(propsIn, baseProperties); } // load the overriding set of configuration properties from the // file indicated by the filename argument Properties fileProperties = appProperties; 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(), appProperties); } // 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> * <li>Properties specified in the file named by the optional * {@link BootProperties#EXTENSION_FILE_PROPERTY} system property.</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. * <p> * 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); } }