byps.http.HWireClientR.java Source code

Java tutorial

Introduction

Here is the source code for byps.http.HWireClientR.java

Source

package byps.http;

/* USE THIS FILE ACCORDING TO THE COPYRIGHT RULES IN LICENSE.TXT WHICH IS PART OF THE SOURCE CODE PACKAGE */
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

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

import byps.BAsyncResult;
import byps.BContentStream;
import byps.BException;
import byps.BExceptionC;
import byps.BMessage;
import byps.BMessageHeader;
import byps.BTargetId;
import byps.BWire;

public class HWireClientR extends BWire {

    public HWireClientR(BWire wireServer) {
        super(wireServer.getFlags());
        this.wireServer = wireServer;
    }

    public synchronized void done() {
        if (log.isDebugEnabled())
            log.debug("done(");

        canceled = true;

        BException bex = new BException(BExceptionC.CANCELLED, "Longpoll canceled");

        // Notify the threads inside the server waiting for results that the
        // their calls are canceled.
        ArrayList<BAsyncResult<BMessage>> asyncResults = mapAsyncResults.getAndRemoveAll();
        for (BAsyncResult<BMessage> asyncResult : asyncResults) {
            // Gib iBuf an den Thread weiter, der auf das Resultat wartet.
            synchronized (asyncResult) {
                if (log.isDebugEnabled())
                    log.debug("pass cancel message to asyncResult, notify thread waiting in send()");
                try {
                    asyncResult.setAsyncResult(null, bex);
                } catch (Throwable ex) {
                    // just make sure, that all results are handled.
                }
                asyncResult.notifyAll();
            }
        }

        // Notify the client that the long-poll has finished.
        final ArrayList<AsyncRequestFromLongpoll> lrequests = lonpollRequests.getAndRemoveAll();
        for (AsyncRequestFromLongpoll lrequest : lrequests) {
            try {
                lrequest.request.setAsyncResult(null, bex);
            } catch (Throwable ignored) {
                // happens, if the HTTP session has already been invalidated
            }
        }
        if (log.isDebugEnabled())
            log.debug(")done");
    }

    /**
     * Receive a long poll (reverse HTTP) request.
     * 
     * The HTTP request body (stored in ibuf) contains the response from the
     * client. The send() function is waiting for this response. As send() has
     * started the request, it has put an BAsyncResult object into the map
     * asyncResults_access_sync associated with the key messageId. This object is
     * found by the messageId and ibuf is passed to it by a call to
     * setAsyncResult.
     * 
     * The HTTP response has been encapsulated in parameter asyncRequest by the
     * caller. It will receive the next call when the server has to invoke the
     * client. Until the server needs the asyncRequest, it is stored in the map
     * asyncRequests_access_sync. The send() function takes the asyncRequest from
     * there and writes the request bytes into it.
     * 
     * @param messageId
     * @param ibuf
     * @param asyncRequest
     * @throws IOException
     */
    public void recvLongPoll(BMessage ibuf, final BAsyncResult<BMessage> nextRequest) throws IOException {
        if (log.isDebugEnabled())
            log.debug("recvLongPoll(messageId=" + ibuf.header.messageId);

        if (log.isDebugEnabled())
            log.debug("message buffer=" + ibuf);

        // Function send() has stored its parameter BAsyncResult mapped under the messageId.  
        BAsyncResult<BMessage> asyncResult = mapAsyncResults.remove(ibuf.header.messageId);
        if (log.isDebugEnabled())
            log.debug("asyncResult for messageId: " + asyncResult);

        // If a BAsyncResult object is found ...
        if (asyncResult != null) {

            // ... pass ibuf as result.
            if (log.isDebugEnabled())
                log.debug("pass message buffer to asyncResult");
            asyncResult.setAsyncResult(ibuf, null);

        } else {
            // Function send() has not been called before. 
        }

        // If function send() could not find a pending long-poll, it pushed 
        // the message into the queue of pending messages.
        PendingMessage pendingMessage = pendingMessages.poll();

        // Pending message available? ...
        if (pendingMessage != null) {

            // ... send the pending message immediately to the client.
            nextRequest.setAsyncResult(pendingMessage.msg, null);
        } else {

            // ... push the long-poll into the queue.
            lonpollRequests.add(new AsyncRequestFromLongpoll(nextRequest, ibuf.header));
            if (log.isDebugEnabled())
                log.debug("add longpoll to list, #polls=" + lonpollRequests.size());
        }

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

    @Override
    public void send(BMessage msg, BAsyncResult<BMessage> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("send(" + msg + ", asyncResult=" + asyncResult);

        try {

            final long messageId = msg.header.messageId;

            // Save result object for next long-poll
            if (log.isDebugEnabled())
                log.debug("map messageId=" + messageId + " to asyncResult=" + asyncResult);
            mapAsyncResults.put(messageId, asyncResult);

            for (int i = 0; i < 2; i++) {

                // If canceled, set BExceptionC.CLIENT_DIED to asyncResult 
                if (canceled) {
                    terminateMessage(messageId, null);
                    break;
                }

                // Find valid long-poll request object
                BAsyncResult<BMessage> asyncRequest = getNextLongpollRequestOrPushMessage(msg);

                // No long-poll found?
                if (asyncRequest == null) {

                    // The message was pushed into pendingMessages_access_sync.
                    // It will be sent in the next call to recvLongPoll. 
                    break;
                }

                // Send the message
                try {

                    asyncRequest.setAsyncResult(msg, null);
                    break;

                } catch (Throwable e) {

                    if (e.toString().indexOf("ClientAbortException") >= 0) {
                        terminateMessage(messageId, e);
                        break;
                    } else {
                        // The underlying long-poll request has already timed out.
                        // Maybe there is another connection available.
                        // -> retry
                    }

                }

            }

        } catch (Throwable e) {
            if (log.isWarnEnabled())
                log.warn("Failed to send reverse message.", e);
            asyncResult.setAsyncResult(null, e);
        }

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

    protected BAsyncResult<BMessage> getNextLongpollRequestOrPushMessage(BMessage msg) {

        // Get next long-poll request from queue.
        AsyncRequestFromLongpoll longpollRequest = lonpollRequests.poll();

        // long-poll active? ...
        if (longpollRequest != null)
            return longpollRequest.request;

        // Push message into the queue of pending messages.
        // This message will be immediately sent at the next time recvLongPoll is called.
        if (!pendingMessages.offer(new PendingMessage(msg))) {

            // Queue is full, terminate message with error.
            if (log.isDebugEnabled())
                log.debug("Failed to add pending msg=" + msg);
            BException ex = new BException(BExceptionC.TOO_MANY_REQUESTS, "Failed to add pending message.");
            terminateMessage(msg.header.messageId, ex);
        }

        return null;
    }

    @Override
    public void putStreams(List<BContentStream> streams, BAsyncResult<BMessage> asyncResult) {
        wireServer.putStreams(streams, asyncResult);
    };

    @Override
    public BContentStream getStream(BTargetId targetId) throws IOException {
        return wireServer.getStream(targetId);
    }

    /**
     * Terminate the message to be sent to the client.
     * The caller receives a {@link BExceptionC#CLIENT_DIED}.
     * @param messageId
     * @param e Exception used for BException detail.
     */
    protected void terminateMessage(final long messageId, Throwable e) {

        BException bex = null;

        // Get the asyncResult pushed from the send() method into the
        // asyncResults_access_sync map.
        BAsyncResult<BMessage> asyncResult = mapAsyncResults.remove(messageId);
        if (asyncResult != null) {

            if (e instanceof BException) {
                bex = (BException) e;
            } else {
                // Find the innermost exception cause
                Throwable innerException = e;
                while (e != null) {
                    innerException = e;
                    e = e.getCause();
                }

                // Notify the caller of send() with an exception.
                bex = new BException(BExceptionC.CLIENT_DIED, "", innerException);
            }
        }

        asyncResult.setAsyncResult(null, bex);
    }

    /**
     * This function releases expired long-polls and messages.
     * The client application receives a status code 204 for a long-poll. 
     */
    public void cleanup() {

        ArrayList<AsyncRequestFromLongpoll> lpolls = removeExpiredLongpolls();
        ArrayList<PendingMessage> msgs = getExpiredMessages();

        if (lpolls.size() != 0) {

            // The client should send a new long-poll.
            // HWriteResponseHelper will return HTTP status cod 204

            BException ex = new BException(BExceptionC.RESEND_LONG_POLL, "");
            for (AsyncRequestFromLongpoll lrequest : lpolls) {
                try {
                    lrequest.request.setAsyncResult(null, ex);
                } catch (Throwable e) {
                    // catch "Response already written"
                    if (log.isDebugEnabled())
                        log.debug("Failed to respond to longpoll=" + lrequest, e);
                }
            }
        }

        if (msgs.size() != 0) {

            BException ex = new BException(BExceptionC.TIMEOUT, "Timeout while waiting for reverse request.");
            for (PendingMessage msg : msgs) {
                try {
                    terminateMessage(msg.msg.header.messageId, ex);
                } catch (Throwable e) {
                    // just make sure that cleanup runs over all messages.
                    if (log.isDebugEnabled())
                        log.debug("Failed to terminate pending message=" + msg, e);
                }
            }
        }

    }

    /**
     * Remove expired long-poll requests from internal list.
     * @return Removed long-poll requests.
     */
    protected ArrayList<AsyncRequestFromLongpoll> removeExpiredLongpolls() {
        ArrayList<AsyncRequestFromLongpoll> ret = lonpollRequests.getAndRemoveExpired();
        return ret;
    }

    /**
     * Remove expired messages.
     * A message is terminated, if it has been waiting more than {@link HConstants#MAX_WAIT_FOR_LONGPOLL_MILLIS}
     * for a long-poll.
     */
    protected ArrayList<PendingMessage> getExpiredMessages() {
        ArrayList<PendingMessage> ret = pendingMessages.getAndRemoveExpired();
        return ret;
    }

    static class AsyncRequestFromLongpoll {
        final long bestBefore;
        //Date dt;
        final BAsyncResult<BMessage> request;
        final BMessageHeader header;

        AsyncRequestFromLongpoll(BAsyncResult<BMessage> request, BMessageHeader header) {
            this.request = request;
            this.header = header;
            long timeout = header.timeoutSeconds != 0 ? (header.timeoutSeconds * 1000)
                    : HConstants.TIMEOUT_LONGPOLL_MILLIS;
            this.bestBefore = System.currentTimeMillis() + timeout;
            //this.dt = new Date(bestBefore);
        }

        boolean isExpired() {
            return bestBefore < System.currentTimeMillis();
        }

        public String toString() {
            return "[" + header + ", expired=" + isExpired() + ", bestBefore=" + new Date(bestBefore) + "]";
        }
    }

    static class PendingMessage {
        long bestBefore;
        BMessage msg;

        PendingMessage(BMessage msg) {
            this.msg = msg;
            this.bestBefore = System.currentTimeMillis() + HConstants.MAX_WAIT_FOR_LONGPOLL_MILLIS;
        }

        boolean isExpired() {
            return bestBefore < System.currentTimeMillis();
        }

        public String toString() {
            return "[" + msg + ", expired=" + isExpired() + ", bestBefore=" + new Date(bestBefore) + "]";
        }
    }

    private final LongPollRequests lonpollRequests = new LongPollRequests();
    private final MapAsyncResults mapAsyncResults = new MapAsyncResults();
    private final PendingMessages pendingMessages = new PendingMessages();
    private final BWire wireServer;
    private final static Log log = LogFactory.getLog(HWireClientR.class);
    private volatile boolean canceled;

    private static class PendingMessages {
        private final ArrayList<PendingMessage> arr = new ArrayList<PendingMessage>();

        public synchronized PendingMessage poll() {
            PendingMessage ret = null;
            if (arr.size() != 0) {
                ret = arr.remove(0);
            }
            return ret;
        }

        public synchronized boolean offer(PendingMessage pendingMessage) {
            arr.add(pendingMessage);
            return true;
        }

        public synchronized ArrayList<PendingMessage> getAndRemoveExpired() {
            ArrayList<PendingMessage> ret = new ArrayList<PendingMessage>();

            boolean foundOne = true;
            while (foundOne) {
                foundOne = false;
                for (Iterator<PendingMessage> it = arr.iterator(); it.hasNext();) {
                    PendingMessage pendingMessage = it.next();
                    if (pendingMessage.isExpired()) {
                        it.remove();
                        ret.add(pendingMessage);
                        foundOne = true;
                        break;
                    }
                }
            }

            return ret;
        }

    }

    private static class MapAsyncResults {
        private final HashMap<Long, BAsyncResult<BMessage>> map = new HashMap<Long, BAsyncResult<BMessage>>();

        public synchronized ArrayList<BAsyncResult<BMessage>> getAndRemoveAll() {
            ArrayList<BAsyncResult<BMessage>> ret = new ArrayList<BAsyncResult<BMessage>>(map.values());
            map.clear();
            return ret;
        }

        public synchronized void put(long messageId, BAsyncResult<BMessage> asyncResult) {
            map.put(messageId, asyncResult);
        }

        public synchronized BAsyncResult<BMessage> remove(long messageId) {
            return map.remove(messageId);
        }

    }

    private static class LongPollRequests {
        private final ArrayList<AsyncRequestFromLongpoll> arr = new ArrayList<AsyncRequestFromLongpoll>();

        public synchronized ArrayList<AsyncRequestFromLongpoll> getAndRemoveAll() {
            ArrayList<AsyncRequestFromLongpoll> ret = new ArrayList<AsyncRequestFromLongpoll>(arr);
            arr.clear();
            return ret;
        }

        public synchronized AsyncRequestFromLongpoll poll() {
            AsyncRequestFromLongpoll lrequest = null;
            if (arr.size() != 0) {
                lrequest = arr.remove(0);
            }
            return lrequest;
        }

        public synchronized int size() {
            return arr.size();
        }

        public synchronized void add(AsyncRequestFromLongpoll asyncRequestFromLongpoll) {
            arr.add(asyncRequestFromLongpoll);
        }

        public synchronized ArrayList<AsyncRequestFromLongpoll> getAndRemoveExpired() {
            ArrayList<AsyncRequestFromLongpoll> ret = new ArrayList<AsyncRequestFromLongpoll>();
            boolean found = true;
            while (found) {
                found = false;
                for (Iterator<AsyncRequestFromLongpoll> it = arr.iterator(); it.hasNext();) {
                    AsyncRequestFromLongpoll lrequest = it.next();
                    if (lrequest.isExpired()) {
                        ret.add(lrequest);
                        it.remove();
                        found = true;
                        break;
                    }
                }
            }
            return ret;
        }
    }
}