byps.http.HWireClient.java Source code

Java tutorial

Introduction

Here is the source code for byps.http.HWireClient.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.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

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

import byps.BApiDescriptor;
import byps.BAsyncResult;
import byps.BBufferJson;
import byps.BContentStream;
import byps.BContentStreamAsyncCallback;
import byps.BException;
import byps.BExceptionC;
import byps.BHashMap;
import byps.BMessage;
import byps.BMessageHeader;
import byps.BNegotiate;
import byps.BSyncResult;
import byps.BTargetId;
import byps.BTestAdapter;
import byps.BTransport;
import byps.BTransportFactory;
import byps.BWire;
import byps.http.client.HHttpClient;
import byps.http.client.HHttpClientFactory;
import byps.http.client.HHttpRequest;
import byps.http.client.jcnn.JcnnClientFactory;
import byps.ureq.BApiDescriptor_BUtilityRequests;
import byps.ureq.BClient_BUtilityRequests;
import byps.ureq.BRegistry_BUtilityRequests;
import byps.ureq.JRegistry_BUtilityRequests;

/**
 * BWire implementation for HTTP.
 * By default, this class uses HttpURLConnection to communicate with the server.  
 * Set 
 * <code>System.setProperty(HWireClient.SYSTEM_PROPERTY_HTTP_CLIENT_FACTORY, "byps.http.client.asf.AsfClientFactory");</code>
 * in order to send messages by Apache HTTP Components 4.3.2.
 */
public class HWireClient extends BWire {

    /**
     * Full qualified name of the HTTP client factory class. The constructor reads
     * this value by System.getProperty() and instantiates an object in order to
     * create a HTTP client object and other necessary objects. If this system
     * property is not set, the factory
     * {@link byps.http.client.jcnn.JcnnClientFactory} is used.
     * The value of this property is "byps.http.client.factory".
     */
    public final static String SYSTEM_PROPERTY_HTTP_CLIENT_FACTORY = "byps.http.client.factory";

    protected final static long MESSAGEID_CANCEL_ALL_REQUESTS = -1;
    protected final static long MESSAGEID_DISCONNECT = -2;

    protected final String surl;
    protected final static int CHUNK_SIZE = 10 * 1000;
    protected final static int MAX_STREAM_PART_SIZE = 1000 * CHUNK_SIZE; // should
                                                                         // be a
                                                                         // multiple
                                                                         // of
                                                                         // CHUNK_SIZE
    protected final static Log log = LogFactory.getLog(HWireClient.class);
    protected final BHashMap<RequestToCancel, Boolean> openRequestsToCancel = new BHashMap<RequestToCancel, Boolean>();
    // protected final Map<RequestToCancel, Boolean> openRequestsToCancel =
    // Collections.synchronizedMap(new HashMap<RequestToCancel, Boolean>());
    protected final Executor threadPool;
    protected final boolean isMyThreadPool;
    protected final HTestAdapter testAdapter;
    protected volatile boolean isDone;
    protected final HHttpClient httpClient;

    protected Statistics stats = null;

    protected int timeoutSecondsClient;

    private enum ERequestDirection {
        FORWARD, REVERSE
    };

    /**
     * Initializes a new client-side HTTP communication.
     * 
     * @param url
     *          URL to server.
     * @param flags
     *          A combination of BWire.FLAG_* values.
     * @param timeoutSeconds
     *          Read timeout in seconds. A timeout of zero is interpreted as an
     *          infinite timeout.
     * @param threadPool
     *          Optional: A thread pool. If null, a thread pool is internally
     *          created.
     * @see BWire#FLAG_GZIP
     */
    public HWireClient(String url, int flags, int timeoutSeconds, Executor threadPool) {
        super(flags);

        if (log.isDebugEnabled())
            log.debug("HWireClient(" + url + ", flags=" + flags + ", timeoutSeconds=" + timeoutSeconds
                    + ", threadPool=" + threadPool);

        if (url == null || url.length() == 0)
            throw new IllegalStateException("Missing URL");

        this.surl = url;
        this.timeoutSecondsClient = timeoutSeconds;

        this.isMyThreadPool = threadPool == null;
        if (threadPool == null) {
            threadPool = Executors.newCachedThreadPool();
        }
        this.threadPool = threadPool;

        this.testAdapter = new HTestAdapter(this);

        // Create HTTP client object by a HHttpClientFactory. ----------------

        // The class name of the HHttpClientFactory is taken from the
        // System.properties.
        String factName = System.getProperty(SYSTEM_PROPERTY_HTTP_CLIENT_FACTORY);
        if (log.isDebugEnabled())
            log.debug("factory from system.properties[" + SYSTEM_PROPERTY_HTTP_CLIENT_FACTORY + "]=" + factName);
        if (factName == null || factName.length() == 0)
            factName = JcnnClientFactory.class.getName();

        // Create a client factory object
        HHttpClientFactory fact = null;
        try {
            if (log.isDebugEnabled())
                log.debug("instantiate factName=" + factName);
            Class<?> clazz = Class.forName(factName);
            fact = (HHttpClientFactory) clazz.newInstance();
            if (log.isDebugEnabled())
                log.debug("fact=" + fact);
        } catch (Exception e) {
            if (log.isDebugEnabled())
                log.debug("failed to create factory, factName=" + factName, e);
            throw new IllegalStateException(e);
        }

        if (log.isDebugEnabled())
            log.debug("createHttpClient...");
        this.httpClient = fact.createHttpClient(url);
        if (log.isDebugEnabled())
            log.debug("createHttpClient OK, " + this.httpClient);

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

    protected class AsyncResultAfterAllRequests implements BAsyncResult<BMessage> {
        final BAsyncResult<BMessage> innerResult;
        final long messageId;
        int nbOfOutstandingResults;
        BMessage result;
        Throwable ex;

        public AsyncResultAfterAllRequests(long messageId, BAsyncResult<BMessage> asyncResult,
                final int nbOfRequests) {
            this.innerResult = asyncResult;
            this.messageId = messageId;
            this.nbOfOutstandingResults = nbOfRequests;
        }

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

            boolean cancelMessage = false;
            boolean isLastResult = false;
            BMessage innerMsg = null;
            Throwable innerEx = null;

            synchronized (this) {

                isLastResult = --nbOfOutstandingResults == 0;
                if (log.isDebugEnabled())
                    log.debug("isLastResult=" + isLastResult);

                if (ex != null) {
                    cancelMessage = this.ex == null;
                    if (cancelMessage)
                        this.ex = ex;
                    if (log.isDebugEnabled())
                        log.debug("cancelMessage=" + cancelMessage);
                }

                if (msg != null && msg.buf != null) {
                    if (log.isDebugEnabled())
                        log.debug("set result=" + msg);
                    this.result = msg;
                } else {
                    // Stream result OK
                }

                if (isLastResult) {
                    innerMsg = this.result;
                    innerEx = this.ex;
                }

            }

            if (isLastResult) {
                if (log.isDebugEnabled())
                    log.debug("innerResult.setAsyncResult(result=" + innerMsg + ", ex=" + innerEx);
                innerResult.setAsyncResult(innerMsg, innerEx);
            }

            if (cancelMessage && !isLastResult) {
                sendCancelMessage(messageId);
            }

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

    }

    @Override
    public synchronized void send(final BMessage msg, final BAsyncResult<BMessage> asyncResult) {
        internalSendMessageAndStreams(msg, asyncResult);
    }

    @Override
    public void sendR(BMessage msg, BAsyncResult<BMessage> asyncResult) {
        internalSendMessageAndStreams(msg, asyncResult);
    }

    protected void executeRequest(RequestToCancel r) throws BException {
        if (log.isDebugEnabled())
            log.debug("executeRequest(" + r);

        if (isDone) {
            BException e = new BException(BExceptionC.CANCELLED, "HTTP Client already disconnected.");
            throw e;
        }

        for (int retry = 0; retry < 10 && !isDone; retry++) {
            try {
                threadPool.execute(r);
                if (log.isDebugEnabled())
                    log.debug(")executeRequest");
                return;
            } catch (RejectedExecutionException e) {
                if (log.isDebugEnabled())
                    log.debug("rejected execution, thread pool exausted? retry=" + retry);
                int retryAfterMillis = rand.nextInt(100);
                try {
                    Thread.sleep((long) retryAfterMillis);
                } catch (InterruptedException e1) {
                    break;
                }
            }
        }

        BException e = new BException(BExceptionC.TOO_MANY_REQUESTS,
                "Failed to send method or stream because too many requests are currently active.");
        throw e;
    }

    @Override
    public void putStreams(List<BContentStream> streams, BAsyncResult<BMessage> asyncResult) {
        try {
            for (BContentStream stream : streams) {
                RequestToCancel req = createRequestForPutStream(stream, asyncResult);
                executeRequest(req);
            }
        } catch (BException e) {
            asyncResult.setAsyncResult(null, e);
        }
    }

    private synchronized void internalSendMessageAndStreams(final BMessage msg,
            final BAsyncResult<BMessage> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("send(" + msg + ", asyncResult=" + asyncResult);

        try {

            // If the BMessage contains streams, the given asyncResult is wrapped into a
            // BAsyncRequest object that sets the asynchronous result after the
            // message and all streams
            // have been sent.
            BAsyncResult<BMessage> outerResult = asyncResult;
            if (msg.streams != null && msg.streams.size() != 0) {
                if (log.isDebugEnabled())
                    log.debug("wrap asyncResult");
                outerResult = new AsyncResultAfterAllRequests(msg.header.messageId, asyncResult,
                        msg.streams.size() + 1);
            }

            // Eval timeout for message.
            // If the message is sent with streams, it does not return before all streams are sent.
            // If large streams are uploaded, the message could cause a timeout error on the client side. 
            // In order to prevent this situation, set an infinite timeout (=0).
            boolean hasStreams = msg.streams != null && msg.streams.size() != 0;
            int timeoutSecondsRequest = hasStreams ? 0 : this.timeoutSecondsClient;

            // Create RequestToCancel for message
            RequestToCancel req = createRequestForMessage(msg, outerResult, timeoutSecondsRequest);
            executeRequest(req);

            // Create RequestToCancel objects for each stream.
            if (hasStreams) {
                putStreams(msg.streams, outerResult);
            }

        } catch (BException e) {
            asyncResult.setAsyncResult(null, e);
        }

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

    @SuppressWarnings("unused")
    private synchronized void internalSendStreamsThenMessage(final BMessage msg,
            final BAsyncResult<BMessage> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("internalSendStreamsThenMessage(" + msg + ", asyncResult=" + asyncResult);

        // Convert the BMessage into RequestToCancel objects.
        // One RequestToCancel is created for msg.buf.
        // For each stream in msg.streams further RequestToCancel objects are
        // created.

        int nbOfStreams = msg.streams != null ? msg.streams.size() : 0;
        final ArrayList<RequestToCancel> requests = new ArrayList<RequestToCancel>(
                nbOfStreams != 0 ? nbOfStreams : 1);

        // Does the message contains streams?
        if (nbOfStreams != 0) {

            // Send the streams first and then send the message.
            // If the message is sent before the streams,
            // a timeout happens for the message if it takes
            // more than timeoutMillisRequest to send the streams.

            // Create an BAsyncResult that sends the message on setAsyncResult
            BAsyncResult<BMessage> asyncSendMessage = new BAsyncResult<BMessage>() {
                @Override
                public void setAsyncResult(BMessage result, Throwable exception) {
                    if (exception != null) {
                        asyncResult.setAsyncResult(null, exception);
                    } else {
                        // Send the message
                        try {
                            RequestToCancel messageRequest = createRequestForMessage(msg, asyncResult,
                                    timeoutSecondsClient);
                            executeRequest(messageRequest);
                        } catch (BException e) {
                            asyncResult.setAsyncResult(null, e);
                        }
                    }
                }
            };

            // Create requests for each stream
            BAsyncResult<BMessage> outerResult = new AsyncResultAfterAllRequests(msg.header.messageId,
                    asyncSendMessage, nbOfStreams);
            for (BContentStream stream : msg.streams) {
                RequestToCancel streamRequest = createRequestForPutStream(stream, outerResult);
                requests.add(streamRequest);
            }

        } else {
            // Create RequestToCancel for msg.buf
            RequestToCancel req = createRequestForMessage(msg, asyncResult, timeoutSecondsClient);
            requests.add(req);
        }

        // Execute the RequestToCancel objects in the thread pool
        if (log.isDebugEnabled())
            log.debug("put requests into thread pool");
        try {
            for (RequestToCancel r : requests) {
                executeRequest(r);
            }
        } catch (BException e) {
            asyncResult.setAsyncResult(null, e);
        }

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

    protected RequestToCancel createRequestForMessage(BMessage msg, BAsyncResult<BMessage> asyncResult,
            int timeoutSecondsRequest) {
        if (log.isDebugEnabled())
            log.debug("createRequestForMessage(" + msg);
        ByteBuffer requestDataBuffer = msg.buf;

        if (log.isDebugEnabled()) {
            requestDataBuffer.mark();
            BBufferJson bbuf = new BBufferJson(requestDataBuffer);
            log.debug(bbuf.toDetailString());
            requestDataBuffer.reset();
        }

        final RequestToCancel requestToCancel = new RequestToCancel(msg.header.messageId, 0L, 0L, asyncResult);

        final boolean isNegotiate = BNegotiate.isNegotiateMessage(requestDataBuffer);
        final boolean isJson = isNegotiate
                || BMessageHeader.detectProtocol(requestDataBuffer) == BMessageHeader.MAGIC_JSON;
        if (log.isDebugEnabled())
            log.debug("isJson=" + isJson);

        try {
            StringBuilder destUrl = null;

            // Negotiate?
            if (isNegotiate) {

                // Send a GET request and pass the negotiate string as parameter

                String negoStr = new String(requestDataBuffer.array(), requestDataBuffer.position(),
                        requestDataBuffer.limit(), "UTF-8");
                negoStr = URLEncoder.encode(negoStr, "UTF-8");

                String negoServlet = getServletPathForNegotiationAndAuthentication();
                destUrl = getUrlStringBuilder(negoServlet);
                destUrl.append("&negotiate=").append(negoStr);

                // Clear session Cookie
                httpClient.clearHttpSession();
            }

            // Reverse request (long-poll) ?
            else if ((msg.header.flags & BMessageHeader.FLAG_RESPONSE) != 0) {

                String longpollServlet = getServletPathForReverseRequest();
                destUrl = getUrlStringBuilder(longpollServlet);

                timeoutSecondsRequest = 0; // timeout controlled by server, 10min by
                                           // default.
            }

            // Ordinary request
            else {
                destUrl = getUrlStringBuilder("");
            }

            if (log.isDebugEnabled())
                log.debug("open connection, url=" + destUrl);
            final HHttpRequest httpRequest = isNegotiate ? httpClient.get(destUrl.toString(), requestToCancel)
                    : httpClient.post(destUrl.toString(), requestDataBuffer, requestToCancel);

            httpRequest.setTimeouts(timeoutSecondsClient, timeoutSecondsRequest);

            requestToCancel.setHttpRequest(httpRequest);

            addRequest(requestToCancel);
        } catch (Throwable e) {
            if (log.isDebugEnabled())
                log.debug("received Throwable: " + e);
            BException bex = new BException(BExceptionC.IOERROR, "IO error", e);
            asyncResult.setAsyncResult(null, bex);
        }

        if (log.isDebugEnabled())
            log.debug(")createRequestForMessage=" + requestToCancel);
        return requestToCancel;
    }

    protected RequestToCancel createRequestForPutStream(BContentStream stream, BAsyncResult<BMessage> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("createRequestForPutStream(" + stream);

        final long messageId = stream.getTargetId().getMessageId();
        final long streamId = stream.getTargetId().getStreamId();
        StringBuilder destUrl = getUrlStringBuilder("");
        destUrl.append("&messageid=").append(messageId).append("&streamid=").append(streamId);

        final RequestToCancel requestToCancel = new RequestToCancel(messageId, streamId, 0L, asyncResult);
        final HHttpRequest httpRequest = httpClient.putStream(destUrl.toString(), stream, requestToCancel);

        requestToCancel.setHttpRequest(httpRequest);
        addRequest(requestToCancel);

        if (log.isDebugEnabled())
            log.debug(")createRequestForPutStream=" + requestToCancel);
        return requestToCancel;
    }

    protected RequestToCancel createRequestForGetStream(final BTargetId targetId,
            final BAsyncResult<BContentStream> asyncResult) {
        if (log.isDebugEnabled())
            log.debug("createRequestForGetStream(" + targetId);

        final long messageId = targetId.getMessageId();
        final long streamId = targetId.getStreamId();

        StringBuilder destUrl = getUrlStringBuilder("");
        destUrl.append("&serverid=").append(targetId.getServerId()).append("&messageid=").append(messageId)
                .append("&streamid=").append(streamId);

        final RequestToCancel requestToCancel = new RequestToCancel(messageId, streamId, 0L, null);
        final HHttpRequest httpRequest = httpClient.getStream(destUrl.toString(),
                new BAsyncResult<BContentStream>() {
                    public void setAsyncResult(BContentStream stream, Throwable ex) {
                        asyncResult.setAsyncResult(stream, ex);
                        requestToCancel.setAsyncResult(null, ex);
                    }
                });

        requestToCancel.setHttpRequest(httpRequest);
        addRequest(requestToCancel);

        if (log.isDebugEnabled())
            log.debug(")createRequestForGetStream=" + requestToCancel);
        return requestToCancel;
    }

    protected RequestToCancel createRequestForCancelMessage(long messageId) {
        final RequestToCancel requestToCancel = new RequestToCancel(0L, 0L, messageId,
                new BAsyncResult<BMessage>() {
                    public void setAsyncResult(BMessage msg, Throwable ex) {
                    }
                });

        String destUrl = surl + "?messageid=" + messageId + "&cancel=1";
        final HHttpRequest httpRequest = httpClient.get(destUrl, requestToCancel);

        requestToCancel.setHttpRequest(httpRequest);

        addRequest(requestToCancel);
        return requestToCancel;
    }

    protected class RequestToCancel implements Runnable, BAsyncResult<ByteBuffer>, Comparable<RequestToCancel> {

        final ERequestDirection requestDirection;
        final long messageId;
        final long streamId;
        final long cancelMessageId;
        final BAsyncResult<BMessage> asyncResult;
        final AtomicBoolean isOpen = new AtomicBoolean(true);
        final int timeoutSecondsRequest;
        HHttpRequest httpRequest;

        protected RequestToCancel(long messageId, long streamId, long cancelMessageId,
                BAsyncResult<BMessage> asyncResult) {
            this.requestDirection = ERequestDirection.FORWARD;
            this.messageId = messageId;
            this.streamId = streamId;
            this.cancelMessageId = cancelMessageId;
            this.timeoutSecondsRequest = 0;
            this.asyncResult = asyncResult;
        }

        public void setHttpRequest(HHttpRequest httpRequest) {
            this.httpRequest = httpRequest;
        }

        @Override
        public void run() {
            httpRequest.run();
        }

        public void setAsyncResult(ByteBuffer buf, Throwable e) {
            if (log.isDebugEnabled())
                log.debug("setAsyncResult" + this + "(");
            try {
                if (isOpen.getAndSet(false) && asyncResult != null) {
                    if (e == null && buf != null && buf.remaining() != 0) {
                        BMessageHeader header = new BMessageHeader();
                        try {
                            if (BNegotiate.isNegotiateMessage(buf)) {

                                BNegotiate nego = new BNegotiate();
                                nego.read(buf);

                                header.messageObject = nego;
                                header.messageId = messageId;

                                applyNegotiateForUtilityRequests(nego);
                            } else {
                                header.read(buf);
                            }
                            BMessage msg = new BMessage(header, buf, null);
                            asyncResult.setAsyncResult(msg, null);
                        } catch (BException ex) {
                            asyncResult.setAsyncResult(null, ex);
                        }
                    } else {
                        asyncResult.setAsyncResult(null, e);
                    }
                }
            } finally {
                // Remove request from HWireClient's internal map of requests.
                HWireClient.this.removeRequest(this);
            }
            if (log.isDebugEnabled())
                log.debug(")setAsyncResult");
        }

        public void cancel() {
            if (log.isDebugEnabled())
                log.debug("cancel" + this + "(");
            httpRequest.cancel();
            if (log.isDebugEnabled())
                log.debug(")cancel");
        }

        @Override
        public int compareTo(RequestToCancel o) {
            if (messageId < o.messageId)
                return -1;
            if (messageId > o.messageId)
                return 1;
            if (streamId < o.streamId)
                return -1;
            if (streamId > o.streamId)
                return 1;
            if (cancelMessageId < o.cancelMessageId)
                return -1;
            if (cancelMessageId > o.cancelMessageId)
                return 1;
            return 0;
        }

        @Override
        public String toString() {
            StringBuilder sbuf = new StringBuilder();
            sbuf.append("[").append(messageId);
            if (cancelMessageId != 0)
                sbuf.append(",cancelMessageId=").append(cancelMessageId);
            sbuf.append(",httpRequest=").append(System.identityHashCode(httpRequest));
            sbuf.append("]");
            return sbuf.toString();
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + (int) (cancelMessageId ^ (cancelMessageId >>> 32));
            result = prime * result + (int) (messageId ^ (messageId >>> 32));
            result = prime * result + (int) (streamId ^ (streamId >>> 32));
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            RequestToCancel other = (RequestToCancel) obj;
            if (!getOuterType().equals(other.getOuterType()))
                return false;
            if (cancelMessageId != other.cancelMessageId)
                return false;
            if (messageId != other.messageId)
                return false;
            if (streamId != other.streamId)
                return false;
            return true;
        }

        private HWireClient getOuterType() {
            return HWireClient.this;
        }
    }

    protected class MyInputStream extends InputStreamWrapper implements BAsyncResult<BContentStream> {

        // Implementierung ist so kompliziert, damit die HTTP-Verbindung abgebrochen
        // werden kann.
        // Die Exception durch RequestToCancel.cancle() wird nur in openStream
        // ausgewertet. Aber da
        // msste das cancel schon whrend des openStream kommen.

        volatile RequestToCancel request;
        volatile HttpURLConnection conn;
        volatile Throwable ex;

        public MyInputStream(BTargetId targetId) {
            super(targetId);
        }

        @Override
        public void setAsyncResult(BContentStream stream, Throwable e) {
            this.ex = e;
            if (e == null) {
                this.innerStream = stream;
                this.copyProperties(stream);
            }
        }

        void throwExceptionIf() throws IOException {
            Throwable e = ex;
            if (e == null)
                return;
            if (e instanceof IOException) {
                throw (IOException) e;
            } else {
                throw new BException(BExceptionC.CANCELLED, "", e);
            }
        }

        @Override
        public InputStream ensureStream() throws IOException {
            throwExceptionIf();
            return super.ensureStream();
        }

        @Override
        protected InputStream openStream() throws IOException {
            if (log.isDebugEnabled())
                log.debug("openStream(");
            InputStream is = null;

            final BContentStreamAsyncCallback streamCallback = getAsyncCallback();
            if (streamCallback != null) {

                BAsyncResult<BContentStream> asyncResult = new BAsyncResult<BContentStream>() {

                    public void setAsyncResult(BContentStream stream, Throwable ex) {
                        if (ex == null) {
                            try {
                                streamCallback.onReceivedContentType(stream.getContentType());
                                streamCallback.onReceivedContentLength(stream.getContentLength());

                                byte[] buf = new byte[10 * 1000];
                                int len = 0;
                                boolean succ = true;
                                while ((len = stream.read(buf)) != -1) {
                                    if (succ) {
                                        succ = streamCallback.onReceivedData(buf, len);
                                    }
                                }
                            } catch (Throwable e) {
                                streamCallback.onReceivedException(e);
                            }

                        } else {
                            streamCallback.onReceivedException(ex);
                        }

                        streamCallback.onFinished();
                    }
                };

                internalOpenStream(asyncResult);

                // Return empty stream to prevent NPE
                is = new InputStream() {
                    public int read() throws IOException {
                        return -1;
                    }
                };

            } else {

                BSyncResult<BContentStream> syncResult = new BSyncResult<BContentStream>() {
                    public void setAsyncResult(BContentStream stream, Throwable ex) {

                        // Set members this.innerStream, this.contentType,
                        // this.contentLength
                        MyInputStream.this.setAsyncResult(stream, ex);

                        // Release thread which is waiting in syncResult.getResult()
                        super.setAsyncResult(stream, ex);
                    }
                };

                internalOpenStream(syncResult);

                is = syncResult.getResult();
            }

            if (log.isDebugEnabled())
                log.debug(")openStream=" + is);
            return is;
        }

        protected void internalOpenStream(BAsyncResult<BContentStream> asyncResult) throws IOException {
            if (log.isDebugEnabled())
                log.debug("internalOpenStream(");
            RequestToCancel requestToCancel = createRequestForGetStream(targetId, asyncResult);
            executeRequest(requestToCancel);
            if (log.isDebugEnabled())
                log.debug(")internalOpenStream");
        }

        @Override
        public void close() throws IOException {
            super.close();
        }

    }

    @Override
    public BContentStream getStream(BTargetId targetId) throws IOException {
        if (log.isDebugEnabled())
            log.debug("getStream(" + targetId);
        BContentStream is = new MyInputStream(targetId);
        if (log.isDebugEnabled())
            log.debug(")getStream=" + is);
        return is;
    }

    protected void internalCancelAllRequests(long cancelMessageId) {
        if (log.isDebugEnabled())
            log.debug("internalCancelAllRequests(");

        ArrayList<RequestToCancel> arr = new ArrayList<RequestToCancel>(openRequestsToCancel.keys());
        openRequestsToCancel.clear();

        if (log.isDebugEnabled())
            log.debug("cancel requests on client, #requests=" + arr.size());
        for (RequestToCancel robj : arr) {
            robj.cancel();
        }

        // Notify the server about the canceled messages
        if (cancelMessageId != 0) {
            sendCancelMessage(cancelMessageId);
        }

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

    @Override
    public void done() {

        internalCancelAllRequests(MESSAGEID_DISCONNECT);

        isDone = true;

        if (isMyThreadPool) {
            if (threadPool instanceof ExecutorService) {
                ExecutorService tp = (ExecutorService) threadPool;
                tp.shutdown();
            }
        }
    }

    protected void addRequest(RequestToCancel robj) {
        if (log.isDebugEnabled())
            log.debug("addRequest(robj=" + robj);
        openRequestsToCancel.put(robj, Boolean.TRUE);
        if (log.isDebugEnabled())
            log.debug(")addRequest=" + robj);
    }

    protected void removeRequest(RequestToCancel robj) {
        if (log.isDebugEnabled())
            log.debug("removeRequest(" + robj);
        if (robj == null)
            return;
        openRequestsToCancel.remove(robj);
        if (log.isDebugEnabled())
            log.debug(")removeRequest");
    }

    /**
     * Cancel a message on the server side.
     * 
     * @param messageId
     *          Either a message ID or -1, if all messages of the session should
     *          be canceled
     */
    protected void sendCancelMessage(final long messageId) {
        if (log.isDebugEnabled())
            log.debug("sendCancelMessage(messageId=" + messageId);
        try {
            BClient_BUtilityRequests bclientU = clientUtilityRequests;
            if (bclientU != null) {
                bclientU.getBUtilityRequests().cancelMessage(messageId);
            } else {
                RequestToCancel r = createRequestForCancelMessage(messageId);
                executeRequest(r);
            }
        } catch (Exception e) {
            log.debug("Exception", e);
        }
        if (log.isDebugEnabled())
            log.debug(")sendCancelMessage");
    }

    String testAdapter(String fnct, String[] args) {
        if (log.isDebugEnabled())
            log.debug("testAdapter(fnct=" + fnct + ", " + Arrays.toString(args));

        String ret = "";

        if (fnct.equals(HTestAdapter.KILL_CONNECTIONS)) {
            internalCancelAllRequests(0);
            return ret;
        }

        try {
            HashMap<String, String> map = new HashMap<String, String>();
            if (args != null) {
                for (int i = 0; i < args.length; i += 2) {
                    map.put(args[i], i < args.length - 1 ? args[i + 1] : "");
                }
            }

            BClient_BUtilityRequests bclientU = clientUtilityRequests;
            Map<String, String> rmap = bclientU.getBUtilityRequests().testAdapter(fnct, map);
            ret = rmap != null && rmap.size() != 0 ? rmap.entrySet().iterator().next().getValue() : "";
        } catch (IOException ignored) {
            log.debug("ignored=" + ignored);
        }

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

    @Override
    public synchronized Statistics getStatistics() {
        return new Statistics(stats);
    }

    @Override
    public synchronized void clearStatistics() {
        stats = new Statistics();
    }

    @Override
    public BTestAdapter getTestAdapter() {
        return testAdapter;
    }

    /**
     * Gets the servlet path for negotiation and authentication. A request for
     * HTTP authentication should be sent to a different sub directory of the web
     * application. E.g. while http://server/app/byps is the URL for BYPS
     * communication, authentication requests are sent to
     * http://server/app/bypsauth/auth. The reason for this is that some browsers
     * initiate the authentication handshake in each request if the first one has
     * had to be authenticated.
     * In order to support HTTP authentication, this function has to be overridden.
     * The default implementation returns the servlet path.
     * 
     * @return servlet path of the URL that was passed in the constructor, e.g. "/byps". 
     */
    public String getServletPathForNegotiationAndAuthentication() {
        String authUrl = surl;
        int p = authUrl.lastIndexOf('/');
        if (p >= 0) {
            authUrl = authUrl.substring(p);
        }
        return authUrl;
    }

    /**
     * Gets the servlet path for reverse HTTP requests. Reverse requests can be
     * sent to a different sub directory of the web application. This is useful if
     * a load balancer or proxy redirects the requests, because it allows to
     * specify a special timeout for long polls.
     * 
     * @return servlet path of the URL that was passed in the constructor, e.g.
     *         /byps
     */
    public String getServletPathForReverseRequest() {
        String longUrl = surl;
        int p = longUrl.lastIndexOf('/');
        if (p >= 0) {
            longUrl = longUrl.substring(p);
        }
        return longUrl;
    }

    private StringBuilder getUrlStringBuilder(String servletPath) {
        StringBuilder sbuf = new StringBuilder();
        if (servletPath.length() != 0) {
            int p = surl.lastIndexOf("/");
            if (p < 0)
                p = surl.length();
            sbuf.append(surl.substring(0, p));
            sbuf.append(servletPath);
        } else {
            sbuf.append(surl);
        }

        // We always want to start adding new parameters with '&'.
        // That's why somthing has to be added to the url which currently ends with '?'
        sbuf.append("?a=a");

        return sbuf;
    }

    public String toString() {
        return "[url=" + surl + ", #openreq=" + openRequestsToCancel.size() + ", done=" + isDone + "]";
    }

    public HHttpClient getHttpClient() {
        return this.httpClient;
    }

    private volatile BClient_BUtilityRequests clientUtilityRequests;

    public void applyNegotiateForUtilityRequests(BNegotiate nego) throws BException {
        if (nego.sessionId != null && nego.sessionId.length() != 0
                && !nego.sessionId.equals(BTargetId.SESSIONID_ZERO)) {
            if (log.isDebugEnabled())
                log.debug("utility requests supported");
            BApiDescriptor apiDesc = BApiDescriptor_BUtilityRequests.instance();
            apiDesc.addRegistry(new BRegistry_BUtilityRequests());
            apiDesc.addRegistry(new JRegistry_BUtilityRequests());
            final BTransportFactory transportFactory = new HTransportFactoryClient(apiDesc, this, 0);
            BClient_BUtilityRequests bclient = BClient_BUtilityRequests.createClient(transportFactory);
            BTransport utransport = bclient.getTransport();
            utransport.applyNegotiate(nego);
            clientUtilityRequests = bclient;
        }
    }
}