/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.jrmp;

import java.lang.ref.WeakReference;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.server.ExportException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.export.Exporter;


/**
 * A <code>JrmpExporter</code> contains the information necessary to export a
 * single remote object to the 
 * <a href="http://java.sun.com/j2se/1.4/docs/guide/rmi/">JRMP</a> runtime.  It
 * acts as an adapter between existing methods for (un)exporting remote objects
 * over JRMP (e.g, {@link UnicastRemoteObject#exportObject(Remote)}, 
 * {@link Activatable#exportObject(Remote, ActivationID, int)}) and the 
 * {@link Exporter} interface.
 *
 * <p>An object exported via a <code>JrmpExporter</code> can customize the
 * following properties that govern invocation behavior and other
 * characteristics of the exported remote object and its stub:
 * <ul>
 * 
 * <li><code>port</code>: the port number for the {@link java.net.ServerSocket}
 * on which the JRMP runtime will listen for incoming calls to the exported
 * object.
 * 
 * <li>{@link RMIClientSocketFactory}: the factory object used by client stubs
 * to create {@link java.net.Socket} objects over which to issue calls to the
 * exported object.  If <code>null</code>, then the global JRMP socket factory
 * will be used (i.e., the value returned by 
 * {@link java.rmi.server.RMISocketFactory#getSocketFactory}), or if there
 * isn't one set, then the default global JRMP socket factory will be used
 * (i.e., the value returned by 
 * {@link java.rmi.server.RMISocketFactory#getDefaultSocketFactory}).
 * 
 * <li>{@link RMIServerSocketFactory}: the factory object used by the JRMP
 * runtime to create <code>ServerSocket</code> objects over which to receive
 * calls to the exported object.  If <code>null</code>, then the global JRMP
 * socket factory will be used, or if there isn't one set, then the default
 * global JRMP socket factory will be used.  The default global JRMP socket
 * factory returns a <code>ServerSocket</code> for an anonymous port if passed
 * a port number of zero.
 * 
 * <li>{@link ActivationID}: the <code>ActivationID</code> identifying this
 * remote object to the activation system, or <code>null</code> if the remote
 * object is not to be exported as activatable.
 * </ul>
 * 
 * <p>This exporter is a front-end adapter on top of
 * <code>UnicastRemoteObject</code> and <code>Activatable</code>; exporting
 * remote objects through this exporter is equivalent to doing so directly via
 * the various <code>exportObject</code> methods defined by the aforementioned
 * classes.
 * 
 * @author	Sun Microsystems, Inc.
 * @since	2.0
 *
 * @com.sun.jini.impl
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.jrmp.JrmpExporter</code> to log
 * information at the following levels:
 *
 * <table summary="Describes what is logged by JrmpExporter at various
 *        logging levels" border=1 cellpadding=5>
 *
 * <tr> <th scope="col"> Level <th scope="col"> Description
 *
 * <tr> <td> {@link Level#FINE FINE} <td> successful export of object
 * 
 * <tr> <td> {@link Level#FINE FINE} <td> attempted unexport of object
 *
 * </table>
 */
public final class JrmpExporter implements Exporter {
    
    private static final Logger logger =
	Logger.getLogger("net.jini.jrmp.JrmpExporter");

    private final int port;
    private final RMIClientSocketFactory csf;
    private final RMIServerSocketFactory ssf;
    private final ActivationID id;
    private WeakReference ref;

    /**
     * Creates an exporter for a non-activatable JRMP "unicast" remote object
     * that exports on an anonymous port and does not use custom socket
     * factories.
     */
    public JrmpExporter() {
	this(0);
    }
    
    /**
     * Creates an exporter for a non-activatable JRMP "unicast" remote object
     * that exports on the specified TCP port and does not use custom socket
     * factories.
     * 
     * @param	port number of the port on which an exported object will
     * 		receive calls (if zero, an anonymous port will be chosen)
     */
    public JrmpExporter(int port) {
	this(port, null, null);
    }
    
    /**
     * Creates an exporter for a non-activatable JRMP "unicast" remote object
     * that exports on the specified TCP port and uses sockets created by the
     * given custom socket factories.
     * 
     * @param	port number of the port on which an exported object will
     * 		receive calls (if zero, an anonymous port will be chosen)
     * @param	csf client-side socket factory (if null, the global JRMP socket
     * 		factory or, if necessary, the default global JRMP socket
     * 		factory will be used to create client-side sockets)
     * @param	ssf server-side socket factory (if null, the global JRMP socket
     * 		factory or, if necessary, the default global JRMP socket
     * 		factory will be used to create server-side sockets)
     */
    public JrmpExporter(int port,
			RMIClientSocketFactory csf,
			RMIServerSocketFactory ssf)
    {
	this.port = port;
	this.csf = csf;
	this.ssf = ssf;
	id = null;
    }
    
    /**
     * Creates an exporter for an activatable JRMP remote object with the given
     * activation ID that exports on the specified TCP port and does not use
     * custom socket factories.
     * 
     * @param	id activation ID associated with the object to export
     * @param	port number of the port on which an exported object will
     * 		receive calls (if zero, an anonymous port will be chosen)
     */
    public JrmpExporter(ActivationID id, int port) {
	this(id, port, null, null);
    }
    
    /**
     * Creates an exporter for an activatable JRMP remote object with the given
     * activation ID that exports on the specified TCP port and uses sockets
     * created by the given custom socket factories.
     * 
     * @param	id activation ID associated with the object to export
     * @param	port number of the port on which an exported object will
     * 		receive calls (if zero, an anonymous port will be chosen)
     * @param	csf client-side socket factory (if null, the global JRMP socket
     * 		factory or, if necessary, the default global JRMP socket
     * 		factory will be used to create client-side sockets)
     * @param	ssf server-side socket factory (if null, the global JRMP socket
     * 		factory or, if necessary, the default global JRMP socket
     * 		factory will be used to create server-side sockets)
     */
    public JrmpExporter(ActivationID id, int port,
			RMIClientSocketFactory csf,
			RMIServerSocketFactory ssf)
    {
	if (id == null) {
	    throw new NullPointerException();
	}
	this.id = id;
	this.port = port;
	this.csf = csf;
	this.ssf = ssf;
    }
    
    /**
     * Returns the port used by this exporter, or zero if an anonymous port is
     * used.
     * 
     * @return	port number, or zero if anonymous
     */
    public int getPort() {
	return port;
    }
    
    /**
     * Returns the client socket factory for this exporter, or
     * <code>null</code> if none (in which case {@link java.net.Socket} objects
     * are created directly).
     * 
     * @return	client socket factory, or <code>null</code> if none
     */
    public RMIClientSocketFactory getClientSocketFactory() {
	return csf;
    }
    
    /**
     * Returns the server socket factory for this exporter, or
     * <code>null</code> if none (in which case
     * <code>java.net.ServerSocket</code> objects are created directly).
     * 
     * @return	server socket factory, or <code>null</code> if none
     */
    public RMIServerSocketFactory getServerSocketFactory() {
	return ssf;
    }
    
    /**
     * Returns the activation ID associated with the object exported by this
     * exporter, or <code>null</code> if activation is not being used with this
     * exporter.
     * 
     * @return	activation ID, or <code>null</code> if none
     */
    public ActivationID getActivationID() {
	return id;
    }
    
    /**
     * Exports a remote object, <code>impl</code>, to the JRMP runtime and
     * returns a proxy (stub) for the remote object.  This method cannot be
     * called more than once to export a remote object or an
     * <code>IllegalStateException</code> will be thrown.
     * 
     * <p>If this exporter was created with a constructor that accepted a
     * <code>java.rmi.activation.ActivationID</code>, then calling this method
     * is equivalent to invoking
     * <code>java.rmi.activation.Activatable.exportObject</code> with the
     * appropriate impl, <code>ActivationID</code>, port,
     * <code>RMIClientSocketFactory</code>, and
     * <code>RMIServerSocketFactory</code> values.  Otherwise, calling this
     * method is equivalent to invoking
     * <code>java.rmi.server.UnicastRemoteObject.exportObject</code> with the
     * appropriate impl, port, <code>RMIClientSocketFactory</code>, and
     * <code>RMIServerSocketFactory</code> values.
     *
     * @throws 	NullPointerException {@inheritDoc}
     * @throws	IllegalStateException {@inheritDoc}
     */
    public synchronized Remote export(Remote impl)
	throws ExportException
    {
	if (impl == null) {
	    throw new NullPointerException();
	} else if (ref != null) {
	    throw new IllegalStateException(
		"object already exported via this exporter");
	}
	ref = new WeakReference(impl);

	try {
	    Remote proxy = (id != null) ? 
		Activatable.exportObject(impl, id, port, csf, ssf) :
		UnicastRemoteObject.exportObject(impl, port, csf, ssf);
	    if (logger.isLoggable(Level.FINE)) {
		logger.log(Level.FINE, 
		    "export of {0} via {1} returns proxy {2}",
		    new Object[]{ impl, this, proxy });
	    }
	    return proxy;
	} catch (ExportException ex) {
	    throw ex;
	} catch (RemoteException ex) {
	    throw new ExportException("export failed", ex);
	}
    }
    
    /**
     * Unexports the remote object exported via this exporter's
     * <code>export</code> method such that the object can no longer
     * accept incoming remote calls that were possible as a result of
     * exporting via this exporter.
     *
     * <p>If <code>force</code> is <code>true</code>, the object is forcibly
     * unexported even if there are pending or in progress calls to the remote
     * object via this exporter.  If <code>force</code> is <code>false</code>,
     * the object is only unexported if there are no pending or in progress
     * calls to the remote object via this exporter.  This method is equivalent
     * to calling <code>java.rmi.activation.Activatable.unexportObject</code>
     * or <code>java.rmi.server.UnicastRemoteObject.unexportObject</code> and
     * passing the "impl" that was previously passed to this exporter's
     * <code>export</code> method, depending on whether or not this exporter
     * was created with a constructor that accepted a
     * <code>java.rmi.activation.ActivationID</code> value.
     * 
     * <p>The return value is <code>true</code> if the object is (or was
     * previously) unexported, and <code>false</code> if the object is still
     * exported.
     *
     * @throws	IllegalStateException {@inheritDoc}
     */
    public synchronized boolean unexport(boolean force) {
	if (ref == null) {
	    throw new IllegalStateException(
		"an object has not been exported via this exporter");
	}
	Remote impl = (Remote) ref.get();
	if (impl == null) {
	    return true;
	}
	try {
	    boolean result = (id != null) ?
		Activatable.unexportObject(impl, force) :
		UnicastRemoteObject.unexportObject(impl, force);
	    if (logger.isLoggable(Level.FINE)) {
		logger.log(Level.FINE, "unexport on {0} returns {1}",
		    new Object[]{ this, Boolean.valueOf(result) });
	    }
	    return result;
	} catch (NoSuchObjectException ex) {
	    return true;
	}
    }
    
    /**
     * Returns the string representation for this exporter.
     * 
     * @return the string representation for this exporter
     */
    public String toString() {
	return (id != null) ?
	    "JrmpExporter[" + id + "," + port + "," + csf + "," + ssf + "]" :
	    "JrmpExporter[" + port + "," + csf + "," + ssf + "]";
    }
}
