/* * 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.start; import com.sun.jini.start.NonActivatableServiceDescriptor; import com.sun.jini.start.ServiceDescriptor; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.ConfigurationProvider; import net.jini.config.EmptyConfiguration; import org.rioproject.config.RioProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.rmi.RMISecurityManager; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * This class provides the main routine for starting shared groups, * non-activatable services, and activatable services. * <p/> * The following implementation-specific items are discussed below: <ul> <li><a * href="#configEntries">Configuring ServiceStarter</a> </ul> * <p/> * <a name="configEntries"> <h3>Configuring ServiceStarter</h3> </a> * <p/> * This implementation of <code>ServiceStarter</code> supports the following * configuration entries, with component <code>org.rioproject.start</code>: * <p/> * <table summary="Describes the loginContext configuration entry" border="0" * cellpadding="2"> <tr valign="top"> <th scope="col" summary="layout"> <font * size="+1">•</font> <th scope="col" align="left" colspan="2"> <font * size="+1"><code> loginContext</code></font> <tr valign="top"> <td>   <th * scope="row" align="right"> Type: <td> {@link javax.security.auth.login.LoginContext} * <tr valign="top"> <td>   <th scope="row" align="right"> Default: <td> * <code>null</code> <tr valign="top"> <td>   <th scope="row" align="right"> * Description: <td> If not <code>null</code>, specifies the JAAS login context * to use for performing a JAAS login and supplying the {@link * javax.security.auth.Subject} to use when running the service starter. If * <code>null</code>, no JAAS login is performed. </table> * <p/> * <table summary="Describes the serviceDescriptors configuration entry" * border="0" cellpadding="2"> <tr valign="top"> <th scope="col" * summary="layout"> <font size="+1">•</font> <th scope="col" * align="left" colspan="2"> <font size="+1"><code> serviceDescriptors</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> Type: <td> {@link * ServiceDescriptor}[] <tr valign="top"> <td>   <th scope="row" * align="right"> Default: no default <tr valign="top"> <td>   <th * scope="row" align="right"> Description: <td> Array of service descriptors to * start. </table> * <p/> * <p/> * * @author Sun Microsystems, Inc. * @author Dennis Reedy */ public class ServiceStarter { static { RioProperties.load(); LogManagementHelper.checkConfigurationReset(); } /** * Component name for service starter configuration entries */ static final String COMPONENT = ServiceStarter.class.getPackage().getName(); /** * Configure logger */ static final Logger logger = LoggerFactory.getLogger(ServiceStarter.class.getPackage().getName()); /** * Array of strong references to transient services */ private static final List<Object> transientServiceRefs = new ArrayList<Object>(); /** * Object returned by {@link ServiceStarter#start} */ public static class ServiceReference { /** The reference to the proxy of the created service */ public final Object proxy; /** The reference to the implementation of the created service */ public final Object impl; /** Constructs an instance of this class. * @param impl reference to the implementation of the created service * @param proxy reference to the proxy of the created service */ public ServiceReference(Object impl, Object proxy) { this.proxy = proxy; this.impl = impl; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("ServiceReference"); sb.append("{proxy=").append(proxy); sb.append(", impl=").append(impl); sb.append('}'); return sb.toString(); } } /** * Prevent instantiation */ private ServiceStarter() { } /** * Trivial class used as the return value by the <code>create</code> * methods. This class aggregates the results of a service creation attempt: * proxy (if any), exception (if any), associated descriptor object. */ private static class Result { public final Object result; public final Exception exception; public final ServiceDescriptor descriptor; /** * Trivial constructor. Simply assigns each argument to the appropriate * field. * @param d The Associated <code>ServiceDescriptor</code> object used * to create the service instance * @param o The service proxy object, if any. * @param e The service creation exception, if any. */ Result(final ServiceDescriptor d, final Object o, final Exception e) { descriptor = d; result = o; exception = e; } public String toString() { StringBuilder builder = new StringBuilder(); builder.append(this.getClass()).append(":[descriptor=").append(descriptor).append(", "); builder.append("result=").append(result).append(", exception=").append(exception).append("]"); return builder.toString(); } } /** * Generic service creation method that attempts to login via the provided * <code>LoginContext</code> and then call the <code>create</code> overload * without a login context argument. * * @param descs The <code>ServiceDescriptor[]</code> that contains the * descriptors for the services to start. * @param config The associated <code>Configuration</code> object used to * customize the service creation process. * @param loginContext The associated <code>LoginContext</code> object used * to login/logout. * @return Returns a <code>Result[]</code> that is the same length as * <code>descs</code>, which contains the details for each service * creation attempt. * * @throws Exception If there was a problem logging in/out or a problem * creating the service. * @see Result * @see ServiceDescriptor * @see net.jini.config.Configuration * @see javax.security.auth.login.LoginContext */ private static Result[] createWithLogin(final ServiceDescriptor[] descs, final Configuration config, final LoginContext loginContext) throws Exception { loginContext.login(); Result[] results = null; try { results = Subject.doAsPrivileged(loginContext.getSubject(), new PrivilegedExceptionAction<Result[]>() { public Result[] run() throws Exception { return create(descs, config); } }, null); } catch (PrivilegedActionException pae) { throw pae.getException(); } finally { try { loginContext.logout(); } catch (LoginException le) { logger.warn("service.logout.exception", le); } } return results; } /** * Generic service creation method that attempts to start the services * defined by the provided <code>ServiceDescriptor[]</code> argument. * * @param descs The <code>ServiceDescriptor[]</code> that contains the * descriptors for the services to start. * @param config The associated <code>Configuration</code> object used to * customize the service creation process. * @return Returns a <code>Result[]</code> that is the same length as * <code>descs</code>, which contains the details for each service * creation attempt. * * @throws Exception If there was a problem creating the service. * @see Result * @see ServiceDescriptor * @see net.jini.config.Configuration */ private static Result[] create(final ServiceDescriptor[] descs, final Configuration config) throws Exception { List<Result> proxies = new ArrayList<Result>(); logger.debug("Starting {} service(s)", descs.length); for (ServiceDescriptor desc : descs) { Object result = null; Exception problem = null; try { if (desc != null) { result = desc.create(config); } } catch (Exception e) { problem = e; } finally { proxies.add(new Result(desc, result, problem)); } } return proxies.toArray(new Result[proxies.size()]); } /** * Utility routine that sets a security manager if one isn't already * present. */ synchronized static void ensureSecurityManager() { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } /** * Start services declared in a configuration. The <tt>args</t> argument is * passed directly to <tt>ConfigurationProvider.getInstance()</t> in order * to obtain a <tt>Configuration</tt> object. * * <p>This configuration object is then queried for the * <tt>org.rioproject.start.serviceDescriptors</tt> entry, which * is assumed to be a <tt>ServiceDescriptor[]</tt>. * * <p>The <tt>create()</tt> method is then called on each of the array * elements. * * @param descriptors {@code ServiceDescriptor}s to start. * * @return An immutable list of <tt>ServiceReference</tt> objects, one for * each transient service started. If there are no transient services * started, a zero-length list is returned. A new list is allocated each * time. * * @throws Exception If there are errors starting the services */ public static List<ServiceReference> start(ServiceDescriptor... descriptors) throws Exception { Result[] results = create(descriptors, EmptyConfiguration.INSTANCE); checkResultFailures(results); List<ServiceReference> serviceRefs = getServiceReferences(results); return Collections.unmodifiableList(serviceRefs); } /** * Start services declared in a configuration. The <tt>args</t> argument is * passed directly to <tt>ConfigurationProvider.getInstance()</t> in order * to obtain a <tt>Configuration</tt> object. * * <p>This configuration object is then queried for the * <tt>org.rioproject.start.serviceDescriptors</tt> entry, which * is assumed to be a <tt>ServiceDescriptor[]</tt>. * * <p>The <tt>create()</tt> method is then called on each of the array * elements. * * @param args <tt>String[]</tt> passed to * <tt>ConfigurationProvider.getInstance()</tt> in order to obtain a * <tt>Configuration</tt> object. * * @return An immutable list of <tt>ServiceReference</tt> objects, one for * each transient service started. If there are no transient services * started, a zero-length list is returned. A new list is allocated each * time. * * @throws Exception If there are errors starting the services */ public static List<ServiceReference> start(String... args) throws Exception { Result[] results = doStart(args); checkResultFailures(results); List<ServiceReference> serviceRefs = getServiceReferences(results); return Collections.unmodifiableList(serviceRefs); } /** * Start services declared in a configuration. The <tt>args</t> argument is * passed directly to <tt>ConfigurationProvider.getInstance()</t> in order * to obtain a <tt>Configuration</tt> object. * * <p>This configuration object is then queried for the * <tt>org.rioproject.start.serviceDescriptors</tt> entry, which * is assumed to be a <tt>ServiceDescriptor[]</tt>. * * <p>The <tt>create()</tt> method is then called on each of the array * elements. * * @param args <tt>String[]</tt> passed to * <tt>ConfigurationProvider.getInstance()</tt> in order to obtain a * <tt>Configuration</tt> object. * * @return An immutable list of <tt>ServiceReference</tt> objects, one for * each transient service started. If there are no transient services * started, a zero-length list is returned. A new list is allocated each * time. * * @throws Exception If there are errors starting the services */ private static Result[] doStart(String... args) throws Exception { Configuration config = ConfigurationProvider.getInstance(args); logger.debug("Getting service descriptors to start"); ServiceDescriptor[] descs = (ServiceDescriptor[])config.getEntry(COMPONENT, "serviceDescriptors", ServiceDescriptor[].class, null); if (descs == null || descs.length == 0) { logger.warn("service.config.empty"); return new Result[0]; } logger.debug("Obtained {} service descriptors to start", descs.length); LoginContext loginContext = (LoginContext)config.getEntry(COMPONENT, "loginContext", LoginContext.class, null); Result[] results; if (loginContext != null) results = createWithLogin(descs, config, loginContext); else results = create(descs, config); return results; } /* * Utility routine that maintains strong references to any transient * services in the provided <code>Result[]</code>. This prevents the * transient services from getting garbage collected. */ private static List<ServiceReference> getServiceReferences(Result[] results) { List<ServiceReference> refs = new ArrayList<ServiceReference>(); if (results.length == 0) return refs; for (Result result : results) { Class rDescClass = result.descriptor.getClass(); if (result.result != null) { if(NonActivatableServiceDescriptor.class.equals(rDescClass)) { NonActivatableServiceDescriptor.Created created = ((NonActivatableServiceDescriptor.Created)result.result); refs.add(new ServiceReference(created.impl, created.proxy)); } else if(RioServiceDescriptor.class.equals(rDescClass)) { RioServiceDescriptor.Created created = ((RioServiceDescriptor.Created)result.result); refs.add(new ServiceReference(created.impl, created.proxy)); } else { refs.add(new ServiceReference(result.result, null)); } } } return refs; } /* * Utility routine that maintains strong references to any * transient services in the provided <code>Result[]</code>. * This prevents the transient services from getting garbage * collected. */ private static void maintainNonActivatableReferences(Result[] results) { if (results.length == 0) return; for (Result result : results) { if (result != null && result.result != null && (NonActivatableServiceDescriptor.class.equals(result.descriptor.getClass()) || RioServiceDescriptor.class.equals(result.descriptor.getClass()))) { logger.trace("Storing ref to: {}", result.result); transientServiceRefs.add(result.result); } } } /* * Utility routine that prints out warning messages for each service * descriptor that produced an exception or that was null. */ private static boolean checkResultFailures(Result[] results) { if (results.length == 0) return false; boolean failures = false; for (int i = 0; i < results.length; i++) { if (results[i].exception != null) { failures = true; logger.warn("service.creation.unknown", results[i].exception); logger.warn("service.creation.unknown.detail {} {}", i, results[i].descriptor); } else if (results[i].descriptor == null) { failures = true; logger.warn("service.creation.null {}", i); } } return failures; } /** * The main method for the <code>ServiceStarter</code> application. The * <code>args</code> argument is passed directly to * <code>ConfigurationProvider.getInstance()</code> in order to obtain a * <code>Configuration</code> object. * * <p>This configuration object is then queried for the * <code>org.rioproject.start.serviceDescriptors</code> entry, which * is assumed to be a <code>ServiceDescriptor[]</code>. * * <p>The <code>create()</code> method is then called on each of the array * elements. * * @param args <code>String[]</code> passed to * <code>ConfigurationProvider.getInstance()</code> in order to obtain a * <code>Configuration</code> object. * * @see RioServiceDescriptor * @see net.jini.config.Configuration * @see net.jini.config.ConfigurationProvider */ public static void main(String[] args) { LogManagementHelper.setup(); logger.debug("Entering {}", ServiceStarter.class.getName()); ensureSecurityManager(); try { Result[] results = doStart(args); checkResultFailures(results); //TODO - kick off daemon thread to maintain refs via LifeCycle object maintainNonActivatableReferences(results); } catch (ConfigurationException cex) { logger.error("service.config.exception", cex); } catch (Exception e) { logger.error("service.creation.exception", e); } } }