/*
 * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.rmi.transport;

import java.io.IOException;
import java.io.ObjectOutput;
import java.rmi.MarshalException;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.LogStream;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteServer;
import java.rmi.server.ServerNotActiveException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import sun.rmi.runtime.Log;
import sun.rmi.server.Dispatcher;
import sun.rmi.server.UnicastServerRef;

/**
 * Transport abstraction for enabling communication between different
 * VMs.
 *
 * @author Ann Wollrath
 */
@SuppressWarnings("deprecation")
public abstract class Transport {

    /** "transport" package log level */
    static final int logLevel = LogStream.parseLevel(getLogLevel());

    private static String getLogLevel() {
        return java.security.AccessController.doPrivileged(
            new sun.security.action.GetPropertyAction("sun.rmi.transport.logLevel"));
    }

    /* transport package log */
    static final Log transportLog =
        Log.getLog("sun.rmi.transport.misc", "transport", Transport.logLevel);

    /** References the current transport when a call is being serviced */
    private static final ThreadLocal<Transport> currentTransport = new ThreadLocal<>();

    /** ObjID for DGCImpl */
    private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);

    /** AccessControlContext for setting context ClassLoader */
    private static final AccessControlContext SETCCL_ACC;
    static {
        Permissions perms = new Permissions();
        perms.add(new RuntimePermission("setContextClassLoader"));
        ProtectionDomain[] pd = { new ProtectionDomain(null, perms) };
        SETCCL_ACC = new AccessControlContext(pd);
    }

    /**
     * Returns a <I>Channel</I> that generates connections to the
     * endpoint <I>ep</I>. A Channel is an object that creates and
     * manages connections of a particular type to some particular
     * address space.
     * @param ep the endpoint to which connections will be generated.
     * @return the channel or null if the transport cannot
     * generate connections to this endpoint
     */
    public abstract Channel getChannel(Endpoint ep);

    /**
     * Removes the <I>Channel</I> that generates connections to the
     * endpoint <I>ep</I>.
     */
    public abstract void free(Endpoint ep);

    /**
     * Export the object so that it can accept incoming calls.
     */
    public void exportObject(Target target) throws RemoteException {
        target.setExportedTransport(this);
        ObjectTable.putTarget(target);
    }

    /**
     * Invoked when an object that was exported on this transport has
     * become unexported, either by being garbage collected or by
     * being explicitly unexported.
     **/
    protected void targetUnexported() { }

    /**
     * Returns the current transport if a call is being serviced, otherwise
     * returns null.
     **/
    static Transport currentTransport() {
        return currentTransport.get();
    }

    /**
     * Verify that the current access control context has permission to accept
     * the connection being dispatched by the current thread.  The current
     * access control context is passed as a parameter to avoid the overhead of
     * an additional call to AccessController.getContext.
     */
    protected abstract void checkAcceptPermission(AccessControlContext acc);

    /**
     * Sets the context class loader for the current thread.
     */
    private static void setContextClassLoader(ClassLoader ccl) {
        AccessController.doPrivileged((PrivilegedAction<Void>)() -> {
                Thread.currentThread().setContextClassLoader(ccl);
                return null;
            }, SETCCL_ACC);
    }

    /**
     * Service an incoming remote call. When a message arrives on the
     * connection indicating the beginning of a remote call, the
     * threads are required to call the <I>serviceCall</I> method of
     * their transport.  The default implementation of this method
     * locates and calls the dispatcher object.  Ordinarily a
     * transport implementation will not need to override this method.
     * At the entry to <I>tr.serviceCall(conn)</I>, the connection's
     * input stream is positioned at the start of the incoming
     * message.  The <I>serviceCall</I> method processes the incoming
     * remote invocation and sends the result on the connection's
     * output stream.  If it returns "true", then the remote
     * invocation was processed without error and the transport can
     * cache the connection.  If it returns "false", a protocol error
     * occurred during the call, and the transport should destroy the
     * connection.
     */
    public boolean serviceCall(final RemoteCall call) {
        try {
            /* read object id */
            final Remote impl;
            ObjID id;

            try {
                id = ObjID.read(call.getInputStream());
            } catch (java.io.IOException e) {
                throw new MarshalException("unable to read objID", e);
            }

            /* get the remote object */
            Transport transport = id.equals(dgcID) ? null : this;
            Target target =
                ObjectTable.getTarget(new ObjectEndpoint(id, transport));

            if (target == null || (impl = target.getImpl()) == null) {
                throw new NoSuchObjectException("no such object in table");
            }

            final Dispatcher disp = target.getDispatcher();
            target.incrementCallCount();
            try {
                /* call the dispatcher */
                transportLog.log(Log.VERBOSE, "call dispatcher");

                final AccessControlContext acc =
                    target.getAccessControlContext();
                ClassLoader ccl = target.getContextClassLoader();

                ClassLoader savedCcl = Thread.currentThread().getContextClassLoader();

                try {
                    setContextClassLoader(ccl);
                    currentTransport.set(this);
                    try {
                        java.security.AccessController.doPrivileged(
                            new java.security.PrivilegedExceptionAction<Void>() {
                            public Void run() throws IOException {
                                checkAcceptPermission(acc);
                                disp.dispatch(impl, call);
                                return null;
                            }
                        }, acc);
                    } catch (java.security.PrivilegedActionException pae) {
                        throw (IOException) pae.getException();
                    }
                } finally {
                    setContextClassLoader(savedCcl);
                    currentTransport.set(null);
                }

            } catch (IOException ex) {
                transportLog.log(Log.BRIEF,
                                 "exception thrown by dispatcher: ", ex);
                return false;
            } finally {
                target.decrementCallCount();
            }

        } catch (RemoteException e) {

            // if calls are being logged, write out exception
            if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) {
                // include client host name if possible
                String clientHost = "";
                try {
                    clientHost = "[" +
                        RemoteServer.getClientHost() + "] ";
                } catch (ServerNotActiveException ex) {
                }
                String message = clientHost + "exception: ";
                UnicastServerRef.callLog.log(Log.BRIEF, message, e);
            }

            /* We will get a RemoteException if either a) the objID is
             * not readable, b) the target is not in the object table, or
             * c) the object is in the midst of being unexported (note:
             * NoSuchObjectException is thrown by the incrementCallCount
             * method if the object is being unexported).  Here it is
             * relatively safe to marshal an exception to the client
             * since the client will not have seen a return value yet.
             */
            try {
                ObjectOutput out = call.getResultStream(false);
                UnicastServerRef.clearStackTraces(e);
                out.writeObject(e);
                call.releaseOutputStream();

            } catch (IOException ie) {
                transportLog.log(Log.BRIEF,
                    "exception thrown marshalling exception: ", ie);
                return false;
            }
        }

        return true;
    }
}
