byps.BTransport.java Source code

Java tutorial

Introduction

Here is the source code for byps.BTransport.java

Source

package byps;

/* USE THIS FILE ACCORDING TO THE COPYRIGHT RULES IN LICENSE.TXT WHICH IS PART OF THE SOURCE CODE PACKAGE */

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BTransport {

    private final BApiDescriptor apiDesc;

    private final BWire wire;

    private final BServerRegistry serverRegistry;

    private BTargetId targetId;

    private String sessionId;

    /**
     * Server ID to which this BTransport is connected.
     * If this object belongs to a foreign interface, 
     * the connectedServerId is not equal to targetId.serverId.
     * This value is used as serverId for streams to be sent.
     */
    private int connectedServerId;

    private BProtocol protocol;

    protected BAuthentication authentication;

    public BTransport(BApiDescriptor apiDesc, BWire wire, BServerRegistry serverRegistry) {
        this.apiDesc = apiDesc;
        this.wire = wire;
        this.targetId = BTargetId.ZERO;
        this.sessionId = BTargetId.SESSIONID_ZERO;
        this.serverRegistry = serverRegistry;
    }

    public BTransport(BTransport rhs, BTargetId targetId) {
        this.apiDesc = rhs.apiDesc;
        this.wire = rhs.wire;
        this.targetId = targetId;
        this.sessionId = rhs.sessionId;
        this.protocol = rhs.getProtocol();

        // This constructor is called, if a stub maybe from another client is deserialized.
        // We cannot use the same authentication here, because the other client can only 
        // authenticate this stub.
        this.authentication = null;

        this.serverRegistry = null;

        // Still connected to the server given by rhs.
        this.connectedServerId = rhs.targetId.getServerId();
    }

    public BWire getWire() {
        return wire;
    }

    public BApiDescriptor getApiDesc() {
        return apiDesc;
    }

    public BServerRegistry getServerRegistry() {
        return serverRegistry;
    }

    public synchronized void setProtocol(BProtocol protocol) {
        this.protocol = protocol;
    }

    public synchronized BProtocol getProtocol() {
        return protocol;
    }

    public synchronized void applyNegotiate(BNegotiate negoResponse) throws BException {
        BNegotiate nego = new BNegotiate(negoResponse);
        protocol = createNegotiatedProtocol(nego);
        setSessionId(negoResponse.sessionId);
        setTargetId(negoResponse.targetId);
        if (log.isDebugEnabled())
            log.debug("targetId=" + targetId + ", protocol=" + protocol);
    }

    public synchronized BOutput getOutput() throws BException {
        if (protocol == null)
            throw new BException(BExceptionC.INTERNAL, "No protocol negotiated.");
        BOutput bout = protocol.getOutput(this, null);
        return bout;
    }

    public synchronized BOutput getResponse(BMessageHeader requestHeader) throws BException {
        if (protocol == null)
            throw new BException(BExceptionC.INTERNAL, "No protocol negotiated.");
        BMessageHeader responseHeader = requestHeader.createResponse();
        BOutput bout = null;

        if (protocol instanceof BProtocolS) {
            if (!responseHeader.isBinaryMessage()) {
                bout = new BOutputJson(this, responseHeader);
            }
        } else {
            if (responseHeader.isBinaryMessage()) {
                throw new BException(BExceptionC.CORRUPT, "JSON protocol expected.");
            }
        }

        if (bout == null) {
            bout = protocol.getOutput(this, responseHeader);
        }

        return bout;
    }

    public synchronized BInput getInput(BMessageHeader header, ByteBuffer buf) throws BException {
        BInput bin = null;

        // header is null in the test cases that check serialization.
        if (header == null) {
            header = new BMessageHeader();
            header.read(buf);
        }

        if (protocol == null)
            throw new BException(BExceptionC.INTERNAL, "No protocol negotiated.");

        if (protocol instanceof BProtocolS) {
            if (!header.isBinaryMessage()) {
                bin = new BInputJson(header, buf, this);
            }
        } else {
            if (header.isBinaryMessage()) {
                throw new BException(BExceptionC.CORRUPT, "JSON protocol expected.");
            }
        }

        if (bin == null) {
            bin = protocol.getInput(this, header, buf);
        }

        return bin;
    }

    public <T> void sendMethod(final BMethodRequest methodRequest, final BAsyncResult<T> asyncResult) {
        BAsyncResultReceiveMethod<T> outerResult = new BAsyncResultReceiveMethod<T>(asyncResult);
        assignSessionThenSendMethod(methodRequest, outerResult);
    }

    protected <T> void assignSessionThenSendMethod(final BMethodRequest methodRequest,
            final BAsyncResult<T> asyncResult) {

        if (protocol == null) {
            Throwable exception = new BException(BExceptionC.INTERNAL, "No protocol negotiated.");
            asyncResult.setAsyncResult(null, exception);
        } else if (authentication != null) {

            try {
                final int typeId = protocol.getRegistry().getSerializer(methodRequest, true).typeId;

                BAsyncResult<Object> sessionResult = new BAsyncResult<Object>() {

                    public void setAsyncResult(Object session, Throwable ex) {

                        if (ex != null) {

                            boolean relogin = internalIsReloginException(ex, typeId);
                            if (relogin) {
                                reloginAndRetrySend(methodRequest, asyncResult);
                            } else {
                                asyncResult.setAsyncResult(null, ex);
                            }
                        } else {

                            methodRequest.setSession(session);
                            BTransport.this.send(methodRequest, asyncResult);
                        }
                    }
                };

                authentication.getSession(null, typeId, sessionResult);
            } catch (BException e) {
                asyncResult.setAsyncResult(null, e);
            }

        } else {
            BTransport.this.send(methodRequest, asyncResult);
        }
    }

    public <T> void send(final Object obj, final BAsyncResult<T> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("send(obj=" + obj + ", asyncResult=" + asyncResult);
        try {
            final BOutput bout = getOutput();

            if (log.isDebugEnabled())
                log.debug("store object");
            bout.store(obj);

            final BAsyncResult<BMessage> outerResult = new BAsyncResult<BMessage>() {

                @SuppressWarnings("unchecked")
                @Override
                public void setAsyncResult(BMessage msgRecv, Throwable e) {
                    if (log.isDebugEnabled())
                        log.debug("setAsyncResult(" + msgRecv + ", ex=" + e);

                    boolean relogin = false;

                    try {

                        if (e != null) {

                            // BYPS relogin error? (HTTP 403)
                            relogin = internalIsReloginException(e,
                                    protocol.getRegistry().getSerializer(obj, true).typeId);
                            if (!relogin) {
                                asyncResult.setAsyncResult(null, e);
                            }

                        } else {
                            final BInput bin = getInput(msgRecv.header, msgRecv.buf);
                            if (log.isDebugEnabled())
                                log.debug("load object");
                            T ret = (T) bin.load();
                            if (log.isDebugEnabled())
                                log.debug("ret = " + ret);
                            asyncResult.setAsyncResult(ret, e);
                        }

                    } catch (Throwable ex) {
                        if (log.isDebugEnabled())
                            log.debug("Received exception.", ex);

                        // Application relogin error?
                        try {
                            relogin = internalIsReloginException(ex, bout.registry.getSerializer(obj, true).typeId);
                        } catch (BException ignored) {
                        }
                        if (log.isDebugEnabled())
                            log.debug("isReloginException=" + relogin);

                        if (!relogin) {
                            asyncResult.setAsyncResult(null, ex);
                        }

                    }

                    if (relogin) {

                        // Authenticate and send the message again.

                        // The server is responsible for killing long-polls of invalid sessions.
                        // So we do not need to stop the serverR before re-login.

                        reloginAndRetrySend((BMethodRequest) obj, asyncResult);

                    }

                    if (log.isDebugEnabled())
                        log.debug(")setAsyncResult");
                }

            };

            final BMessage msgSend = bout.toMessage();
            wire.send(msgSend, outerResult);

        } catch (Throwable e) {
            if (log.isDebugEnabled())
                log.debug("Failed to serialize object", e);
            asyncResult.setAsyncResult(null, e);
        }

        if (log.isDebugEnabled())
            log.debug(")send");
    }

    protected <T> void reloginAndRetrySend(final BMethodRequest methodRequest, final BAsyncResult<T> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("re-login");
        try {

            final BAsyncResult<Boolean> loginResult = new BAsyncResult<Boolean>() {

                public void setAsyncResult(Boolean succ, Throwable e2) {
                    if (log.isDebugEnabled())
                        log.debug("auth.login asyncResult=" + succ + ", ex=" + e2);
                    if (e2 != null) {
                        asyncResult.setAsyncResult(null, e2);
                    } else {
                        // Send again
                        BTransport.this.assignSessionThenSendMethod(methodRequest, asyncResult);
                    }
                };
            };

            negotiateProtocolClient(loginResult);

        } catch (Throwable ex2) {
            asyncResult.setAsyncResult(null, ex2);
        }
    }

    public void recv(BServer server, BMessage msg, final BAsyncResult<BMessage> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("recv(");

        try {

            final BInput bin = getInput(msg.header, msg.buf);

            final BAsyncResult<Object> methodResult = new BAsyncResult<Object>() {

                @Override
                public void setAsyncResult(Object obj, Throwable e) {
                    if (log.isDebugEnabled())
                        log.debug("setAsyncResultOrException(");
                    try {
                        BOutput bout = getResponse(bin.header);
                        if (e != null) {
                            if (log.isDebugEnabled())
                                log.debug("exception:", e);
                            bout.setException(e);
                        } else {
                            bout.store(obj);
                        }
                        final BMessage msg = bout.toMessage();
                        asyncResult.setAsyncResult(msg, null);
                    } catch (BException ex) {

                        // Try to send the exception to the other part
                        try {
                            BOutput bout = getResponse(bin.header);
                            bout.setException(ex);
                            final BMessage msg = bout.toMessage();
                            asyncResult.setAsyncResult(msg, null);
                        } catch (BException ex2) {

                            // Process the error in this part. 
                            // The server side will return a HTTP 500.
                            asyncResult.setAsyncResult(null, ex2);
                        }
                    }
                    if (log.isDebugEnabled())
                        log.debug(")setAsyncResultOrException");
                }

            };

            // server-side: Target ID might be encrypted
            final BTargetId targetIdEncr = bin.header.targetId;
            final BTargetId clientTargetId = (serverRegistry != null)
                    ? serverRegistry.encryptTargetId(targetIdEncr, false)
                    : targetIdEncr;

            // Does the clientTargetId belong to another server?
            // If so, get the BClient object to forward the message.
            final BClient client = (serverRegistry != null)
                    ? serverRegistry.getForwardClientIfForeignTargetId(clientTargetId)
                    : null;

            // Read message
            final Object methodObj = bin.load();
            if (log.isDebugEnabled())
                log.debug("messageId=" + bin.header.messageId);

            // Forward message to other server?
            if (client != null) {
                forwardMessage(client, clientTargetId, methodObj, methodResult);
            } else {
                // Server the message here.
                server.recv(clientTargetId, methodObj, methodResult);
            }
        } catch (Exception e) {
            asyncResult.setAsyncResult(null, e);
        }

        if (log.isDebugEnabled())
            log.debug(")recv");
    }

    protected void forwardMessage(final BClient client, final BTargetId clientTargetId, final Object methodObj,
            final BAsyncResult<Object> methodResult) throws BException {
        //    BOutput bout = client.getTransport().getOutput();
        //    bout.header.targetId = clientTargetId;
        //    bout.store(methodObj);
        //    BMessage forwardMessage = bout.toMessage();
        //
        //    BAsyncResult<BMessage> messageResult = new BAsyncResult<BMessage>() {
        //      public void setAsyncResult(BMessage result, Throwable ex) {
        //        try {
        //          if (ex != null) {
        //            methodResult.setAsyncResult(null, ex);
        //          }
        //          else {
        //            BInput bin = client.getTransport().getInput(result.header, result.buf);
        //            Object obj = bin.load();
        //            methodResult.setAsyncResult(obj, null);
        //          }
        //        } catch (Exception e) {
        //          methodResult.setAsyncResult(null, e);
        //        }
        //      }
        //    };
        //
        //    client.getTransport().wire.send(forwardMessage, messageResult);

        BAsyncResult<Object> outerResult = new BAsyncResult<Object>() {

            @Override
            public void setAsyncResult(Object result, Throwable exception) {
                methodResult.setAsyncResult(result, exception);
            }

        };

        client.getTransport().send(methodObj, outerResult);
    }

    public void negotiateProtocolClient(final BAsyncResult<Boolean> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("negotiateProtocolClient(");

        if (log.isDebugEnabled())
            log.debug("negotiateActive=" + negotiateActive);

        // Check that we do not run into recursive authentication requests.

        // Observed in production environments: negotateActive==true and no authentication request active
        // The reason for this problem might be that I did not set negotiateActive=false and asyncResultsWaitingForAuthentication.clear() in the same synchronized block. 
        // To make authentication more stable, the number of outstanding authentication requests are checked. 
        // If there are no authentication requests active, the flag negotiateActive is reset.  

        synchronized (asyncResultsWaitingForAuthentication) {

            // Already have an active negotiation request?
            if (negotiateActive) {

                // Are there threads waiting?
                if (asyncResultsWaitingForAuthentication.size() != 0) {

                    // Most likely slow or recursive authentication 
                    BException ex = new BException(BExceptionC.FORBIDDEN,
                            "Authentication procedure failed. Server returned 401 for every request. "
                                    + "A common reason for this error is slow authentication handling.");
                    // ... or calling a function that requires authentication in BAuthentication.authenticate() - see. TestRemoteWithAuthentication.testAuthenticateBlocksRecursion 
                    asyncResult.setAsyncResult(false, ex);
                    return;

                } else {

                    // Correction: if no threads are waiting then there cannot be an aktive negotiation request.
                    negotiateActive = false;
                }

            } else {

                // Now, this is the active negotiation request.
                negotiateActive = true;
            }
        }

        try {
            if (log.isDebugEnabled())
                log.debug("build nego message");
            ByteBuffer buf = ByteBuffer.allocate(BNegotiate.NEGOTIATE_MAX_SIZE);
            final BNegotiate negoRequest = new BNegotiate(apiDesc);
            negoRequest.write(buf);
            buf.flip();

            BAsyncResult<BMessage> outerResult = new BAsyncResult<BMessage>() {

                @Override
                public void setAsyncResult(BMessage msg, Throwable e) {
                    try {
                        if (log.isDebugEnabled())
                            log.debug("nego result=" + msg + ", ex=" + e);
                        if (e == null) {

                            BNegotiate negoResponse = null;
                            if (msg.header.messageObject != null) {
                                negoResponse = (BNegotiate) msg.header.messageObject;
                            } else {
                                negoResponse = new BNegotiate();
                                negoResponse.read(msg.buf);
                            }

                            applyNegotiate(negoResponse);

                            internalAuthenticate(asyncResult);
                        } else {
                            asyncResult.setAsyncResult(Boolean.FALSE, e);
                        }
                    } catch (Throwable ex) {
                        asyncResult.setAsyncResult(Boolean.FALSE, ex);
                    }
                }

            };

            if (log.isDebugEnabled())
                log.debug("send nego");
            BMessageHeader header = new BMessageHeader();
            header.messageId = wire.makeMessageId();
            final BMessage msg = new BMessage(header, buf, null);
            wire.send(msg, outerResult);

        } catch (Throwable e) {
            if (log.isDebugEnabled())
                log.debug("nego failed, ", e);
            asyncResult.setAsyncResult(Boolean.FALSE, e);
        }
        if (log.isDebugEnabled())
            log.debug(")negotiateProtocolClient");
    }

    protected void internalAuthenticate(final BAsyncResult<Boolean> asyncResult) {

        if (log.isDebugEnabled())
            log.debug("internalAuthenticate(");

        // Authentication enabled?
        if (authentication != null) {

            // (1)
            // If the session has expired, all threads will receive an re-login
            // exception and run through this block.
            // Only the first one will perform an authentication. From the
            // other threads the BAsyncResult objects are collected and triggered
            // when authentication will have been done, see (2).

            boolean first = false;
            boolean assumeAuthenticationIsValid = false;
            synchronized (asyncResultsWaitingForAuthentication) {
                assumeAuthenticationIsValid = lastAuthenticationTime + RETRY_AUTHENTICATION_AFTER_MILLIS >= System
                        .currentTimeMillis();
                if (!assumeAuthenticationIsValid) {
                    first = asyncResultsWaitingForAuthentication.size() == 0;
                    asyncResultsWaitingForAuthentication.add(asyncResult);
                }
            }
            if (log.isDebugEnabled())
                log.debug("valid auth=" + assumeAuthenticationIsValid);

            // First thread in this block performs authentication.
            if (log.isDebugEnabled())
                log.debug("first auth=" + first);
            if (first) {

                BAsyncResult<Boolean> authResult = new BAsyncResult<Boolean>() {

                    // (2)
                    // Authentication has been performed and either succeeded or failed,
                    // if this function is called. The function triggers all collected
                    // BAsyncResult objects.
                    public void setAsyncResult(Boolean ignored, Throwable ex) {

                        ArrayList<BAsyncResult<Boolean>> copyResults = null;
                        synchronized (asyncResultsWaitingForAuthentication) {
                            copyResults = new ArrayList<BAsyncResult<Boolean>>(
                                    asyncResultsWaitingForAuthentication);
                            asyncResultsWaitingForAuthentication.clear();
                            lastAuthenticationTime = System.currentTimeMillis();
                            lastAuthenticationException = ex;

                            // Now, the authentication is assumed to be valid for RETRY_AUTHENTICATION_AFTER_SECONDS.
                            // If there are threads currently waiting at (1), they will not trigger an authentication
                            // but will set the result immediately.

                            if (log.isDebugEnabled())
                                log.debug("reset negotiateActive");
                            negotiateActive = false;
                        }

                        if (log.isDebugEnabled())
                            log.debug("notify pending=#" + copyResults.size());
                        for (int i = 0; i < copyResults.size(); i++) {
                            copyResults.get(i).setAsyncResult(ignored, ex);
                        }
                    }
                };

                if (log.isDebugEnabled())
                    log.debug("call authenticate");
                authentication.authenticate(null, authResult);

            }

            // Last authentication was performed just a few seconds before.
            else if (assumeAuthenticationIsValid) {

                // Assume that the session is still valid or that
                // the exception from the last authentication would
                // be received again at this time.
                asyncResult.setAsyncResult(Boolean.FALSE, lastAuthenticationException);
            } else {
                // innerResult has been added to asyncResultsWaitingForAuthentication 
                // and will be called in InternalAuthenticate_BAsyncResult
            }

        }

        // Authentication is not used. The session is valid per definition.
        else {
            asyncResult.setAsyncResult(Boolean.FALSE, null);
        }

        if (log.isDebugEnabled())
            log.debug(")internalAuthenticate");
    }

    private BProtocol createNegotiatedProtocol(BNegotiate nego) throws BException {
        BProtocol protocol = null;

        if (nego.protocols.startsWith(BProtocolS.BINARY_MODEL.getProtocolId())) {
            int negotiatedBypsVersion = Math.min(BMessageHeader.BYPS_VERSION_CURRENT, nego.bversion);
            long negotiatedVersion = Math.min(apiDesc.version, nego.version);
            nego.protocols = BProtocolS.BINARY_MODEL.getProtocolId();
            if (nego.byteOrder == null)
                nego.byteOrder = ByteOrder.BIG_ENDIAN;
            nego.version = negotiatedVersion;
            nego.bversion = negotiatedBypsVersion;
            protocol = new BProtocolS(apiDesc, negotiatedBypsVersion, negotiatedVersion, nego.byteOrder);
        } else if (nego.protocols.startsWith(BProtocolJson.BINARY_MODEL.getProtocolId())) {
            nego.protocols = BProtocolJson.BINARY_MODEL.getProtocolId();
            protocol = new BProtocolJson(apiDesc);
        } else {
            throw new BException(BExceptionC.CORRUPT, "Protocol negotiation failed.");
        }

        return protocol;
    }

    protected BProtocol negotiateProtocolServer(BTargetId targetId, ByteBuffer buf,
            BAsyncResult<ByteBuffer> asyncResult) {
        BProtocol ret = null;
        try {
            BNegotiate nego = new BNegotiate();
            nego.read(buf);

            synchronized (this) {
                this.protocol = ret = createNegotiatedProtocol(nego);
                setSessionId(targetId.toSessionId());
                setTargetId(targetId);
            }

            ByteBuffer bout = ByteBuffer.allocate(BNegotiate.NEGOTIATE_MAX_SIZE);
            try {
                nego.targetId = targetId;
                nego.sessionId = targetId.toSessionId();
                nego.write(bout);
                bout.flip();
                asyncResult.setAsyncResult(bout, null);
            } finally {
            }

        } catch (Throwable e) {
            asyncResult.setAsyncResult(null, e);
        }
        return ret;
    }

    public synchronized BTargetId getTargetId() {
        return targetId;
    }

    public synchronized void setTargetId(BTargetId v) {
        this.targetId = v;
        this.connectedServerId = v.getServerId();
    }

    public synchronized String getSessionId() {
        return sessionId;
    }

    public synchronized void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    public synchronized int getConnectedServerId() {
        return this.connectedServerId;
    }

    public String toString() {
        return "[" + targetId + ", wire=" + wire + "]";
    }

    protected boolean internalIsReloginException(Throwable ex, int typeId) {
        if (log.isDebugEnabled())
            log.debug("isReloginException(ex=" + ex);

        boolean ret = false;

        if (authentication != null && ex != null) {
            ret = authentication.isReloginException(null, ex, typeId);
        }

        if (log.isDebugEnabled())
            log.debug(")internalIsReloginException=" + ret);
        return ret;
    }

    /**
     * Returns true, if a re-login should be performed.
     * This function can be called from an implementation of the BAuthentication interface.
     * It returns true, if the given exception was caused by a HTTP 403 error or
     * if it is a BException and contains the code BExceptionO.AUTHENTICATION_REQUIRED.
     * @param ex Exception
     * @param typeId Type ID (serialVersionUID) of the BMethodRequest class.
     * @return true, if re-login should be performed.
     */
    public boolean isReloginException(Throwable ex, int typeId) {
        if (log.isDebugEnabled())
            log.debug("isReloginException(ex=" + ex);

        boolean ret = false;

        // Check exception
        if (ex instanceof BException) {
            BException bex = (BException) ex;
            ret = (bex.code == BExceptionC.UNAUTHORIZED);
        }

        if (log.isDebugEnabled())
            log.debug(")isReloginException=" + ret);
        return ret;
    }

    /**
     * Assing authentication object.
     * @param auth Authentication object
     */
    protected void setAuthentication(BAuthentication auth) {
        synchronized (asyncResultsWaitingForAuthentication) {
            authentication = auth;
            asyncResultsWaitingForAuthentication.clear();
            lastAuthenticationException = null;
            lastAuthenticationTime = 0;
        }
    }

    /**
     * List of BAsyncResult objects from requests waiting for authentication.
     */
    protected final ArrayList<BAsyncResult<Boolean>> asyncResultsWaitingForAuthentication = new ArrayList<BAsyncResult<Boolean>>();

    /**
     * Sytem millis when authentication was perfomed the last time.
     */
    protected long lastAuthenticationTime = 0;

    /**
     * Exception received from the last authentication.
     * Is null, if authentication was successful.
     */
    protected Throwable lastAuthenticationException = null;

    /**
     * Last authentication result is assumed to be valid for this time.
     */
    public final static long RETRY_AUTHENTICATION_AFTER_MILLIS = 1 * 1000;

    protected boolean negotiateActive;

    private final static Log log = LogFactory.getLog(BTransport.class);

}