byps.http.HHttpServlet.java Source code

Java tutorial

Introduction

Here is the source code for byps.http.HHttpServlet.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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import byps.BApiDescriptor;
import byps.BAsyncResult;
import byps.BBuffer;
import byps.BBufferJson;
import byps.BClient;
import byps.BContentStream;
import byps.BException;
import byps.BExceptionC;
import byps.BHashMap;
import byps.BMessage;
import byps.BMessageHeader;
import byps.BProtocol;
import byps.BServer;
import byps.BServerRegistry;
import byps.BSyncResult;
import byps.BTargetId;
import byps.BTransport;
import byps.BWire;
import byps.RemoteException;
import byps.ureq.BRegistry_BUtilityRequests;
import byps.ureq.BSkeleton_BUtilityRequests;
import byps.ureq.JRegistry_BUtilityRequests;

public abstract class HHttpServlet extends HttpServlet implements HServerContext {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HHttpServlet() {
        if (log.isDebugEnabled())
            log.debug("BHttpServlet()");
    }

    /**
     * Create a session.
     * 
     * @param hsess
     *          HttpSession
     * @param remoteUser
     *          Request attribute REMOTE_USER.
     * @return HSession object
     * @throws BExeption
     *           with code=BExceptionC.FORBIDDEN if authentication has failed.
     */
    protected abstract HSession createSession(HttpSession hsess, String remoteUser) throws BException;

    /**
     * Return the API descriptor
     * 
     * @return API descriptor
     */
    protected abstract BApiDescriptor getApiDescriptor();

    // /////////////////////////////////////////////////
    // Begin implementation of HServerContext

    /**
     * Get configuration object.
     * 
     * @return Configuration object.
     */
    public abstract HConfig getConfig();

    @Override
    public BServerRegistry getServerRegistry() {
        return serverRegistry_use_getServerRegistry;
    }

    @Override
    public HServerListener getListener() {
        return defaultListener;
    }

    @Override
    public HActiveMessages getActiveMessages() {
        return activeMessages_use_getActiveMessages;
    }

    // End implementation of HServerContext
    // /////////////////////////////////////////////////

    /**
     * This function is called after initialization has finished.
     */
    protected void initializationFinished() {
    }

    protected HTargetIdFactory getTargetIdFactory() {
        return targetIdFact_use_getTargetIdFactory;
    }

    @Override
    public void init() throws ServletException {
        if (log.isDebugEnabled())
            log.debug("init(");
        super.init();

        // Initialize in Background.
        // Tomcat should not hang during startup if initialization takes some time
        // or if it connects other servlets running on the same tomcat.
        ServletConfig servletConfig = getServletConfig();
        (new InitThread(servletConfig)).start();

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

    private void initLogger(HConfig config) {

        String logLevel = config.getValue("bypshttp.log.level", "WARN");
        String logFile = config.getValue("bypshttp.log.file", null);

        if (logFile != null) {
            logFile = logFile.replace('/', File.separatorChar);

            Logger rootLogger = Logger.getRootLogger();
            Appender ap = rootLogger.getAppender("FI");
            if (ap != null) {
                FileAppender fap = (FileAppender) ap;
                fap.setFile(logFile);
                fap.activateOptions();
            }

            if (logLevel.equalsIgnoreCase("DEBUG"))
                rootLogger.setLevel(Level.DEBUG);
            if (logLevel.equalsIgnoreCase("INFO"))
                rootLogger.setLevel(Level.INFO);
            if (logLevel.equalsIgnoreCase("WARN"))
                rootLogger.setLevel(Level.WARN);
            if (logLevel.equalsIgnoreCase("ERROR"))
                rootLogger.setLevel(Level.ERROR);

            if (log.isDebugEnabled())
                log.debug("Logger opened.");
        }

    }

    /**
     * Initialization thread
     */
    private class InitThread extends Thread {
        ServletConfig servletConfig;

        InitThread(ServletConfig servletConfig) {
            super("eloix-binit");
            this.servletConfig = servletConfig;
        }

        public void run() {

            // Configuration entries from web.xml
            try {
                HConfig config = getConfig();
                config.init(servletConfig);

                initLogger(config);

                int serverId = config.getMyServerId();
                targetIdFact_use_getTargetIdFactory = new HTargetIdFactory(serverId);

                serverRegistry_use_getServerRegistry = new HRemoteRegistry(config,
                        targetIdFact_use_getTargetIdFactory) {

                    @Override
                    protected BClient createForwardClientToOtherServer(BTransport transport) throws BException {
                        return HHttpServlet.this.createForwardClientToOtherServer(transport);
                    }

                    @Override
                    protected BApiDescriptor getApiDescriptor() {
                        return HHttpServlet.this.getApiDescriptor();
                    }

                };

                activeMessages_use_getActiveMessages = new HActiveMessages(config.getTempDir());

                cleanupThread = new HCleanupResources(HSessionListener.getAllSessions(), HHttpServlet.this);

                initializationFinished();

                isInitialized.set(true);

            } catch (ServletException e) {
                log.error("Initialization failed.", e);
            }

        }
    }

    @Override
    public void destroy() {
        if (log.isDebugEnabled())
            log.debug("done(");
        if (cleanupThread != null) {
            cleanupThread.done();
        }
        if (serverRegistry_use_getServerRegistry != null) {
            serverRegistry_use_getServerRegistry.done();
        }
        if (log.isDebugEnabled())
            log.debug(")done");
    }

    /**
     * Streams are sent by HTTP PUT.
     * 
     * @see HttpServlet#doPut(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPut(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (log.isDebugEnabled())
            log.debug("doPut(");

        // Wird ein Stream gesendet?
        String messageIdStr = request.getParameter("messageid");
        String streamIdStr = request.getParameter("streamid");

        if (log.isDebugEnabled())
            log.debug("messageId=" + messageIdStr + ", streamId=" + streamIdStr);

        if (messageIdStr != null && messageIdStr.length() != 0 && streamIdStr != null
                && streamIdStr.length() != 0) {

            final long messageId = BBufferJson.parseLong(messageIdStr);
            final long streamId = BBufferJson.parseLong(streamIdStr);

            doPutStream(messageId, streamId, request, response);
        } else {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }

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

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (log.isDebugEnabled())
            log.debug("doPost(");

        if (request.getParameter("uploadHandler") != null) {
            doHtmlUpload(request, response);
        } else {
            doPostMessage(request, response);
        }
        if (log.isDebugEnabled())
            log.debug(")doPost");
    }

    private void sendOutgoingStream(BContentStream is, HttpServletResponse response, HRangeRequest rangeRequest)
            throws IOException {
        if (log.isDebugEnabled())
            log.debug("sendOutgoingStream(stream=" + is);

        OutputStream os = null;
        byte[] buf = new byte[HConstants.DEFAULT_BYTE_BUFFER_SIZE];
        int len = 0;

        try {

            // Evaluate offset, length, etc.
            rangeRequest.evaluateByteRange(is);

            // Position the stream to the given offset
            if (rangeRequest.isValid()) {
                is.position(rangeRequest.getOffset());
            }

            // Before setting headers, read the first bytes to return. 
            // This makes sure that the stream can be read before the response code is set to 200 or 206.
            // Furthermore, if the stream is a BContentStreamWrapper, it calls ensureStream() which initializes the stream properties (contentLength, ....)
            len = is.read(buf);

            // Always return Content-Type and Content-Disposition 
            final String contentType = is.getContentType();
            response.setContentType(contentType);

            final String contentDisposition = is.getContentDisposition();
            if (contentDisposition != null && contentDisposition.length() != 0) {
                response.setHeader("Content-Disposition", contentDisposition);
            }

            // Set headers Accept-Ranges, Content-Range, ETAG, Content-Length,...
            rangeRequest.setResponseHeaders(response);

            // Copy bytes to socket -------------
            long bytesWritten = 0;
            os = response.getOutputStream();
            while (len != -1) {

                // Browsers use to send a "Range: 123-" header only with an start offset but without an end offset. 
                // If the user e.g. changes the play position of a video, the browser resets the socket and 
                // requests the stream with another "Range: 234-" header again. 
                // Resetting the socket causes at this point (os.write) a 
                // "ClientAbortException:  java.net.SocketException: Connection reset by peer: socket write error"
                os.write(buf, 0, len);

                bytesWritten += len;

                int bytesToRead = buf.length;
                if (rangeRequest.getLength() != -1) {
                    bytesToRead = (int) Math.min(rangeRequest.getLength() - bytesWritten, buf.length);
                }

                if (bytesToRead == 0)
                    break;
                len = is.read(buf, 0, bytesToRead);
            }

        } catch (BException e) {
            log.debug("Read stream failed", e);
            throw e;
        } catch (IOException e) {
            log.debug("Read stream failed", e);
            throw e;
        } catch (Throwable e) {
            log.debug("Read stream failed", e);
            throw new IOException("Read stream failed.", e);
        } finally {

            if (rangeRequest.isValid()) {
                // Keep the stream open until BContentStream.isExpired 
            } else {
                if (log.isDebugEnabled())
                    log.debug("close response of outgoing stream, stream=" + is);
                if (is != null)
                    try {
                        is.close();
                    } catch (IOException ignored) {
                    }
            }

            if (os != null)
                try {
                    os.close();
                } catch (IOException ignored) {
                }
        }

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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (log.isDebugEnabled())
            log.debug("doGet(");

        // Test adapter function?
        final String testAdapterStr = request.getParameter(HTestAdapter.KEY_PARAM);
        if (testAdapterStr != null && testAdapterStr.length() != 0) {
            doTestAdapter(request, response);
            if (log.isDebugEnabled())
                log.debug(")doGet");
            return;
        }

        // Negotiate?
        final String negoStr = request.getParameter("negotiate");
        if (log.isDebugEnabled())
            log.debug("negotiate=" + negoStr);
        if (negoStr != null && negoStr.length() != 0) {
            ByteBuffer ibuf = ByteBuffer.allocate(negoStr.length() * 3);
            ibuf.put(negoStr.getBytes("UTF-8"));
            ibuf.flip();
            doNegotiate(request, response, ibuf);
            return;
        }

        // Get stream or old utility request

        // Parameter messageid
        final String messageIdStr = request.getParameter("messageid");
        if (log.isDebugEnabled())
            log.debug("messageId=" + messageIdStr);
        if (messageIdStr == null || messageIdStr.length() == 0) {
            // response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getWriter().println("HHttpServlet running.");
            return;
        }

        // Paremter streamid is set, if a stream is to be read
        final String streamIdStr = request.getParameter("streamid");
        if (log.isDebugEnabled())
            log.debug("streamId=" + streamIdStr);

        // Parameter cancel is set, if the message given by messageid must be
        // canceled. For newer clients, this functionality is replaced by the
        // UtilityRequest interface.
        // To support older clients, it is still handled here.
        final String cancelStr = request.getParameter("cancel");
        if (log.isDebugEnabled())
            log.debug("cancel=" + cancelStr);
        if (cancelStr != null && cancelStr.length() != 0) {

            final HSession sess = getSessionFromMessageHeaderOrHttpRequest(null, request);
            if (sess != null) {

                long messageId = BBufferJson.parseLong(messageIdStr);

                if (messageId == HWireClient.MESSAGEID_CANCEL_ALL_REQUESTS) {
                    if (log.isDebugEnabled())
                        log.debug("activeMessages.cleanup");
                    sess.wireServer.cancelAllMessages();
                } else if (messageId == HWireClient.MESSAGEID_DISCONNECT) {
                    if (log.isDebugEnabled())
                        log.debug("sess.done");
                    sess.done();
                } else {
                    if (log.isDebugEnabled())
                        log.debug("activeMessages.cancelMessage");
                    sess.wireServer.cancelMessage(messageId);
                }

            }

            response.setStatus(HttpServletResponse.SC_OK);
            response.getOutputStream().close();

        }

        // Read a stream
        else if (streamIdStr != null && streamIdStr.length() != 0) {
            if (log.isDebugEnabled())
                log.debug("sendOutgoingStream");

            final String serverIdStr = request.getParameter("serverid");

            // Byte-Range request?
            // http://stackoverflow.com/questions/8293687/sample-http-range-request-session
            // Range: bytes=0-
            // Range: bytes=64312833-64657026

            final BContentStream stream = doGetStream(serverIdStr, messageIdStr, streamIdStr);

            HRangeRequest rangeRequest = new HRangeRequest(request);

            sendOutgoingStream(stream, response, rangeRequest);
        }

        // Bad request
        else {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getOutputStream().close();
        }

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

    private BContentStream doGetStream(final String serverIdStr, final String messageIdStr,
            final String streamIdStr) throws RemoteException, IOException {
        final long messageId = BBufferJson.parseLong(messageIdStr);
        final long streamId = BBufferJson.parseLong(streamIdStr);
        final int serverId = serverIdStr != null && serverIdStr.length() != 0 ? Integer.valueOf(serverIdStr)
                : getConfig().getMyServerId();

        final BServerRegistry serverRegistry = getServerRegistry();
        final BTargetId targetIdEncr = new BTargetId(serverId, messageId, streamId);
        final BTargetId targetId = serverRegistry.encryptTargetId(targetIdEncr, false);

        final BClient forwardClient = serverId != 0 ? serverRegistry.getForwardClientIfForeignTargetId(targetId)
                : null;

        final BContentStream stream = forwardClient != null
                ? forwardClient.getTransport().getWire().getStream(targetId)
                : getActiveMessages().getOutgoingStream(targetId);

        // Session verlngern. Wichtig fr den Download sehr groer Dateien.
        final HActiveMessage msg = getActiveMessages().getActiveMessage(messageId);
        final String sessionId = msg != null ? msg.getSessionId() : null;
        final HSession sess = sessionId != null ? HSessionListener.getAllSessions().get(sessionId) : null;
        if (sess != null) {
            sess.touch();
        }
        return stream;
    }

    protected void doPostMessage(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (log.isDebugEnabled())
            log.debug("doPostMessage(");

        if (log.isDebugEnabled())
            log.debug("read message");
        String contentType = request.getContentType();
        String contentLength = request.getHeader("Content-Length");
        if (log.isDebugEnabled())
            log.debug("contentType=" + contentType + ", contentLength=" + contentLength);

        InputStream is = request.getInputStream();
        ByteBuffer ibuf = BWire.bufferFromStream(is);

        if (log.isDebugEnabled()) {
            log.debug(BBuffer.toDetailString(ibuf));
        }

        doMessage(request, response, ibuf);

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

    protected HSession getSessionFromMessageHeaderOrHttpRequest(BMessageHeader header, HttpServletRequest request) {
        if (log.isDebugEnabled())
            log.debug("getSessionFromMessageHeaderOrHttpRequest(");
        final BHashMap<String, HSession> sessions = HSessionListener.getAllSessions();
        HSession sess = null;
        HttpSession hsess = request.getSession();

        if (log.isDebugEnabled())
            log.debug("header=" + header + ", request.session=" + hsess);

        // New client: sessionId found in message header.
        if (header != null) {
            if (log.isDebugEnabled())
                log.debug("header.sessionId=" + header.sessionId);
            sess = sessions.get(header.sessionId);
            if (log.isDebugEnabled())
                log.debug("sess from header, session=" + sess);
        }

        // Old client: sessionId found in HTTP cookie.
        if (sess == null) {
            if (hsess != null) {
                sess = getFirstBypsSessionFromHttpSession(hsess);
                if (log.isDebugEnabled())
                    log.debug("sess from cookie, session=" + sess);
            }
        }

        if (sess != null) {
            if (log.isDebugEnabled())
                log.debug("sess expired=" + sess.isExpired());
            if (sess.isExpired()) {
                sess.done();
                sess = null;
            }
        }

        if (sess != null) {
            sess.touch();
        }

        return sess;
    }

    protected void doMessage(final HttpServletRequest request, final HttpServletResponse response,
            final ByteBuffer ibuf) throws IOException {
        if (log.isDebugEnabled())
            log.debug("doMessage(");

        HRequestContext rctxt = null;

        try {
            // NDC.push(hsess.getId());

            if (log.isDebugEnabled())
                log.debug("read header");
            final BMessageHeader header = new BMessageHeader();
            header.read(ibuf);

            // Get HSession.
            // Newer clients: header.sessionId
            // Older clients: request.getSession()
            final HSession sess = getSessionFromMessageHeaderOrHttpRequest(header, request);
            if (sess == null) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }

            final boolean isClientR = (header.flags & BMessageHeader.FLAG_LONGPOLL) != 0;

            if (log.isDebugEnabled())
                log.debug("longpoll=" + isClientR);

            final BMessage msg = new BMessage(header, ibuf, null);

            if (isClientR) {
                rctxt = createRequestContext(request, response, true);

                long timeout = (header.flags & BMessageHeader.FLAG_TIMEOUT) != 0 ? (header.timeoutSeconds * 1000)
                        : HConstants.TIMEOUT_LONGPOLL_MILLIS;
                if (log.isDebugEnabled())
                    log.debug("set timeout=" + timeout);
                rctxt.setTimeout(timeout);

                final BAsyncResult<BMessage> asyncResponse = sess.wireServer.addMessage(header, rctxt, null);

                // Message already canceled?
                if (asyncResponse != null) {
                    sess.wireClientR.recvLongPoll(msg, asyncResponse);
                }

            } else {
                final HRequestContext rctxt2 = rctxt = createRequestContext(request, response,
                        HConstants.PROCESS_MESSAGE_ASYNC);

                final Runnable run = new Runnable() {
                    public void run() {

                        final BAsyncResult<BMessage> asyncResponse = sess.wireServer.addMessage(header, rctxt2,
                                Thread.currentThread());

                        // Message already canceled?
                        if (asyncResponse == null)
                            return;

                        try {
                            if (HConstants.PROCESS_MESSAGE_ASYNC) {
                                // NDC.push(hsess.getId());
                            }

                            // ---------- execute Message ------------------
                            final BServer server = sess.getServer();
                            final BTransport transport = server.getTransport();

                            if (HConstants.PROCESS_MESSAGE_ASYNC) {
                                transport.recv(server, msg, asyncResponse);
                            } else {

                                final BSyncResult<BMessage> syncResponse = new BSyncResult<BMessage>();

                                transport.recv(server, msg, syncResponse);

                                if (log.isDebugEnabled())
                                    log.debug("wait for result");
                                try {
                                    BMessage omsg = syncResponse.getResult(HConstants.REQUEST_TIMEOUT_MILLIS);
                                    if (log.isDebugEnabled())
                                        log.debug("received result=" + omsg);
                                    asyncResponse.setAsyncResult(omsg, null);
                                } catch (Throwable e) {
                                    if (log.isDebugEnabled())
                                        log.debug("Failed to execute.", e);
                                    asyncResponse.setAsyncResult(null, e);
                                }
                            }

                        } catch (Throwable e) {
                            if (log.isDebugEnabled())
                                log.debug("Failed to execute.", e);
                            asyncResponse.setAsyncResult(null, e);
                        } finally {

                            getActiveMessages().removeWorker(header.messageId);

                            if (HConstants.PROCESS_MESSAGE_ASYNC) {
                                // NDC.pop();
                            }
                        }

                    }
                };

                // Set request timeout
                long timeout = (header.flags & BMessageHeader.FLAG_TIMEOUT) != 0 ? (header.timeoutSeconds * 1000)
                        : HConstants.REQUEST_TIMEOUT_MILLIS;
                if (log.isDebugEnabled())
                    log.debug("set timeout=" + timeout);
                rctxt.setTimeout(timeout);

                // Start request
                if (log.isDebugEnabled())
                    log.debug("start sync/async");
                rctxt.start(run);
            }

        } catch (Throwable e) {
            if (log.isInfoEnabled())
                log.info("Failed to process message.", e);
            HttpServletResponse resp = rctxt != null ? (HttpServletResponse) rctxt.getResponse() : response;
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            resp.getWriter().print(e.toString());
            resp.getWriter().close();
            if (rctxt != null) {
                rctxt.complete();
            }
        } finally {
            // NDC.pop();
        }

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

    protected void doNegotiate(final HttpServletRequest request, final HttpServletResponse response,
            final ByteBuffer ibuf) throws ServletException, BException {
        if (log.isDebugEnabled())
            log.debug("doNegotiate(");

        // Initialization finished?
        if (!isInitialized.get()) {
            int httpStatus = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
            if (log.isInfoEnabled())
                log.info("HHttpServlet not initialized. Return " + httpStatus);
            response.setStatus(httpStatus);
            return;
        }

        final HSession sess = doCreateSession(request);

        // Process Negotiate message

        final HRequestContext rctxt = createRequestContext(request, response, HConstants.PROCESS_MESSAGE_ASYNC);

        final BAsyncResult<ByteBuffer> asyncResponse = new BAsyncResult<ByteBuffer>() {

            @Override
            public void setAsyncResult(ByteBuffer obuf, Throwable e) {
                if (log.isDebugEnabled())
                    log.debug("setAsyncResult(");
                final HttpServletResponse resp = (HttpServletResponse) rctxt.getResponse();
                try {
                    if (e != null) {
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                        e.printStackTrace(resp.getWriter());
                    } else {
                        resp.setContentType("application/json");
                        OutputStream os = resp.getOutputStream();
                        BWire.bufferToStream(obuf, false, os);
                    }

                } catch (Throwable ex) {
                    if (log.isInfoEnabled())
                        log.info("Failed to write negotiate result", e);
                    try {
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                        e.printStackTrace(resp.getWriter());
                    } catch (IOException ignored) {
                    }
                } finally {
                    rctxt.complete();
                }
                if (log.isDebugEnabled())
                    log.debug(")setAsyncResult");
            }

        };

        final Runnable run = new Runnable() {
            public void run() {
                if (log.isDebugEnabled())
                    log.debug("run(");
                try {
                    BServer server = sess.getServer();

                    BProtocol protocol = server.negotiate(server.getTransport().getTargetId(), ibuf, asyncResponse);

                    // Teile dem Reverse-Client das ausgehandelte Protokoll mit.
                    // Server und ClientR mssen dasselbe Protokoll verwenden, andernfalls
                    // bruchten sie separate Sessions

                    BClient clientR = sess.getClientR();
                    if (clientR != null) {
                        if (log.isDebugEnabled())
                            log.debug("clientR=" + clientR + ", set negotiated protocol=" + protocol);
                        clientR.getTransport().setProtocol(protocol);
                    }

                } catch (Throwable e) {
                    asyncResponse.setAsyncResult(null, e);
                }
                if (log.isDebugEnabled())
                    log.debug(")run");
            }
        };

        rctxt.start(run);
        if (log.isDebugEnabled())
            log.debug(")doNegotiate");
    }

    protected HSession doCreateSession(final HttpServletRequest request) throws BException {
        // Create new JSESSIONID to support load balancing.
        // For newer clients, we do not rely on the JSESSIONID to identify the BYPS
        // session in incoming requests.
        // Otherwise two JSON connections in a browser window could not be
        // distinguished.
        // Older clients still need to reach their HSession by the JSESSIONID.
        HttpSession hsess = request.getSession(true);
        if (log.isDebugEnabled())
            log.debug("JSESSIONID=" + hsess.getId());

        // Assign a set of BYPS session objects to the app server's session.
        hsess.setAttribute(HConstants.HTTP_SESSION_BYPS_SESSIONS, new HHttpSessionObject());

        // Constrain the lifetime of the session to 10s. It is extended, if the
        // session gets authenticated.
        hsess.setMaxInactiveInterval(HConstants.MAX_INACTIVE_SECONDS_BEFORE_AUTHENTICATED);

        // Create new BYPS session
        final HTargetIdFactory targetIdFactory = getTargetIdFactory();
        final BTargetId targetId = targetIdFactory.createTargetId();
        final HSession sess = createSession(hsess, request.getRemoteUser());
        sess.setTargetId(targetId);
        if (log.isDebugEnabled())
            log.debug("targetId=" + targetId);

        // Add session to session map
        final BHashMap<String, HSession> sessions = HSessionListener.getAllSessions();
        final String bsessionId = targetId.toSessionId();
        sessions.put(bsessionId, sess);

        // Add BRemote for utility requests.
        addUtilityRequestsInterface(sess);
        return sess;
    }

    private HRequestContext createRequestContext(HttpServletRequest request, HttpServletResponse response,
            boolean async) {
        final HRequestContext rctxt = async ? new HAsyncContext(request.startAsync(request, response))
                : new HSyncContext(request, response);
        return rctxt;
    }

    private void doPutStream(final long messageId, final long streamId, HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        if (log.isDebugEnabled())
            log.debug("doPutStream(");

        try {
            // NDC.push(hsess.getId());

            if (log.isDebugEnabled())
                log.debug("start async, timeout=" + HConstants.REQUEST_TIMEOUT_MILLIS);

            final HRequestContext rctxt = createRequestContext(request, response,
                    HConstants.PROCESS_PUT_STREAM_ASYNC);
            rctxt.setTimeout(HConstants.REQUEST_TIMEOUT_MILLIS);

            try {
                final BTargetId targetId = new BTargetId(getConfig().getMyServerId(), messageId, streamId);
                final HActiveMessage msg = getActiveMessages().addIncomingStream(targetId, rctxt);

                // Session verlngern. Wichtig fr den Upload sehr groer Dateien.
                final String sessionId = msg.getSessionId();
                final HSession sess = sessionId != null ? HSessionListener.getAllSessions().get(sessionId) : null;
                if (sess != null) {
                    sess.touch();
                }

            } catch (BException e) {
                int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                if (e.code == BExceptionC.CANCELLED) {
                    status = HttpServletResponse.SC_NOT_ACCEPTABLE;
                }
                ((HttpServletResponse) rctxt.getResponse()).setStatus(status);
                rctxt.complete();
            }
        } finally {
            // NDC.pop();
        }

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

    /**
     * This function helps to check the internal state of the server. This
     * function is for testing purposes only. The servlet init-parameter
     * testAdapterEnabled must be true in order to use this function.
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    protected void doTestAdapter(HttpServletRequest request, HttpServletResponse response) throws IOException {

        if (!getConfig().isTestAdapterEnabled()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        final String testAdapter = request.getParameter(HTestAdapter.KEY_PARAM);
        if (testAdapter == null) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        if (log.isDebugEnabled())
            log.debug("check testAdapter=" + testAdapter);

        // Return the active messages.
        if (testAdapter.equals(HTestAdapter.ACTIVE_MESSAGES)) {

            String inclLongPollsStr = request.getParameter(HTestAdapter.INCL_LONG_POLLS);
            boolean inclLongPolls = (inclLongPollsStr != null && inclLongPollsStr.length() != 0)
                    ? Boolean.parseBoolean(inclLongPollsStr)
                    : true;

            List<Long> messageIds = getActiveMessages().getActiveMessageIds(inclLongPolls, Thread.currentThread());
            if (log.isDebugEnabled())
                log.debug("active messageIds=" + messageIds);
            PrintWriter wr = response.getWriter();
            for (Long messageId : messageIds) {
                wr.println(messageId);
            }
            wr.close();
            return;
        }

        // Print a line into the servers log file.
        if (testAdapter.equals(HTestAdapter.PRINT_LOG)) {
            String line = request.getParameter(HTestAdapter.PRINT_LOG_LINE);
            log.info(line);
            return;
        }

        // Return the file names in the temporary directory.
        if (testAdapter.equals(HTestAdapter.TEMP_FILES)) {
            File[] files = getConfig().getTempDir().listFiles();
            PrintWriter wr = response.getWriter();
            for (File file : files) {
                wr.println(file.getName());
            }
            wr.close();
        }

        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);

    }

    protected void doHtmlUpload(HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (log.isDebugEnabled())
            log.debug("doHtmlUpload(");

        try {
            // NDC.push(hsess.getId());

            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (!isMultipart) {
                throw new IllegalStateException("File upload must be sent as multipart/form-data.");
            }

            // Create a factory for disk-based file items
            DiskFileItemFactory factory = new DiskFileItemFactory(HConstants.INCOMING_STREAM_BUFFER,
                    getConfig().getTempDir());

            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload(factory);

            // Set overall request size constraint
            long maxSize = getHtmlUploadMaxSize();
            if (log.isDebugEnabled())
                log.debug("set max upload file size=" + maxSize);
            upload.setSizeMax(maxSize);

            // Parse the request
            @SuppressWarnings("unchecked")
            List<FileItem> items = upload.parseRequest(request);
            if (log.isDebugEnabled())
                log.debug("received #items=" + items.size());

            ArrayList<HFileUploadItem> uploadItems = new ArrayList<HFileUploadItem>();
            for (FileItem item : items) {

                String fieldName = item.getFieldName();
                if (log.isDebugEnabled())
                    log.debug("fieldName=" + fieldName);
                String fileName = item.getName();
                if (log.isDebugEnabled())
                    log.debug("fileName=" + fileName);
                boolean formField = item.isFormField();
                if (log.isDebugEnabled())
                    log.debug("formField=" + formField);
                if (!formField && fileName.length() == 0)
                    continue;
                long streamId = formField ? 0L
                        : (System.currentTimeMillis()
                                ^ ((((long) fileName.hashCode()) << 16L) | (long) System.identityHashCode(this))); // used as pseudo random number

                HFileUploadItem uploadItem = new HFileUploadItem(formField, fieldName, fileName,
                        item.getContentType(), item.getSize(), Long.toString(streamId));
                uploadItems.add(uploadItem);
                if (log.isDebugEnabled())
                    log.debug("uploadItem=" + uploadItem);

                if (item.isFormField())
                    continue;

                final BTargetId targetId = new BTargetId(getConfig().getMyServerId(), 0, streamId);
                getActiveMessages().addIncomingUploadStream(
                        new HFileUploadItemIncomingStream(item, targetId, getConfig().getTempDir()));

            }

            makeHtmlUploadResult(request, response, uploadItems);

        } catch (Throwable e) {
            if (log.isInfoEnabled())
                log.info("Failed to process message.", e);
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getWriter().print(e.toString());
            response.getWriter().close();
        } finally {
            // NDC.pop();
        }

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

    protected void makeHtmlUploadResult(HttpServletRequest request, HttpServletResponse response,
            Collection<HFileUploadItem> items) throws IOException {
        response.setContentType("text/html");
        PrintWriter wr = response.getWriter();
        wr.print("<html><body>[");
        boolean first = true;
        for (HFileUploadItem item : items) {
            if (first)
                first = !first;
            else
                wr.print(",");
            wr.print("\"");
            wr.print(item.streamId);
            wr.print("\"");
        }
        wr.print("]</body></html>");
        response.getWriter().close();
    }

    protected long getHtmlUploadMaxSize() {
        return -1L;
    }

    protected BClient createForwardClientToOtherServer(BTransport transport) throws BException {
        BClient clientR = null;
        BApiDescriptor apiDesc = getApiDescriptor();
        String clientClassName = apiDesc.basePackage + ".BClient_" + apiDesc.name;

        try {
            Class<?> clazz = Class.forName(clientClassName);
            Method m = clazz.getDeclaredMethod("createClientR", BTransport.class);
            clientR = (BClient) m.invoke(null, transport);
        } catch (Throwable e) {
            throw new BException(BExceptionC.INTERNAL, "Failed to create forward client", e);
        }

        return clientR;
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (log.isDebugEnabled())
            log.debug("service(");
        if (log.isDebugEnabled()) {
            log.debug("method=" + request.getMethod());
            log.debug("params= " + request.getParameterMap());
        }

        int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        Throwable ex = null;

        try {

            super.service(request, response);
            status = response.getStatus();

        } catch (Throwable e) {
            ex = e;

            if (!(e instanceof ClientAbortException)) {
                if (e instanceof FileNotFoundException) {
                    status = HttpServletResponse.SC_NOT_FOUND;
                }

                try {
                    response.sendError(status, e.getMessage());
                } catch (Throwable e2) {
                    // java.lang.IllegalStateException: Cannot call sendError() after the
                    // response has been committed
                    log.debug("ex2=" + e2 + " caused by:", e);
                }
            }

        } finally {

            switch (status) {
            case HttpServletResponse.SC_OK:
            case BExceptionC.RESEND_LONG_POLL:
            case BExceptionC.UNAUTHORIZED:
            case BExceptionC.SESSION_CLOSED:
                if (log.isDebugEnabled())
                    log.debug(makeLogRequest(request, status));
                break;
            case BExceptionC.FORBIDDEN:
            case BExceptionC.TIMEOUT:
                if (log.isInfoEnabled())
                    log.info(makeLogRequest(request, status));
                break;
            default:
                if (log.isInfoEnabled())
                    log.info(makeLogRequest(request, status), ex);
                break;
            }
        }

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

    private String makeLogRequest(HttpServletRequest request, int status) {
        StringBuilder sbuf = new StringBuilder();
        sbuf.append("Request: ").append(request.getMethod()).append(" ").append(" ").append(request.getRequestURI())
                .append(" (");
        for (Enumeration<String> en = request.getParameterNames(); en.hasMoreElements();) {
            String paramName = en.nextElement();
            String[] paramValues = request.getParameterValues(paramName);
            sbuf.append(paramName).append("=").append(Arrays.toString(paramValues));
            if (en.hasMoreElements())
                sbuf.append(", ");
        }
        sbuf.append(") = ").append(status);
        return sbuf.toString();
    }

    private void addUtilityRequestsInterface(final HSession sess) {
        BTransport transport = sess.getServer().getTransport();
        BApiDescriptor apiDesc = transport.getApiDesc();
        apiDesc.addRegistry(new BRegistry_BUtilityRequests());
        apiDesc.addRegistry(new JRegistry_BUtilityRequests());

        sess.getServer().addRemote((int) BSkeleton_BUtilityRequests.serialVersionUID,
                new BSkeleton_BUtilityRequests() {

                    @Override
                    public Map<String, String> testAdapter(String functionName, Map<String, String> params)
                            throws RemoteException {
                        if (log.isDebugEnabled())
                            log.debug("testAdapter(" + functionName + ", " + params);

                        Map<String, String> ret = new HashMap<String, String>();

                        if (!getConfig().isTestAdapterEnabled()) {
                            throw new BException(HttpServletResponse.SC_UNAUTHORIZED, "testAdapter failed.");
                        }

                        // Return the active messages.
                        if (functionName.equals(HTestAdapter.ACTIVE_MESSAGES)) {

                            String inclLongPollsStr = params.get(HTestAdapter.INCL_LONG_POLLS);
                            boolean inclLongPolls = inclLongPollsStr != null
                                    ? Boolean.parseBoolean(inclLongPollsStr)
                                    : false;

                            getActiveMessages().cleanup(false);

                            List<Long> messageIds = getActiveMessages().getActiveMessageIds(inclLongPolls,
                                    Thread.currentThread());
                            if (log.isDebugEnabled())
                                log.debug("active messageIds=" + messageIds);
                            StringBuilder wr = new StringBuilder();
                            for (Long messageId : messageIds) {
                                wr.append(messageId).append("\r\n");
                            }
                            ret.put("return", wr.toString());
                        }

                        // Print a line into the servers log file.
                        else if (functionName.equals(HTestAdapter.PRINT_LOG)) {
                            String line = params.get(HTestAdapter.PRINT_LOG_LINE);
                            log.info(line);
                        }

                        // Return the file names in the temporary directory.
                        else if (functionName.equals(HTestAdapter.TEMP_FILES)) {
                            File[] files = getConfig().getTempDir().listFiles();
                            StringBuilder wr = new StringBuilder();
                            for (File file : files) {
                                wr.append(file.getName()).append("\r\n");
                            }
                            ret.put("return", wr.toString());
                        }

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

                    @Override
                    public Map<String, String> execute(String functionName, Map<String, String> params)
                            throws RemoteException {
                        return super.execute(functionName, params);
                    }

                    @Override
                    public void cancelMessage(long messageId) throws RemoteException {
                        if (log.isDebugEnabled())
                            log.debug("cancelMessage(" + messageId);
                        if (messageId == HWireClient.MESSAGEID_CANCEL_ALL_REQUESTS) {
                            if (log.isDebugEnabled())
                                log.debug("activeMessages.cleanup");
                            sess.wireServer.cancelAllMessages();
                        } else if (messageId == HWireClient.MESSAGEID_DISCONNECT) {
                            if (log.isDebugEnabled())
                                log.debug("sess.done");
                            sess.done();
                        } else {
                            if (log.isDebugEnabled())
                                log.debug("activeMessages.cancelMessage");
                            sess.wireServer.cancelMessage(messageId);
                        }
                        if (log.isDebugEnabled())
                            log.debug(")cancelMessage");
                    }
                });
    }

    /**
     * Get first BYPS session from application server's session. Old client
     * applications do not send the session ID in the BMessageHeader. For this
     * clients, the BYPS session is found in the application server's session.
     * 
     * @param hsess
     *          Application server's session.
     * @return BYPS session, or null
     */
    protected synchronized HSession getFirstBypsSessionFromHttpSession(HttpSession hsess) {
        HSession ret = null;
        try {
            HHttpSessionObject sessObj = (HHttpSessionObject) hsess
                    .getAttribute(HConstants.HTTP_SESSION_BYPS_SESSIONS);
            if (sessObj != null) {
                ret = sessObj.getFirstSessionOrNull();
            }
            if (ret == null) {
                hsess.invalidate();
            }
        } catch (IllegalStateException ignored) {
            // HttpSession could be already invalidated.
        }
        return ret;
    }

    private static Log log = LogFactory.getLog(HHttpServlet.class);
    private volatile BServerRegistry serverRegistry_use_getServerRegistry;
    private volatile HTargetIdFactory targetIdFact_use_getTargetIdFactory;
    private volatile HActiveMessages activeMessages_use_getActiveMessages;
    private HCleanupResources cleanupThread;
    private AtomicBoolean isInitialized = new AtomicBoolean();

    private final static HServerListener defaultListener = new HServerListener() {
        @Override
        public boolean onBeforeWriteHttpResponse(ByteBuffer obuf, Throwable e, HttpServletResponse resp,
                boolean isAsync) throws IOException {
            return false;
        }

        @Override
        public void onAfterWriteHttpResponse(int nbOfBytesWritten) {
        }
    };

}