Package org.pereni.dina.http

Source Code of org.pereni.dina.http.HttpResponseImpl

package org.pereni.dina.http;


import org.pereni.dina.server.ServerRuntimeException;
import org.pereni.dina.util.MultiMap;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Provides the default implementation of a HttpResponse.
*
* @author Remus Pereni <remus@pereni.org>
*/
public class HttpResponseImpl implements HttpResponse {


    /**
     * Server string
     */
    public static final String SERVER_INFO = "Dina/0.1";

    /**
     * Class logger.
     */
    private static final Logger LOG = Logger.getLogger( HttpResponseImpl.class.getName());

    /**
     * Default response version
     */
    private static final String RESPONSE_VERSION = "HTTP/1.1";


    /**
     * Response content type
     */
    private String contentType = "text/html";

    /**
     * Response status code
     */
    private int statusCode = 200;

    /**
     * Response status message
     */
    private String statusMessage = "OK";

    /**
     * Response content length
     */
    private long contentLength = -1;

    /**
     * Flag monitoring if the response is committed
     */
    private boolean isCommitted = false;

    /**
     * Response headers
     */
    private MultiMap headers = new MultiMap();

    /**
     * Response character encoding
     */
    private String characterEncoding = "UTF-8";

    /**
     * Response output channel
     */
    private WritableByteChannel outputByteChannel;

    /**
     * Temp output for calculating content length.
     */
    private ByteArrayOutputStream tempOutput;

    /**
     * Date format used for sending date headers.
     */
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");

    /**
     * Print writer exposed by the interface
     */
    private PrintWriter outputWriter;

    /**
     * The response servlet context
     */
    private ServletContext servletContext;

    /**
     * Flag indicating if there is a body in the response or the response is header only.
     */
    private boolean isHeaderOnlyResponse = false;


    /**
     * Default constructor
     */
    public HttpResponseImpl() {
        addHeader( Http.Headers.Server, SERVER_INFO);
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    }


    /**
     * Constructor with the output channel already set.
     * @param writableByteChannel
     */
    public HttpResponseImpl(WritableByteChannel writableByteChannel) {
        this();
        this.outputByteChannel = writableByteChannel;
    }


    /**
     *
     * @param outputByteChannel
     */
    public void setOutputChannel(WritableByteChannel outputByteChannel) {
        this.outputByteChannel = outputByteChannel;
    }


    /**
     *
     * @param statusCode
     * @param statusMessage
     * @throws IOException
     */
    @Override
    public void sendError(int statusCode, String statusMessage) throws IOException {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");

        this.contentType = null;
        this.statusCode = statusCode;
        this.statusMessage = statusMessage;
        flushBuffer();
    }


    /**
     *
     * @param statusCode
     * @throws IOException
     */
    @Override
    public void sendError(int statusCode) throws IOException {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");

        this.contentType = null;
        this.statusCode = statusCode;
        this.statusMessage = null;
        flushBuffer();
    }


    /**
     *
     * @param name the name of the header to set
     * @param date the assigned date value
     */
    @Override
    public void setDateHeader(String name, long date) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        headers.set(name,  dateFormat.format( date ));

    }


    /**
     *
     * @param name the name of the header to set
     * @param date the assigned date value
     */
    @Override
    public void addDateHeader(String name, long date) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        headers.put(name,  dateFormat.format( date ));
    }


    /**
     *
     * @param name the name of the header to set
     * @param value the assigned header value
     */
    @Override
    public void setHeader(String name, String value) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        headers.set(name, value);
    }


    /**
     *
     * @param name the name of the header to set
     * @param value the assigned header value
     */
    @Override
    public void addHeader(String name, String value) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        headers.put(name, value);
    }


    /**
     *
     * @param name the name of the header
     * @param value  the assigned integer value
     */
    @Override
    public void setIntHeader(String name, int value) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        headers.set(name, String.valueOf(value));
    }


    /**
     *
     * @param name the name of the header
     * @param value the assigned integer value
     */
    @Override
    public void addIntHeader(String name, int value) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        headers.put(name, String.valueOf(value));
    }


    /**
     *
     * @param statusCode
     */
    @Override
    public void setStatus(int statusCode) {
        if( isCommitted() ) throw new IllegalStateException("The response was already committed");
        this.statusCode = statusCode;

        // NO Content
        if( statusCode == 204 ) {
            setHeaderOnlyResponse(true);
        }
    }


    /**
     *
     * @return
     */
    @Override
    public int getStatus() {
        return statusCode;
    }


    /**
     *
     * @return
     */
    @Override
    public String getCharacterEncoding() {
        return this.characterEncoding;
    }


    /**
     *
     * @return
     */
    @Override
    public String getContentType() {
        return this.contentType;
    }


    /**
     *
     * @return
     * @throws IOException
     */
    @Override
    public PrintWriter getWriter() throws IOException {
        if( outputWriter == null ) {

            // Check to see if we already have the output byte channel
            // if not then we'll use a temp buffer to write what we need into
            // it and when the actual output will be available make sure we
            // copy the content.
            //
            // The reason we need a temp buffer is to capture error codes and
            // messages processed during request processing

            if( getOutputChannel() != null && contentLength != -1 ) {
                isCommitted = true;
                outputWriter = new PrintWriter( Channels.newWriter( outputByteChannel, getCharacterEncoding()));
                sendStatusLine(outputWriter );
                sendHeaders(outputWriter);
                outputWriter.flush();
            } else {
                LOG.log(Level.FINER, "No output byte channel yet or contentLength so working with temp output");
                tempOutput = new ByteArrayOutputStream();
                outputWriter = new PrintWriter( tempOutput );
            }

        }
        return outputWriter;
    }


    /**
     *
     * @param charset charset - a String specifying only the character set defined by IANA
     */
    @Override
    public void setCharacterEncoding(String charset) {
        this.characterEncoding = charset;
    }


    /**
     *
     * @param contentLength
     */
    @Override
    public void setContentLength(long contentLength) {
        this.contentLength = contentLength;
    }


    /**
     *
     * @param contentType
     */
    @Override
    public void setContentType(String contentType ) {
        this.contentType = contentType;
    }


    /**
     *
     * @return
     */
    @Override
    public boolean isCommitted() {
        return this.isCommitted;
    }


    /**
     *
     */
    @Override
    public void flushBuffer() {
        LOG.finer("Flush buffer requested, response is committed " + isCommitted());

        if( outputWriter == null ) {
            try {
                getWriter();
            } catch (IOException e) {
                LOG.log(Level.WARNING, "Unable to create writer", e);
            }
        }
        outputWriter.flush();
    }


    /**
     *
     * @return
     */
    @Override
    public ServletContext getServletContext() {
        return servletContext;
    }


    /**
     *
     * @param servletContext
     */
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }


    /**
     *
     * @param out
     */
    private void sendStatusLine(PrintWriter out) {
        out.print(RESPONSE_VERSION);
        out.print( Http.WS );
        out.print( statusCode );
        if( statusMessage != null ) {
            out.print( Http.WS );
            out.print( statusMessage );
        }
        out.print( Http.EOL );
        LOG.finer("Response sent status line " + RESPONSE_VERSION + " " + statusCode );
    }


    /**
     *
     * @param out
     */
    private void sendHeaders(PrintWriter out){

        if( getContentType() != null ) {
            sendHeader(out, Http.Headers.Content_Type, getContentType());
        }

        Iterator<String> headerKeyIterator = headers.getKeys().iterator();

        String keyName;
        Object keyValue;

        while( headerKeyIterator.hasNext() ){

            keyName = headerKeyIterator.next();
            keyValue = headers.getActualValue( keyName );
            if( keyValue instanceof List) {
                Iterator<String> valueListIterator = ( (List) keyValue).iterator();
                while( valueListIterator.hasNext() ){
                    sendHeader(out, keyName, String.valueOf(valueListIterator.next()));
                }

            }   else {
                sendHeader(out, keyName, String.valueOf(keyValue));
            }
        }

        if( contentLength >= 0 ) {
            sendHeader(out, Http.Headers.Content_Length, String.valueOf(contentLength));
        }

        out.print( Http.EOH );
    }


    /**
     *
     * @return
     */
    @Override
    public WritableByteChannel getOutputChannel() {
        return outputByteChannel;
    }


    /**
     *
     * @return
     */
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append( getStatus() ).append(' ');
        res.append(getContentType()).append(' ');
        res.append(contentLength);
        return res.toString();
    }


    /**
     *
     */
    public void dump() {
        LOG.finer("Response dump " + toString());
    }


    /**
     *
     * @return
     */
    public boolean isHeaderOnlyResponse() {
        return isHeaderOnlyResponse;
    }


    /**
     *
     * @param headerOnlyResponse
     */
    public void setHeaderOnlyResponse(boolean headerOnlyResponse) {
        isHeaderOnlyResponse = headerOnlyResponse;
    }


    /**
     *
     * @param out
     * @param headerName
     * @param headerValue
     */
    private void sendHeader(PrintWriter out, String headerName, String headerValue) {
        out.print( headerName);
        out.print( Http.HEADER_SEPARATOR );
        out.print( headerValue );
        out.print( Http.EOL );
    }


    /**
     *
     */
    private void copyTempOutputToOutputChannel() {

        if( outputByteChannel == null ) {
            LOG.warning("Unable to copy temp content to the output, output byte channel is null, ignoring output");
            return;
        }

        if( outputWriter != null ) outputWriter.flush();


        if( !isCommitted() ) {
            isCommitted = true;
            outputWriter = new PrintWriter( Channels.newWriter( outputByteChannel, getCharacterEncoding()));
            sendStatusLine(outputWriter );
            sendHeaders(outputWriter);
            outputWriter.flush();
        }

        if( !isHeaderOnlyResponse() && tempOutput != null && tempOutput.size() > 0 ) {
            ByteBuffer tempBuffer = ByteBuffer.wrap( tempOutput.toByteArray());
            try {
                this.outputByteChannel.write(tempBuffer);
                LOG.finer("Wrote " + tempOutput.size() + " bytes to output channel");
            } catch (IOException e) {
                throw new ServerRuntimeException("Unable to empty temporary output buffer");
            }
        }
    }


    /**
     *
     */
    public void finalizeResponse() {

        if( isCommitted() ) {
            outputWriter.flush();
            return;
        }

        if( tempOutput != null && contentLength == -1 ) {
            setContentLength(tempOutput.size());
        }

        copyTempOutputToOutputChannel();
    }
}
TOP

Related Classes of org.pereni.dina.http.HttpResponseImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.