/*
 * 
 * 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.jeri.connection;

import com.sun.jini.jeri.internal.mux.MuxServer;
import com.sun.jini.logging.Levels;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.InboundRequest;
import net.jini.jeri.RequestDispatcher;
import net.jini.jeri.ServerEndpoint;

/**
 * Provides server-side connection management using the <a
 * href="{@docRoot}/net/jini/jeri/connection/doc-files/mux.html">Jini(TM)
 * extensible remote invocation (Jini ERI) multiplexing protocol</a>
 * to frame and multiplex requests and responses over connections.
 *
 * <p>A <code>ServerConnectionManager</code> is created by a
 * connection-based {@link ServerEndpoint} implemention to manage
 * connections.  The {@link #handleConnection handleConnection} method
 * is used to manage connections for a particular {@link
 * ServerConnection}.
 *
 * <p>Each <i>session</i> of the Jini ERI multiplexing protocol is
 * mapped to a new request.  Request data is read as the data received
 * for the session, and response data is written as the data sent for
 * the session.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl
 *
 * This implementation uses the {@link Logger} named
 * <code>net.jini.jeri.connection.ServerConnectionManager</code> to
 * log information at the following levels:
 *
 * <p><table summary="Describes what is logged by
 * ServerConnectionManager to its logger at various logging levels"
 * border=1 cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td>
 * I/O exception initiating handling of a new request on a connection
 *
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td>
 * I/O exception initiating multiplexing on a new connection
 *
 * </table>
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.jeri.connection.mux</code> to log information at the
 * following levels:
 *
 * <p><table summary="Describes what is logged by
 * ServerConnectionManager to the mux logger at various logging
 * levels" border=1 cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Level#INFO INFO} <td> unexpected exception during
 * asynchronous I/O processing
 *
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td> I/O exception during
 * asynchronous I/O processing
 *
 * <tr> <td> {@link Level#FINEST FINEST} <td> detailed implementation
 * activity
 *
 * </table>
 **/
public final class ServerConnectionManager {
    /**
     * ServerConnectionManager logger.
     */
    private static final Logger logger =
	Logger.getLogger("net.jini.jeri.connection.ServerConnectionManager");

    /**
     * Creates a new <code>ServerConnectionManager</code>.
     **/
    public ServerConnectionManager() {
    }

    /**
     * Request dispatcher wrapper around the request dispatcher passed to
     * handleConnection (i.e., the request dispatcher created by the runtime).
     */
    private static final class Dispatcher implements RequestDispatcher {
	/**
	 * The request dispatcher passed to handleConnection.
	 */
	private final RequestDispatcher dispatcher;
	/**
	 * The connection passed to handleConnection.
	 */
	private final ServerConnection c;

	Dispatcher(RequestDispatcher dispatcher,
		   ServerConnection c)
	{
	    this.dispatcher = dispatcher;
	    this.c = c;
	}

	/**
	 * Calls processRequestData on the connection, passing the streams
	 * from the specified inbound request, to obtain the request handle,
	 * wraps the mux inbound request, connection and handle in an inbound
	 * request, checks for accept permission, and calls dispatch on the
	 * underlying dispatcher. If an IOException is thrown, catch it. It
	 * is assumed that the caller will always abort the request (even on
	 * normal return).
	 */
	public void dispatch(InboundRequest req) {
	    try {
		InboundRequest sreq =
		    new Inbound(req, c,
				c.processRequestData(
					    req.getRequestInputStream(),
					    req.getResponseOutputStream()));
		sreq.checkPermissions();
		dispatcher.dispatch(sreq);
	    } catch (IOException e) {
		if (logger.isLoggable(Levels.HANDLED)) {
		    logThrow(logger, "dispatchRequest",
			     "{0} throws", new Object[]{c}, e);
		}
	    }
	}
    }

    /**
     * Inbound request wrapper around the inbound request created by the mux.
     */
    private static final class Inbound implements InboundRequest {
	/**
	 * The inbound request created by the mux.
	 */
	private final InboundRequest req;
	/**
	 * The connection on which the inbound request originates.
	 */
	private final ServerConnection c;
	/**
	 * The inbound request handle.
	 */
	private final InboundRequestHandle handle;

	Inbound(InboundRequest req,
		ServerConnection c,
		InboundRequestHandle handle)
	{
	    this.req = req;
	    this.c = c;
	    this.handle = handle;
	}

	/* delegate to both the underlying request and the connection */
	public void populateContext(Collection context) {
	    req.populateContext(context);
	    c.populateContext(handle, context);
	}

	/* pass-through to the connection */
	public InvocationConstraints checkConstraints(
					    InvocationConstraints constraints)
	    throws UnsupportedConstraintException
	{
	    return c.checkConstraints(handle, constraints);
	}

	/* pass-through to the connection */
	public void checkPermissions() {
	    c.checkPermissions(handle);
	}

	/* pass-through to the underlying request */
	public InputStream getRequestInputStream() {
	    return req.getRequestInputStream();
	}

	/* pass-through to the underlying request */
	public OutputStream getResponseOutputStream() {
	    return req.getResponseOutputStream();
	}

	/* pass-through to underlying request */
	public void abort() {
	    req.abort();
	}
    }

    /**
     * Subclass wrapper around MuxServer for inbound connections.
     */
    private static final class InboundMux extends MuxServer {
	/**
	 * The inbound connection.
	 */
	private final ServerConnection c;

	/**
	 * Constructs an instance from the connection's streams.
	 */
	private InboundMux(ServerConnection c,
			   RequestDispatcher dispatcher)
	    throws IOException
	{
	    super(c.getOutputStream(), c.getInputStream(), dispatcher);
	    this.c = c;
	}

	/**
	 * Constructs an instance from the connection's channel.
	 */
	private InboundMux(ServerConnection c,
			   RequestDispatcher dispatcher,
			   boolean ignore)
	    throws IOException
	{
	    super(c.getChannel(), dispatcher);
	    this.c = c;
	}

	/**
	 * Constructs an instance from the connection.
	 */
	static void create(ServerConnection c,
			   RequestDispatcher dispatcher)
	{
	    RequestDispatcher d = new Dispatcher(dispatcher, c);
	    try {
		if (c.getChannel() == null) {
		    new InboundMux(c, d).start();
		} else {
		    new InboundMux(c, d, true).start();
		}
	    } catch (IOException e) {
		if (logger.isLoggable(Levels.HANDLED)) {
		    logThrow(logger, "handleConnection",
			     "{0} throws", new Object[]{c}, e);
		}
		try {
		    c.close();
		} catch (IOException ee) {
		}
	    }
	}

	/**
	 * Close the connection, so that the provider is notified.
	 */
	protected void handleDown() {
	    try {
		c.close();
	    } catch (IOException e) {
	    }
	}
    }

    /**
     * Log a throw.
     */
    private static void logThrow(Logger logger,
				 String method,
				 String msg,
				 Object[] args,
				 Exception e)
    {
	LogRecord lr = new LogRecord(Levels.HANDLED, msg);
	lr.setLoggerName(logger.getName());
	lr.setSourceClassName(ServerConnectionManager.class.getName());
	lr.setSourceMethodName(method);
	lr.setParameters(args);
	lr.setThrown(e);
	logger.log(lr);
    }

    /**
     * Starts handling requests received on the specified newly
     * accepted connection, dispatching them to the specified request
     * dispatcher asynchronously, and returns immediately.
     *
     * <p>The Jini ERI multiplexing protocol is started on the
     * connection (as the server).  As each request is received, the
     * {@link ServerConnection#processRequestData processRequestData}
     * method of the connection will be invoked with the request input
     * stream and the response output stream of the {@link
     * InboundRequest} created for the request, to obtain a handle for
     * the request.  The {@link ServerConnection#checkPermissions
     * checkPermissions} method of the connection is then invoked with
     * that handle, and if it returns normally,
     * <code>dispatcher</code> is invoked with the
     * <code>InboundRequest</code>.  All of this processing is
     * performed using the same security context in force when this
     * <code>handleConnection</code> method was invoked.  The {@link
     * InboundRequest#checkPermissions checkPermissions}, {@link
     * InboundRequest#checkConstraints checkConstraints}, and {@link
     * InboundRequest#populateContext populateContext} methods of each
     * <code>InboundRequest</code> created are implemented by
     * delegating to the corresponding method of the connection
     * passing the handle for the request and the other arguments (if
     * any).
     *
     * <P>The implementation may close the connection if it determines
     * that the client has closed its side of the connection, if there
     * is an unrecoverable problem with the connection, or for other
     * implementation-specific reasons.  The caller is responsible for
     * closing the connection when the {@link
     * net.jini.jeri.ServerEndpoint.ListenHandle#close close} method
     * of the associated {@link
     * net.jini.jeri.ServerEndpoint.ListenHandle ListenHandle} is
     * invoked.
     *
     * @param conn the server connection
     *
     * @param dispatcher the request dispatcher to use to dispatch
     * requests received on the specified connection
     *
     * @throws NullPointerException if <code>conn</code> or
     * <code>dispatcher</code> is <code>null</code>
     **/
    public void handleConnection(ServerConnection conn,
				 RequestDispatcher dispatcher)
    {
	if (conn == null || dispatcher == null) {
	    throw new NullPointerException();
	}
	InboundMux.create(conn, dispatcher);
    }
}
