/* * Copyright to the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.rioproject.impl.servicebean; import com.sun.jini.admin.DestroyAdmin; import com.sun.jini.start.LifeCycle; import net.jini.admin.Administrable; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.ConfigurationProvider; import net.jini.core.discovery.LookupLocator; import net.jini.id.Uuid; import net.jini.id.UuidFactory; import net.jini.security.BasicProxyPreparer; import net.jini.security.ProxyPreparer; import org.rioproject.admin.ServiceBeanControlException; import org.rioproject.util.MulticastStatus; import org.rioproject.config.Constants; import org.rioproject.impl.container.DiscardManager; import org.rioproject.servicebean.ServiceBeanContext; import org.rioproject.impl.container.ServiceAdvertiser; import org.rioproject.deploy.SystemRequirements; import org.rioproject.loader.CommonClassLoader; import org.rioproject.loader.ServiceClassLoader; import org.rioproject.log.LoggerConfig; import org.rioproject.opstring.ClassBundle; import org.rioproject.opstring.ServiceBeanConfig; import org.rioproject.opstring.ServiceElement; import org.rioproject.impl.client.DiscoveryManagementPool; import org.rioproject.impl.client.LookupCachePool; import org.rioproject.impl.service.Destroyer; import org.rioproject.rmi.RegistryUtil; import org.rioproject.sla.ServiceLevelAgreements; import org.rioproject.impl.system.ComputeResource; import org.rioproject.watch.ThresholdValues; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * The ServiceBeanActivation class is a utility that provides bootstrap support * for a ServiceBean * * @author Dennis Reedy */ @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") public class ServiceBeanActivation { static final String COMPONENT = ServiceBeanActivation.class.getPackage().getName(); public static final String QOS_COMPONENT = Constants.BASE_COMPONENT+".system"; public static final String BOOT_COMPONENT = Constants.BASE_COMPONENT+".boot"; public static final String BOOT_CONFIG_COMPONENT = BOOT_COMPONENT+".configComponent"; static LifeCycleManager sbLifeCycleManager; static final Logger logger = LoggerFactory.getLogger(COMPONENT); static final String BOOT_COOKIE = "boot-cookie"; /** * Create a ServiceBeanContext from the Configuration element. * * @param configComponent The configuration component name of the * ServiceBean to use when obtaining configuration attributes. Must not be null. * @param defaultServiceName The default name of the service. Must not be null. * @param configArgs Configuration for the ServiceBean. Must not be null. * @param loader The ClassLoader which loaded the ServiceBean. Must not be null. * * @return A ServiceBeanContext. A new ServiceBeanContext is created each time * * @throws ConfigurationException If the Configuration cannot be created * @throws IOException If the hostname cannot be accessed * @throws IllegalArgumentException If any of the arguments are null. */ public static ServiceBeanContext getServiceBeanContext(final String configComponent, final String defaultServiceName, final String[] configArgs, final ClassLoader loader) throws ConfigurationException, IOException { if(configArgs == null) throw new IllegalArgumentException("configArgs is null"); logger.debug("Creating configuration for {}", configComponent); Configuration config = ConfigurationProvider.getInstance(configArgs, loader); logger.debug("Created configuration for {}", configComponent); return getServiceBeanContext(configComponent, defaultServiceName, configArgs, config, loader); } /** * Create a ServiceBeanContext from the Configuration element. This method is used if the * {@code Configuration} object has already been created. * * @param configComponent The configuration component name of the * ServiceBean to use when obtaining configuration attributes. Must not be null. * @param defaultServiceName The default name of the service. Must not be null. * @param configArgs Configuration arguments for the ServiceBean. This is used to create a * {@code ServiceBeanConfig} object, and the {@code configArgs} are those used to create the * {@code Configuration} argument following. Must not be null. * @param config Configuration for the ServiceBean. Must not be null. * @param loader The ClassLoader which loaded the ServiceBean. Must not be null. * * @return A ServiceBeanContext. A new ServiceBeanContext is created each time * * @throws ConfigurationException If the Configuration cannot be created * @throws IOException If the hostname cannot be accessed * @throws IllegalArgumentException If any of the arguments are null. */ public static ServiceBeanContext getServiceBeanContext(final String configComponent, final String defaultServiceName, final String[] configArgs, final Configuration config, final ClassLoader loader) throws ConfigurationException, IOException { logger.debug("Entering getServiceBeanContext"); if(configComponent == null) throw new IllegalArgumentException("configComponent is null"); if(defaultServiceName == null) throw new IllegalArgumentException("defaultServiceName is null"); if(configArgs == null) throw new IllegalArgumentException("configArgs is null"); if(config == null) throw new IllegalArgumentException("config is null"); if(loader == null) throw new IllegalArgumentException("loader is null"); Map<String, Object> configParms = new HashMap<String, Object>(); RegistryUtil.checkRegistry(); configParms.putAll(readServiceBeanConfig(configComponent, defaultServiceName, config)); String jmxName = (String)config.getEntry(configComponent, "jmxName", String.class, null); if(jmxName!=null) configParms.put(ServiceBeanConfig.JMX_NAME, jmxName); ServiceBeanConfig sbConfig = new ServiceBeanConfig(configParms, configArgs); sbConfig.addInitParameter(BOOT_CONFIG_COMPONENT, configComponent); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if(cl instanceof ServiceClassLoader) { Properties p = new Properties(); p.setProperty("opStringName", sbConfig.getOperationalStringName()); p.setProperty("serviceName", sbConfig.getName()); ((ServiceClassLoader)cl).addMetaData(p); } /* Add a boot-cookie, indicating that the JSB was booted by this utility */ sbConfig.addInitParameter("org.rioproject.boot", BOOT_COOKIE); ServiceLevelAgreements sla = new ServiceLevelAgreements(); /* Get the export codebase from the ClassLoader which loaded us. The * export codebase will be returned by the getURLs() method since * getURLs() is overridden to return configured export codebase */ URL[] urls = ((URLClassLoader)loader).getURLs(); if(urls.length==0) throw new RuntimeException("Unknown Export Codebase"); ClassBundle exportBundle; String exportCodebase = urls[0].toExternalForm(); if(!exportCodebase.startsWith("artifact:")) { if(exportCodebase.contains(".jar")) { int index = exportCodebase.lastIndexOf('/'); if(index != -1) exportCodebase = exportCodebase.substring(0, index+1); } else { throw new RuntimeException("Cannot determine export codebase from "+exportCodebase); } exportBundle = new ClassBundle(""); exportBundle.setCodebase(exportCodebase); for (URL url : urls) { String jar = url.getFile(); int index = jar.lastIndexOf('/'); if (index != -1) jar = jar.substring(1); exportBundle.addJAR(jar); } } else { exportBundle = new ClassBundle(""); exportBundle.setArtifact(exportCodebase); } /* Default system threshold is the number of available processors. * Since the system threshold is the summation of all depletion oriented * resources, total utilization is = * (num_available_processors * cpu_utilization * mem_utilization * diskspace_utilization) */ double defaultSystemThreshold = Runtime.getRuntime().availableProcessors(); double systemThreshold = (Double)config.getEntry(QOS_COMPONENT, "systemThreshold", double.class, defaultSystemThreshold); sla.getSystemRequirements().addSystemThreshold(SystemRequirements.SYSTEM, new ThresholdValues(0.0, systemThreshold)); ServiceElement sElem = new ServiceElement(ServiceElement.ProvisionType.EXTERNAL, sbConfig, sla, new ClassBundle[]{exportBundle}); if(sbLifeCycleManager == null) sbLifeCycleManager = new LifeCycleManager(); checkUtilityConfiguration(config); ComputeResource computeResource = new ComputeResource(config); Uuid serviceID = UuidFactory.generate(); DefaultServiceBeanManager serviceBeanManager = new DefaultServiceBeanManager(sElem, computeResource.getHostName(), computeResource.getAddress().getHostAddress(), serviceID); serviceBeanManager.setDiscardManager(sbLifeCycleManager); serviceBeanManager.setServiceID(serviceID); DefaultServiceBeanContext serviceBeanContext = new DefaultServiceBeanContext(sElem, serviceBeanManager, computeResource, null); /* Shared Configuration */ serviceBeanContext.setConfiguration(config); serviceBeanContext.setConfigurationFiles(configArgs); logger.debug("Leaving getServiceBeanContext"); return (serviceBeanContext); } /** * Get ServiceBean config params, using the package name of the ServiceBean * class as the component * * @param configComponent The component name used to access entries in * the configuration * @param defaultServiceName The service's default name * @param config The Configuration to use * * @return A Map of name,value pairs specifying configuration * attributes * * @throws ConfigurationException if there are errors reading the configuration 1 * @throws IllegalArgumentException If any of the arguments are null. */ static Map<String, Object> readServiceBeanConfig(final String configComponent, final String defaultServiceName, final Configuration config) throws ConfigurationException { if(configComponent == null) throw new IllegalArgumentException("configComponent is null"); if(defaultServiceName == null) throw new IllegalArgumentException("defaultServiceName is null"); if(config == null) throw new IllegalArgumentException("config is null"); Map<String, Object> configParms = new HashMap<String, Object>(); /* Set the ClassLoader for the DiscoveryManagementPool to be the * CommonCLassLoader, not the current context ClassLoader */ ClassLoader cCL = Thread.currentThread().getContextClassLoader(); CommonClassLoader common = CommonClassLoader.getInstance(); Thread.currentThread().setContextClassLoader(common); try { /* Set the Configuration for the DiscoveryManagementPool */ DiscoveryManagementPool.getInstance().setConfiguration(config); LookupCachePool.getInstance().setConfiguration(config); } finally { Thread.currentThread().setContextClassLoader(cCL); } /* Set the config component */ configParms.put(ServiceBeanConfig.COMPONENT, configComponent); /* Get the ServiceBean name */ String serviceName = (String)config.getEntry(configComponent, "serviceName", String.class, defaultServiceName); configParms.put(ServiceBeanConfig.NAME, serviceName); /* Get the comment for the ServiceBean */ String serviceComment = (String)config.getEntry(configComponent, "serviceComment", String.class, null); if(serviceComment != null) configParms.put(ServiceBeanConfig.COMMENT, serviceComment); /* Get the log OperationalString name */ String opStringName = (String)config.getEntry(configComponent, "opStringName", String.class, Constants.CORE_OPSTRING); configParms.put(ServiceBeanConfig.OPSTRING, opStringName); /* Get group values, default to all groups */ String[] groups = (String[])config.getEntry(configComponent, "initialLookupGroups", String[].class, new String[]{"all"}); configParms.put(ServiceBeanConfig.GROUPS, groups); /* Get LookupLocator values */ LookupLocator[] lookupLocators = (LookupLocator[])config.getEntry(configComponent, "initialLookupLocators", LookupLocator[].class, null); /* Get LoggerConfig values */ LoggerConfig[] logConfigs = (LoggerConfig[])config.getEntry(configComponent, "loggerConfigs", LoggerConfig[].class, null); if(logConfigs != null) { for (LoggerConfig logConfig : logConfigs) { logConfig.getLogger(); } } if(lookupLocators != null) configParms.put(ServiceBeanConfig.LOCATORS, lookupLocators); return (configParms); } /** * Check for multicast verification and setting up a shutdown hook * * @param config Configuration to use * * @throws IOException If the multicast check is performed and it fails * @throws ConfigurationException if there are problems reading the configuration * @throws IllegalArgumentException If the config argument is null. */ static void checkUtilityConfiguration(final Configuration config) throws IOException, ConfigurationException { if(config == null) throw new IllegalArgumentException("configArgs are null"); Boolean multiCheck = (Boolean)config.getEntry(BOOT_COMPONENT, "verifyMulticast", Boolean.class, false); if(logger.isDebugEnabled()) logger.debug("Verify multicast [{}]", multiCheck); if(multiCheck) { if(logger.isInfoEnabled()) logger.info("Verifying multicast, wait up to 10 seconds ... "); MulticastStatus.checkMulticast(1000 * 10); if(logger.isInfoEnabled()) logger.info("Multicast responds okay"); } /* Check for the creation of a ShutdownHook */ Boolean createShutdownHook = (Boolean)config.getEntry(BOOT_COMPONENT, "createShutdownHook", Boolean.class, true); if(logger.isDebugEnabled()) logger.debug("Create ShutdownHook [{}]", createShutdownHook); if(createShutdownHook) { boolean okay = false; if(sbLifeCycleManager != null) { if(!sbLifeCycleManager.isShutDownHookRegistered()) { okay = true; } } else { okay = true; } if(okay) { ShutdownHook shutdownHook = new ShutdownHook(); shutdownHook.setDaemon(true); Runtime.getRuntime().addShutdownHook(shutdownHook); if(sbLifeCycleManager != null) { sbLifeCycleManager.setShutDownHookRegistered(); } } } } /** * Class that provides basic functionality to enable bootstrapping */ public static class LifeCycleManager implements DiscardManager, LifeCycle { boolean terminated = false; boolean shutdownHookRegistered = false; final Map<DestroyAdmin, Subject>registrationMap = new HashMap<DestroyAdmin, Subject>(); /* * Register the ServiceBean to the LifeCycleManager and advertise it */ public void register(final Object sbProxy, final ServiceBeanContext context) throws ServiceBeanControlException { try { if(sbProxy == null) throw new IllegalArgumentException("sbProxy is null"); if(context == null) throw new IllegalArgumentException("context is null"); ServiceAdvertiser.advertise(sbProxy, context, false); try { ProxyPreparer prep = (ProxyPreparer)context.getConfiguration().getEntry(BOOT_COMPONENT, "adminProxyPreparer", BasicProxyPreparer.class, new BasicProxyPreparer()); Object preparedPProxy = prep.prepareProxy(sbProxy); if(preparedPProxy instanceof Administrable) { Administrable admin = (Administrable)preparedPProxy; Object adminObject = admin.getAdmin(); if(adminObject instanceof DestroyAdmin) { Subject subject = null; if(context instanceof DefaultServiceBeanContext) subject = ((DefaultServiceBeanContext)context).getSubject(); registrationMap.put((DestroyAdmin)adminObject, subject); } } } catch (Exception e) { e.printStackTrace(); } } catch(Throwable t) { String name = (context == null ? "unknown name" : context.getServiceElement().getName()); throw new ServiceBeanControlException("ServiceBean ["+ name+"] advertise failed", t); } } /** * A flag to indicate that a shutdown hook has already been registered * by this utility */ void setShutDownHookRegistered() { shutdownHookRegistered = true; } /* * Return whether a shutdown hook has already been registered * by this utility */ boolean isShutDownHookRegistered() { return(shutdownHookRegistered); } /** * Shutdown the utility and terminate the ServiceBean if needed */ public void terminate() { if(!terminated) { for (Map.Entry<DestroyAdmin, Subject> mapEntry : registrationMap.entrySet()) { final DestroyAdmin dAdmin = mapEntry.getKey(); final Subject subject = mapEntry.getValue(); if (subject != null) { try { Subject.doAsPrivileged(subject, new PrivilegedExceptionAction<Void>() { public Void run() throws Exception { try { dAdmin.destroy(); } catch (Exception e) { //e.printStackTrace(); } return (null); } }, null); } catch (PrivilegedActionException e) { e.printStackTrace(); } } else { try { dAdmin.destroy(); } catch (Exception e) { //e.printStackTrace(); } } } } } /** * @see org.rioproject.impl.container.DiscardManager#discard */ public void discard() { if(terminated) return; terminated = true; new Destroyer(null, null); } /** * @see com.sun.jini.start.LifeCycle#unregister */ public boolean unregister(final Object impl) { if(terminated) return (true); terminate(); new Destroyer(null, null); return (true); } } /** * ShutdownHook for the ServiceBean */ static class ShutdownHook extends Thread { ShutdownHook() { super("ShutdownHook"); } public void run() { try { if(sbLifeCycleManager != null) { sbLifeCycleManager.terminate(); } } catch(Throwable t) { logger.error("Terminating ServiceBean", t); } } } }