org.tizzit.util.spring.httpinvoker.StreamSupportingHttpInvokerServiceExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.tizzit.util.spring.httpinvoker.StreamSupportingHttpInvokerServiceExporter.java

Source

/**
 * Copyright (c) 2009 Juwi MacMillan Group GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tizzit.util.spring.httpinvoker;

import java.io.BufferedOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationResult;

/**
 * Extends <code>HttpInvokerServiceExporter</code> to allow
 * <code>InputStream</code> parameters to remote service methods and
 * <code>InputStream</code> return values from remote service methods.  See
 * the documentation for
 * <code>StreamSupportingHttpInvokerProxyFactoryBean</code> for important
 * restrictions and usage information.  Also see
 * <code>HttpInvokerServiceExporter</code> for general usage of the exporter
 * facility.
 *
 * @author Andy DePue
 * @since 1.2.3
 * @see StreamSupportingHttpInvokerProxyFactoryBean
 * @see HttpInvokerServiceExporter
 */
public class StreamSupportingHttpInvokerServiceExporter extends HttpInvokerServiceExporter {
    private static final Log log = LogFactory.getLog(StreamSupportingHttpInvokerServiceExporter.class);

    public StreamSupportingHttpInvokerServiceExporter() {
        setRegisterTraceInterceptor(false);
    }

    private boolean emptyInputStreamParameterBeforeReturn = false;

    //
    // METHODS FROM CLASS HttpInvokerServiceExporter
    //

    protected RemoteInvocation readRemoteInvocation(final HttpServletRequest request, final InputStream is)
            throws IOException, ClassNotFoundException {
        final RemoteInvocation ret = super.readRemoteInvocation(request,
                new StreamSupportingHttpInvokerRequestExecutor.CloseShieldedInputStream(is));
        boolean closeIs = true;
        if (ret instanceof StreamSupportingRemoteInvocation) {
            final StreamSupportingRemoteInvocation ssri = (StreamSupportingRemoteInvocation) ret;
            if (ssri.getInputStreamParam() >= 0 && !ssri.isInputStreamParamNull()) {
                ssri.getArguments()[ssri.getInputStreamParam()] = new ParameterInputStream(is);
                closeIs = false;
            }
        }
        if (closeIs) {
            is.close();
        }
        return ret;
    }

    protected void writeRemoteInvocationResult(final HttpServletRequest request, final HttpServletResponse response,
            final RemoteInvocationResult result) throws IOException {
        if (hasStreamResult(result)) {
            response.setContentType(
                    StreamSupportingHttpInvokerRequestExecutor.CONTENT_TYPE_SERIALIZED_OBJECT_WITH_STREAM);
        } else {
            response.setContentType(CONTENT_TYPE_SERIALIZED_OBJECT);
        }

        writeRemoteInvocationResult(request, response, result, response.getOutputStream());
    }

    protected void writeRemoteInvocationResult(final HttpServletRequest request, final HttpServletResponse response,
            final RemoteInvocationResult result, final OutputStream os) throws IOException {
        if (hasStreamResult(result)) {
            final OutputStream decoratedOut = decorateOutputStream(request, response, os);
            response.setHeader("Transfer-Encoding", "chunked");

            try {
                // We want to be able to close the ObjectOutputStream in order to
                // properly flush and clear it out, but we don't want it closing
                // our underlying OutputStream.
                final ObjectOutputStream oos = new ObjectOutputStream(
                        new CloseShieldedOutputStream(new BufferedOutputStream(decoratedOut, 4096)));
                try {
                    doWriteRemoteInvocationResult(result, oos);
                    oos.flush();
                } finally {
                    oos.close();
                }
                doWriteReturnInputStream((StreamSupportingRemoteInvocationResult) result, decoratedOut);
            } finally {
                decoratedOut.close();
            }
        } else {
            super.writeRemoteInvocationResult(request, response, result, os);
        }
    }

    protected RemoteInvocationResult invokeAndCreateResult(final RemoteInvocation invocation,
            final Object targetObject) {
        try {
            final Object value = invoke(invocation, targetObject);
            if (invocation instanceof StreamSupportingRemoteInvocation) {
                final Boolean closedInputStreamParam = getParameterInputStreamClosedFlag(invocation);
                if (value instanceof InputStream) {
                    return new StreamSupportingRemoteInvocationResult((InputStream) value, closedInputStreamParam);
                } else {
                    return new StreamSupportingRemoteInvocationResult(value, closedInputStreamParam);
                }
            } else {
                return new RemoteInvocationResult(value);
            }
        } catch (Throwable ex) {
            if (invocation instanceof StreamSupportingRemoteInvocation) {
                return new StreamSupportingRemoteInvocationResult(ex,
                        getParameterInputStreamClosedFlag(invocation));
            } else {
                if (log.isWarnEnabled()) {
                    log.warn(ex.getCause().getMessage());
                }
                if (log.isDebugEnabled()) {
                    log.debug(ex);
                }
                if (ex.getCause() != null) {
                    return new RemoteInvocationResult(ex.getCause());
                } else {
                    return new RemoteInvocationResult(new Exception(ex.getMessage()));
                }

            }
        } finally {
            final ParameterInputStream pi = getParameterInputStreamFrom(invocation);
            if (pi != null) {
                try {
                    pi.doRealClose(getEmptyInputStreamParameterBeforeReturn());
                } catch (IOException e) {
                    log.warn("Error while attempting to close InputStream parameter for RemoteInvocation '"
                            + invocation + "'", e);
                }
            }
        }
    }

    //
    // HELPER METHODS
    //

    /**
     * See {@link #setEmptyInputStreamParameterBeforeReturn(boolean)}.
     *
     * @return <code>true</code> if any InputStream parameter should be
     *         "emptied" before sending the response to the client.
     * @see #setEmptyInputStreamParameterBeforeReturn(boolean)
     */
    public boolean getEmptyInputStreamParameterBeforeReturn() {
        return this.emptyInputStreamParameterBeforeReturn;
    }

    /**
     * Determines if this servlet should "empty" any InputStream parameter to a
     * service method before returning to the client.  This is provided as a
     * workaround for some servlet containers in order to ensure that if an
     * exception is thrown or the service method returns before the InputStream
     * parameter is read that the client will not block trying to send the
     * remaining InputStream to the server.  This means that in the face of an
     * exception or early return from a method that the client will still finish
     * uploading all of its data before it becomes aware of the situation,
     * taking up unnecessary time and bandwidth.  Because of this, a better
     * solution should be found to this problem in the future.  This property
     * defaults to <code>false</code>.
     *
     * @param emptyInputStreamParameterBeforeReturn
     *
     *
     * @see #getEmptyInputStreamParameterBeforeReturn()
     */
    public void setEmptyInputStreamParameterBeforeReturn(final boolean emptyInputStreamParameterBeforeReturn) {
        this.emptyInputStreamParameterBeforeReturn = emptyInputStreamParameterBeforeReturn;
    }

    protected boolean hasStreamResult(final RemoteInvocationResult result) {
        return result instanceof StreamSupportingRemoteInvocationResult
                && ((StreamSupportingRemoteInvocationResult) result).getHasReturnStream();
    }

    protected void doWriteReturnInputStream(final StreamSupportingRemoteInvocationResult result,
            final OutputStream unbufferedChunkedOut) throws IOException {
        // We use the unbuffered chunked out with a custom buffer for optimum
        // performance - partly because we can't be sure that the returned
        // InputStream is itself buffered.
        final InputStream isResult = result.getServerSideInputStream();
        if (isResult != null) {
            try {
                final byte[] buffer = new byte[4096];
                int read;
                while ((read = isResult.read(buffer)) != -1) {
                    unbufferedChunkedOut.write(buffer, 0, read);
                }
            } finally {
                result.setServerSideInputStream(null);
                isResult.close();
            }
        }
    }

    protected ParameterInputStream getParameterInputStreamFrom(final RemoteInvocation invocation) {
        if (invocation instanceof StreamSupportingRemoteInvocation) {
            final StreamSupportingRemoteInvocation ssri = (StreamSupportingRemoteInvocation) invocation;
            if (ssri.getInputStreamParam() >= 0 && !ssri.isInputStreamParamNull()) {
                return (ParameterInputStream) ssri.getArguments()[ssri.getInputStreamParam()];
            }
        }

        return null;
    }

    protected Boolean getParameterInputStreamClosedFlag(final RemoteInvocation invocation) {
        final ParameterInputStream pi = getParameterInputStreamFrom(invocation);
        if (pi != null) {
            return pi.isClosed() ? Boolean.TRUE : Boolean.FALSE;
        } else {
            return null;
        }
    }

    /**
     * Shields an underlying OutputStream from being closed.
     */
    public static class CloseShieldedOutputStream extends FilterOutputStream {
        public CloseShieldedOutputStream(final OutputStream out) {
            super(out);
        }

        public void close() throws IOException {
            flush();
        }
    }

    /**
     * Tracks if an InputStream parameter is closed by a service method, if
     * any input method threw an exception during operation, and if the
     * service method read the InputStream to the end-of-stream.  Also provides
     * the ability to optionally read an InputStream to end-of-stream if the
     * service method did not.
     */
    public static class ParameterInputStream extends FilterInputStream {
        private boolean fullyRead = false;
        private boolean erroredOut = false;
        private boolean closed = false;

        public ParameterInputStream(final InputStream in) {
            super(in);
        }

        public boolean isFullyRead() {
            return this.fullyRead;
        }

        public boolean isErroredOut() {
            return this.erroredOut;
        }

        public boolean isClosed() {
            return this.closed;
        }

        public void doRealClose(final boolean emptyStream) throws IOException {
            if (!isClosed()) {
                if (log.isDebugEnabled())
                    log.debug(
                            "Service method failed to close InputStream parameter from remote invocation.  Will perform the close anyway.");
            }
            if (!isFullyRead() && emptyStream && !isErroredOut()) {
                final byte[] buf = new byte[4096];
                //noinspection StatementWithEmptyBody
                while (read(buf) != -1)
                    ;
            }

            super.close();
        }

        protected int checkEos(final int read) {
            if (read == -1) {
                this.fullyRead = true;
            }
            return read;
        }

        protected IOException checkException(final IOException ioe) {
            this.erroredOut = true;
            return ioe;
        }

        protected void assertOpen() throws IOException {
            if (this.closed) {
                throw new IOException("Stream closed");
            }
        }

        //
        // METHODS FROM CLASS FilterInputStream
        //

        public int read() throws IOException {
            assertOpen();
            try {
                return checkEos(super.read());
            } catch (IOException e) {
                throw checkException(e);
            }
        }

        public int read(byte b[]) throws IOException {
            assertOpen();
            try {
                return checkEos(super.read(b));
            } catch (IOException e) {
                throw checkException(e);
            }
        }

        public int read(byte b[], int off, int len) throws IOException {
            assertOpen();
            try {
                return checkEos(super.read(b, off, len));
            } catch (IOException e) {
                throw checkException(e);
            }
        }

        public long skip(long n) throws IOException {
            assertOpen();
            try {
                return super.skip(n);
            } catch (IOException e) {
                throw checkException(e);
            }
        }

        public int available() throws IOException {
            assertOpen();
            try {
                return super.available();
            } catch (IOException e) {
                throw checkException(e);
            }
        }

        public void close() throws IOException {
            // Close will happen later.
            this.closed = true;
        }
    }
}