/*
* 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 net.jini.activation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.activation.ActivationID;
import java.rmi.server.ExportException;
import java.security.Permission;
import java.util.LinkedHashSet;
import java.util.Set;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.export.Exporter;
import net.jini.security.proxytrust.ProxyTrust;
/**
* An <code>Exporter</code> implementation for exporting an activatable
* remote object using its underlying exporter. The proxy returned by
* the <code>export</code> method activates the remote object on
* demand. Each instance of <code>ActivationExporter</code> can export only
* a single remote object.
*
* @author Sun Microsystems, Inc.
* @since 2.0
**/
public final class ActivationExporter implements Exporter
{
/** the activation identifier */
private final ActivationID id;
/** the underlying exporter */
private final Exporter underlyingExporter;
/** The class loader to define the proxy class in */
private final ClassLoader loader;
/** If true, this exporter has already been used to export an object */
private boolean used = false;
/** Cached getClassLoader permission */
private static final Permission getClassLoaderPermission =
new RuntimePermission("getClassLoader");
/**
* Creates an exporter for an activatable remote object with the
* specified activation identifier, underlying exporter, and a
* <code>null</code> class loader.
*
* @param id an activation identifier
* @param underlyingExporter an exporter
* @throws NullPointerException if <code>id</code> or
* <code>underlyingExporter</code> is <code>null</code>
**/
public ActivationExporter(ActivationID id,
Exporter underlyingExporter)
{
this(id, underlyingExporter, null);
}
/**
* Creates an exporter for an activatable remote object with the
* specified activation identifier, underlying exporter, and
* class loader.
*
* @param id an activation identifier
* @param underlyingExporter an exporter
* @param loader the class loader to define the proxy class in, or
* <code>null</code>
* @throws NullPointerException if <code>id</code> or
* <code>underlyingExporter</code> is <code>null</code>
**/
public ActivationExporter(ActivationID id,
Exporter underlyingExporter,
ClassLoader loader)
{
if (id == null || underlyingExporter == null) {
throw new NullPointerException();
}
this.id = id;
this.underlyingExporter = underlyingExporter;
this.loader = loader;
}
/**
* Exports an activatable remote object. This exporter exports
* <code>impl</code> by calling the <code>export</code> method on the
* underlying exporter (supplied during construction of this exporter)
* to obtain an underlying proxy. It then constructs and returns a
* {@link Proxy} instance where:
*
* <ul>
* <li>If the class loader specified at construction is not
* <code>null</code>, the proxy's class is defined by the specified
* loader. Otherwise, if a security manager exists, its {@link
* SecurityManager#checkPermission checkPermission} method is invoked
* with the permission <code>{@link
* RuntimePermission}{"getClassLoader")</code>; this invocation may
* throw a <code>SecurityException</code>. If the above security check
* succeeds, the proxy's class is defined by the class loader of the
* underlying proxy's class.
*
* <li>The proxy implements the following ordered list of interfaces
* (except if the underlying proxy is an instance of {@link
* RemoteMethodControl}, the interface {@link ProxyTrust} is not among
* the interfaces implemented by the proxy):
*
* <p>for each superclass of the underlying proxy's class, starting
* with <code>java.lang.Object</code> and following with each direct
* subclass to the direct superclass of the underlying proxy's class,
* all of the direct superinterfaces of the given superclass that do
* not appear previously in the list, in declaration order (the order
* in which they are declared in the class's <code>implements</code>
* clause), followed by
*
* <p>all of the direct superinterfaces of the underlying proxy's class
* that do not appear previously in the list, in declaration order.
*
* <li>The proxy's invocation handler is an {@link
* ActivatableInvocationHandler} instance constructed with the {@link
* ActivationID} and underlying proxy supplied during construction of
* this exporter.
* </ul>
*
* @throws NullPointerException {@inheritDoc}
* @throws IllegalStateException {@inheritDoc}
* @throws ExportException if a problem occurs exporting
* <code>impl</code> or if the underlying proxy's class
* is non-<code>public</code> and implements
* non-<code>public</code> interfaces
**/
public synchronized Remote export(Remote impl)
throws ExportException
{
/*
* Check if object is already exported; disallow exporting more
* than once via this exporter.
*/
if (used) {
throw new IllegalStateException(
"object already exported via this exporter");
}
if (impl == null) {
throw new NullPointerException("impl is null");
}
/*
* Export the remote object using the underlying exporter.
*/
Remote uproxy = underlyingExporter.export(impl);
used = true;
boolean success = false;
try {
/*
* Choose class loader for proxy's class.
*/
Class uproxyClass = uproxy.getClass();
ClassLoader proxyLoader;
if (loader != null) {
proxyLoader = loader;
} else {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(getClassLoaderPermission);
}
proxyLoader = uproxyClass.getClassLoader();
}
/*
* Get superinterfaces of underlying proxy.
*/
Set interfaceList = new LinkedHashSet();
boolean isConstrainable = uproxy instanceof RemoteMethodControl;
boolean checkPublic =
!Modifier.isPublic(uproxyClass.getModifiers());
getSuperinterfaces(interfaceList, uproxyClass,
isConstrainable, checkPublic);
Class[] proxyInterfaces = (Class[])
interfaceList.toArray(new Class[interfaceList.size()]);
/*
* Create a dynamic proxy with an ActivatableInvocationHandler.
*/
InvocationHandler handler =
new ActivatableInvocationHandler(id, uproxy);
Remote proxy = (Remote)
Proxy.newProxyInstance(proxyLoader,
proxyInterfaces,
handler);
success = true;
return proxy;
} catch (IllegalArgumentException e) {
throw new ExportException("proxy creation failed", e);
} finally {
if (!success) {
unexport(true);
}
}
}
/**
* Unexports the activatable remote object that was previously exported
* via the <code>export</code> method of the underlying exporter
* supplied during construction of this exporter. Returns the result
* of unexporting the remote object by calling the
* <code>unexport</code> method on the underlying exporter passing
* <code>force</code> as the argument.
*
* @throws IllegalStateException {@inheritDoc}
**/
public synchronized boolean unexport(boolean force) {
if (!used) {
throw new IllegalStateException(
"an object has not been exported via this exporter");
}
return underlyingExporter.unexport(force);
}
/**
* Fills the given array list with the superinterfaces implemented by
* the given class eliminating duplicates. If isConstrainable is true,
* then the ProxyTrust interface won't be added to the list.
*
* @param list the list to fill with interfaces
* @param class the class to get the superinterfaces of
* @param isConstrainable true if proxy class implements
* RemoteMethodControl
* @param checkPublic true if proxy has a non-public class
* @throws IllegalArgumentException if the specified class implements
* any illegal remote interfaces
* @throws NullPointerException if the specified class or list is null
* @throws ExportException if proxy has a non-public class and
* implements a non-public interface
**/
private static void getSuperinterfaces(Set list,
Class cl,
boolean isConstrainable,
boolean checkPublic)
throws ExportException
{
Class superclass = cl.getSuperclass();
if (superclass != null) {
getSuperinterfaces(list, superclass, isConstrainable, checkPublic);
}
Class[] interfaces = cl.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class intf = interfaces[i];
/*
* Complain if the underlying proxy has a non-public class and
* implements non-public interfaces.
*/
if (checkPublic && !Modifier.isPublic(intf.getModifiers())) {
throw new ExportException(
"proxy implements non-public interface");
}
/*
* Don't add ProxyTrust remote interface to list of proxy
* interfaces if proxy is "constrainable" (that is implements
* RemoteMethodControl) and don't add duplicates.
*/
if (!isConstrainable || intf != ProxyTrust.class) {
list.add(intf);
}
}
}
}