/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.sun.jini.start;
import com.sun.jini.config.Config;
import net.jini.config.Configuration;
import net.jini.export.ProxyAccessor;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.policy.DynamicPolicy;
import net.jini.security.policy.DynamicPolicyProvider;
import net.jini.security.policy.PolicyFileProvider;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.net.URLClassLoader;
import java.rmi.MarshalledObject;
import java.security.Permission;
import java.security.Policy;
import java.security.AllPermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class used to launch shared, non-activatable, in-process
* services. Clients construct this object with the details
* of the service to be launched, then call
* {@link #create(net.jini.config.Configuration) create(Configuration config) }
* to launch the service in the invoking object's VM.
* <P>
* This class provides separation of the import codebase
* (where the service implementation classes are loaded from)
* from the export codebase
* (where service clients should load classes from, for example stubs).
* as well as providing an independent security policy file for each
* service object. This functionality allows multiple service objects
* to be placed in the same VM, with each object maintaining distinct
* export codebase and security policy settings.
* <P>
* <a name="serviceConstructor">
* Services need to implement the following "non-activatable
* constructor":
* <blockquote><pre><impl>(String[] args, {@link LifeCycle LifeCycle} lc)</blockquote></pre>
* where,
* <UL>
* <LI>args - are the service configuration arguments
* <LI>lc - is the hosting environment's
* {@link LifeCycle} reference.
* </UL>
*
* <a name="serviceProxy">
* A service implementation can return its service proxy
* (via the <code>proxy</code> field of the
* {@link Created Created} object returned by
* {@link #create(net.jini.config.Configuration) create})
* in the following order of precedence:
* <OL>
* <LI> if the service class implements
* {@link ServiceProxyAccessor ServiceProxyAccessor} then return value of
* {@link ServiceProxyAccessor#getServiceProxy() getServiceProxy}
* will be used as the service proxy.
* <LI> if the service class implements
* {@link net.jini.export.ProxyAccessor ProxyAccessor}
* then the return value of
* {@link net.jini.export.ProxyAccessor#getProxy() getProxy} will be used
* as the service proxy.
* <LI> Otherwise <code>null</code> will be returned as the service proxy.
* </OL>
*
* The following items are discussed below:
* <ul>
* <li><a href="#configEntries">Configuring NonActivatableServiceDescriptor</a>
* <li><a href="#logging">Logging</a>
* </ul>
*
* <a name="configEntries">
* <h3>Configuring NonActivatableServiceDescriptor</h3>
* </a>
*
* <code>NonActivatableServiceDescriptor</code>
* depends on {@link ActivateWrapper}, which can itself be configured. See
* {@linkplain ActivateWrapper}'s
* <A HREF="ActivateWrapper.html#configEntries">configuration</A>
* information for details.
*<p>
* This implementation obtains its configuration entries from the
* {@link net.jini.config.Configuration Configuration} object passed into
* the {@link #create(net.jini.config.Configuration) create} method.
* The following configuration entries use the
* component prefix "<code>com.sun.jini.start</code>": <p>
*
* <a name="servicePreparer">
* <table summary="Describes the default service preparer 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>
* servicePreparer</code></font>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.security.ProxyPreparer}
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>
* new {@link net.jini.security.BasicProxyPreparer}()
* </code>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description: <td> The default proxy preparer used to prepare
* the <a href="#serviceProxy">service proxy</a>.
* This value should not be <code>null</code>. This entry is
* obtained during the invocation of
* {@link #create(net.jini.config.Configuration) create} and is used
* to prepare the service proxy returned by
* the service implementation
* (see <a href="#serviceProxy">service proxy</a> section for details).
* This entry is superseded by explicitly passing a
* {@link ProxyPreparer ProxyPreparer} to one of the constructors that
* accept a {@linkplain ProxyPreparer proxy preparer} argument.
* </table>
*
* <a name="logging">
* <h3>Loggers and Logging Levels</h3>
* </a>
*
* This implementation uses the {@link
* java.util.logging.Logger}, named
* <code>com.sun.jini.start.service.starter</code>.
* The following table describes the
* type of information logged as well as the levels of information logged.
* <p>
*
* <table border="1" cellpadding="5"
* summary="Describes logging performed by the non-activatable,
* service descriptor at different logging levels">
*
* <caption halign="center" valign="top"><b><code>
* com.sun.jini.start.service.starter</code></b></caption>
*
* <tr> <th scope="col"> Level <th scope="col"> Description
*
* <tr> <td> {@link java.util.logging.Level#SEVERE SEVERE} <td>
* for significant service creation problems
* <tr> <td> {@link java.util.logging.Level#FINER FINER} <td>
* for high level
* service operation tracing
* <tr> <td> {@link java.util.logging.Level#FINEST FINEST} <td>
* for low level
* service operation tracing
*
* </table> <p>
*
* @author Sun Microsystems, Inc.
*
* @since 2.0
*/
// TODO - add discussion about how to provide a service's proxy
public class NonActivatableServiceDescriptor
implements ServiceDescriptor, Serializable
{
private static final long serialVersionUID = 1L;
/**
* The parameter types for the "activation constructor".
*/
private static final Class[] actTypes = {
String[].class, LifeCycle.class
};
/**
* @serial <code>String</code> containing export codebase.
* That is, location where clients can download required
* service-related classes (for example, stubs, proxies, etc.).
*/
private final String codebase;
/**
* @serial <code>String</code> containing server policy filename or URL
*/
private final String policy;
/**
* @serial <code>String</code> containing import codebase location.
* That is, location where server implementation
* classes can be found.
*/
private final String classpath;
/**
* @serial <code>String</code> containing the
* name of server implementation class
*/
private final String implClassName;
/**
* @serial <code>String[]</code> containing server
* configuration arguments
*/
private final String[] serverConfigArgs;
/**
* serverConfigArgs override.
*/
private final Configuration configuration ;
/*
* <code>LifeCycle</code> reference for hosting environment
*/
private /*final*/ transient LifeCycle lifeCycle;
/*
* <code>ProxyPreparer</code> reference for preparing the service's proxy
*/
private /*final*/ transient ProxyPreparer servicePreparer;
/** Flag indicating when create() has been called */
protected transient boolean descCreated = false;
/** Lock object for <code>descCreated</code> flag */
protected transient Object descCreatedLock = new Object();
private static LifeCycle NoOpLifeCycle =
new LifeCycle() { // default, no-op object
public boolean unregister(Object impl) { return false; }
};
private static AggregatePolicyProvider globalPolicy = null;
private static Policy initialGlobalPolicy = null;
private static final Logger logger = ServiceStarter.logger;
/**
* Object returned by
* {@link NonActivatableServiceDescriptor#create(net.jini.config.Configuration)
* NonActivatableServiceDescriptor.create()}
* method that returns the proxy and implementation references
* for the created service.
*/
public static class Created {
/** 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 Created(Object impl, Object proxy) {
this.proxy = proxy;
this.impl = impl;
}//end constructor
}//end class Created
/**
* Main constructor. Simply assigns given parameters to
* their associated, internal fields unless otherwise noted.
*
* @param exportCodebase location where clients can download required
* service-related classes (for example, stubs, proxies, etc.).
* Codebase components must be separated by spaces in which
* each component is in <code>URL</code> format.
* @param policy server policy filename or URL
* @param importCodebase location where server implementation
* classes can be found.
* This <code>String</code> assumed (in order) to be either
* 1) a space delimited set of <code>URL</code>(s)
* representing a codebase or
* 2) a <code>File.pathSeparator</code> delimited set
* of class paths.
* @param implClassName name of server implementation class
* @param serverConfigArgs service configuration arguments. This parameter
* is passed as the <code>String[]</code> argument to the
* implementation's
* <a href="#serviceConstructor">constructor</a>.
* @param lifeCycle <code>LifeCycle</code> reference
* for hosting environment. This parameter
* is passed as the <code>LifeCycle</code> argument to the
* implementation's
* <a href="#serviceConstructor">constructor</a>. If this argument
* is null, then a default, no-op <code>LifeCycle</code> object will
* be assigned.
* @param preparer <code>ProxyPreparer</code> reference. This object
* will be used to prepare the service's proxy object, if any
* (see <a href="#serviceProxy">service proxy</a> section for details).
* If this argument is null, then the default
* <a href="#servicePreparer">service preparer</a> will be used.
*/
public NonActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
// Optional Args
String[] serverConfigArgs,
LifeCycle lifeCycle,
ProxyPreparer preparer)
{
if (exportCodebase == null || policy == null ||
importCodebase == null || implClassName == null)
throw new NullPointerException(
"export codebase, policy, import codebase, and"
+ " implementation cannot be null");
this.codebase = exportCodebase;
this.policy = policy;
this.classpath = importCodebase;
this.implClassName = implClassName;
this.serverConfigArgs = serverConfigArgs;
this.configuration = null ;
this.lifeCycle =
(lifeCycle == null)?NoOpLifeCycle:lifeCycle;
this.servicePreparer = preparer;
}
public NonActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
// Optional Args
Configuration config,
LifeCycle lifeCycle,
ProxyPreparer preparer)
{
if (exportCodebase == null || policy == null ||
importCodebase == null || implClassName == null)
throw new NullPointerException(
"export codebase, policy, import codebase, and"
+ " implementation cannot be null");
this.codebase = exportCodebase;
this.policy = policy;
this.classpath = importCodebase;
this.implClassName = implClassName;
this.serverConfigArgs = null;
this.configuration = config ;
this.lifeCycle =
(lifeCycle == null)?NoOpLifeCycle:lifeCycle;
this.servicePreparer = preparer;
}
/**
* Convenience constructor. Equivalent to calling this
* {@link #NonActivatableServiceDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[], com.sun.jini.start.LifeCycle, net.jini.security.ProxyPreparer) contructor}
* with <code>null</code> for the <code>preparer</code>
* reference.
*/
public NonActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
// Optional Args
String[] serverConfigArgs,
LifeCycle lifeCycle)
{
this(exportCodebase, policy, importCodebase, implClassName,
serverConfigArgs, lifeCycle, null);
}
/**
* Convenience constructor. Equivalent to calling this
* {@link #NonActivatableServiceDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[], com.sun.jini.start.LifeCycle, net.jini.security.ProxyPreparer) contructor}
* with <code>null</code> for the <code>lifeCycle</code> and <code>preparer</code>
* references.
*/
public NonActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
// Optional Args
String[] serverConfigArgs)
{
this(exportCodebase, policy, importCodebase, implClassName,
serverConfigArgs, null, null);
}
/**
* Convenience constructor. Equivalent to calling this
* {@link #NonActivatableServiceDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[], com.sun.jini.start.LifeCycle, net.jini.security.ProxyPreparer) contructor}
* with <code>null</code> for the <code>lifeCycle</code>
* reference.
*/
public NonActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
// Optional Args
String[] serverConfigArgs,
ProxyPreparer preparer)
{
this(exportCodebase, policy, importCodebase, implClassName,
serverConfigArgs, null, preparer);
}
/**
* Codebase accessor method.
*
* @return the export codebase string associated with this service descriptor.
*/
final public String getExportCodebase() { return codebase; }
/**
* Policy accessor method.
*
* @return the policy string associated with this service descriptor.
*/
final public String getPolicy() { return policy; }
/**
* Classpath accessor method.
*
* @return the import codebase string associated with this service descriptor.
*/
final public String getImportCodebase() { return classpath; }
/**
* Implementation class accessor method.
*
* @return the implementation class string associated with
* this service descriptor.
*/
final public String getImplClassName() { return implClassName; }
/**
* Service configuration arguments accessor method.
*
* @return the service configuration arguments associated with
* this service descriptor.
*/
final public String[] getServerConfigArgs() {
return (serverConfigArgs != null)
? (String[])serverConfigArgs.clone()
: null;
}
/**
* <code>LifeCycle</code> accessor method.
*
* @return the <code>LifeCycle</code> object associated with
* this service descriptor.
*/
final public LifeCycle getLifeCycle() { return lifeCycle; }
/**
* Sets the <code>LifeCycle</code> object for this
* descriptor. This needs to
* be called on the service descriptor prior to calling
* <code>create()</code>. Useful for (re-)setting the
* the associated <code>LifeCycle</code> upon deserialization
* of this descriptor.
*
* @param lc The <code>LifeCycle</code> object to be
* associated with this service descriptor.
*
* @throws IllegalStateException if called after <code>create()</code> is invoked
*/
final public void setLifeCycle(LifeCycle lc) {
synchronized (descCreatedLock) {
if (descCreated) {
throw new IllegalStateException("Can't set LifeCycle after descriptor creation");
} else {
lifeCycle = lc;
}
}
}
/**
* <code>ProxyPreparer</code> accessor method.
*
* @return the <code>ProxyPreparer</code> object associated with
* this service descriptor.
*/
final public ProxyPreparer getServicePreparer() { return servicePreparer; }
/**
* Sets the <code>ProxyPreparer</code> for this descriptor.
* This needs to
* be called on the service descriptor prior to calling
* <code>create()</code>. Useful for (re-)setting the
* the associated <code>ProxyPreparer</code> upon deserialization
* of this descriptor.
*
* @param serviceProxyPreparer
* The <code>ProxyPreparer</code> object to be
* associated with this service descriptor.
*
* @throws IllegalStateException if called after <code>create()</code> is invoked
*/
final public void setServicePreparer(ProxyPreparer serviceProxyPreparer) {
synchronized (descCreatedLock) {
if (descCreated) {
throw new IllegalStateException("Can't set ProxyPreparer after descriptor creation");
} else {
servicePreparer = serviceProxyPreparer;
}
}
}
/**
* Attempts to create a service instance based on the service
* description information provided via constructor parameters.
* <P>
* This method:
* <UL>
* <LI> installs an {@link java.rmi.RMISecurityManager RMISecurityManager}
* if no security manager is already in place
* <LI> installs an {@link AggregatePolicyProvider AggregatePolicyProvider}
* as the VM-global policy object
* (upon the first invocation of this method)
* <LI> creates an
* <code>ActivateWrapper.ExportClassLoader</code> with
* the associated service's import codebase, export codebase and
* the current thread's context class loader as its arguments
* <LI> associates the newly created
* <code>ExportClassLoader</code> and the associated service's
* policy file with the
* {@link AggregatePolicyProvider AggregatePolicyProvider}
* <LI> sets the newly created <code>ExportClassLoader</code> as
* the current thread's context class loader
* <LI> loads the service object's class and calls a constructor
* with the following signature:
* <blockquote><pre><impl>(String[], LifeCycle)</blockquote></pre>
* <LI> obtains the service proxy by calling
* {@link ServiceProxyAccessor#getServiceProxy() ServiceProxyAccessor.getServiceProxy()}
* or
* {@link net.jini.export.ProxyAccessor#getProxy() ProxyAccessor.getProxy()},
* respectively, on the implementation instance.
* If neither interface is supported, the
* proxy reference is set to <code>null</code>
* <LI> resets the context class loader to the
* context classloader in effect when the method was called
* </UL>
*
* @return a
* {@link com.sun.jini.start.NonActivatableServiceDescriptor.Created
* Created} instance with the service's proxy and implementation
* references.
* @throws java.lang.Exception Thrown if there was any problem
* creating the object.
*/
public Object create(Configuration config) throws Exception {
ServiceStarter.ensureSecurityManager();
logger.entering(NonActivatableServiceDescriptor.class.getName(),
"create", new Object[] {config});
if (config == null) {
throw new NullPointerException(
"Configuration argument cannot be null");
}
synchronized (descCreatedLock) {
descCreated = true;
}
if (servicePreparer == null) {
servicePreparer =
(ProxyPreparer) Config.getNonNullEntry(config,
ServiceStarter.START_PACKAGE,
"servicePreparer", ProxyPreparer.class,
new BasicProxyPreparer());
}
/* Warn user of inaccessible codebase(s) */
HTTPDStatus.httpdWarning(getExportCodebase());
Object proxy = null;
Object impl = null;
//Create custom class loader that preserves codebase annotations
Thread curThread = Thread.currentThread();
ClassLoader oldClassLoader = curThread.getContextClassLoader();
logger.log(Level.FINEST, "Saved current context class loader: {0}",
oldClassLoader);
URLClassLoader newClassLoader = null;
try {
newClassLoader =
new ActivateWrapper.ExportClassLoader(
ClassLoaderUtil.getImportCodebaseURLs(getImportCodebase()),
ClassLoaderUtil.getCodebaseURLs(getExportCodebase()),
oldClassLoader);
logger.log(Level.FINEST, "Created ExportClassLoader: {0}",
newClassLoader);
} catch (IOException ioe) {
logger.log(Level.SEVERE, "classloader.problem",
new Object[] {getImportCodebase(), getExportCodebase()});
throw ioe;
}
synchronized (NonActivatableServiceDescriptor.class) {
// supplant global policy 1st time through
if (globalPolicy == null) {
initialGlobalPolicy = Policy.getPolicy();
if (!(initialGlobalPolicy instanceof DynamicPolicy)) {
initialGlobalPolicy =
new DynamicPolicyProvider(initialGlobalPolicy);
}
globalPolicy =
new AggregatePolicyProvider(initialGlobalPolicy);
Policy.setPolicy(globalPolicy);
logger.log(Level.FINEST,
"Global policy set: {0}", globalPolicy);
}
Policy service_policy =
ActivateWrapper.getServicePolicyProvider(
new PolicyFileProvider(getPolicy()));
Policy backstop_policy =
ActivateWrapper.getServicePolicyProvider(initialGlobalPolicy);
LoaderSplitPolicyProvider split_service_policy =
new LoaderSplitPolicyProvider(
newClassLoader, service_policy, backstop_policy);
/* Grant "this" code enough permission to do its work
* under the service policy, which takes effect (below)
* after the context loader is (re)set.
* Note: Throws UnsupportedOperationException if dynamic grants
* aren't supported (because underlying policies don't support it).
*/
split_service_policy.grant(
this.getClass(),
null, /* Principal[] */
new Permission[] { new AllPermission() } );
globalPolicy.setPolicy(newClassLoader, split_service_policy);
}
logger.finest("Attempting to get implementation class");
Class implClass = null;
implClass =
Class.forName(getImplClassName(), false, newClassLoader);
logger.finest("Setting context class loader");
curThread.setContextClassLoader(newClassLoader);
try {
logger.finest("Attempting to get implementation constructor");
Object argParms[] = new Object[]{getServerConfigArgs(), lifeCycle} ;
Class argTypes[] = actTypes ;
if( configuration != null ) {
if( getServerConfigArgs() != null ) {
logger.severe("both configArgs and configuration specified, using configuration");
}
argParms = new Object[]{configuration, lifeCycle} ;
argTypes = new Class[]{Configuration.class, LifeCycle.class }; // TODO:make static
}
Constructor constructor =
implClass.getDeclaredConstructor(argTypes);
logger.log(Level.FINEST,
"Obtained implementation constructor: {0}",
constructor);
constructor.setAccessible(true);
impl = constructor.newInstance(argParms);
logger.log(Level.FINEST,
"Obtained implementation instance: {0}", impl);
if (impl instanceof ServiceProxyAccessor) {
proxy = ((ServiceProxyAccessor)impl).getServiceProxy();
} else if (impl instanceof ProxyAccessor) {
proxy = ((ProxyAccessor)impl).getProxy();
} else {
proxy = null; // just for insurance
}
logger.log(Level.FINEST, "Proxy = {0}", proxy);
curThread.setContextClassLoader(oldClassLoader);
//TODO - factor in code integrity for MO
proxy = (new MarshalledObject(proxy)).get();
} finally {
curThread.setContextClassLoader(oldClassLoader);
}
if(proxy != null) {
proxy = servicePreparer.prepareProxy(proxy);
}
Created created = new Created(impl, proxy);
logger.exiting(NonActivatableServiceDescriptor.class.getName(),
"create", created);
return created;
}
/** Prints out a field summary */
public String toString() {
ArrayList fields = new ArrayList(7);
fields.add(codebase);
fields.add(policy);
fields.add(classpath);
fields.add(implClassName);
fields.add(
((serverConfigArgs == null)
? null : Arrays.asList(serverConfigArgs)));
fields.add(lifeCycle);
fields.add(servicePreparer);
return fields.toString();
}
/**
* Reads the default serializable field values for this object
* and resets the tranisient fields to legal values.
* Also, verifies that the deserialized values are legal.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
// Verify that serialized fields aren't null
if (codebase == null) {
throw new InvalidObjectException("null export codebase");
}
if (policy == null) {
throw new InvalidObjectException("null policy");
}
if (classpath == null) {
throw new InvalidObjectException("null import codebase");
}
if (implClassName == null) {
throw new InvalidObjectException("null implementation class name");
}
//Reinitialize transient fields upon de-serialization.
lifeCycle = NoOpLifeCycle;
servicePreparer = null;
descCreated = false;
descCreatedLock = new Object();
}
/**
* Throws InvalidObjectException, since data for this class is required.
*/
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("no data");
}
}