/*
 * $Id: DownloadAction.java 164530 2005-04-25 03:11:07Z niallp $
 *
 * Copyright 2004-2005 The Apache Software Foundation.
 *
 * 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.apache.struts.actions;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;


/**
 * This is an abstract base class that minimizes the amount of special coding
 * that needs to be written to download a file. All that is required to use
 * this class is to extend it and implement the <code>getStreamInfo()</code>
 * method so that it returns the relevant information for the file (or other
 * stream) to be downloaded. Optionally, the <code>getBufferSize()</code>
 * method may be overridden to customize the size of the buffer used to
 * transfer the file.
 *
 * @since Struts 1.2.6
 */
public abstract class DownloadAction extends Action {

    /**
     * If the <code>getBufferSize()</code> method is not overridden, this is
     * the buffer size that will be used to transfer the data to the servlet
     * output stream.
     */
    protected static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    /**
     * Returns the information on the file, or other stream, to be downloaded
     * by this action. This method must be implemented by an extending class.
     *
     * @param mapping  The ActionMapping used to select this instance.
     * @param form     The optional ActionForm bean for this request (if any).
     * @param request  The HTTP request we are processing.
     * @param response The HTTP response we are creating.
     *
     * @return The information for the file to be downloaded.
     *
     * @throws Exception if an exception occurs.
     */
    protected abstract StreamInfo getStreamInfo(ActionMapping mapping,
            ActionForm form, HttpServletRequest request,
            HttpServletResponse response)
            throws Exception;

    /**
     * Returns the size of the buffer to be used in transferring the data to
     * the servlet output stream. This method may be overridden by an extending
     * class in order to customize the buffer size.
     *
     * @return The size of the transfer buffer, in bytes.
     */
    protected int getBufferSize() {
        return DEFAULT_BUFFER_SIZE;
    }

    /**
     * Process the specified HTTP request, and create the corresponding HTTP
     * response (or forward to another web component that will create it).
     * Return an <code>ActionForward</code> instance describing where and how
     * control should be forwarded, or <code>null</code> if the response has
     * already been completed.
     *
     * @param mapping  The ActionMapping used to select this instance.
     * @param form     The optional ActionForm bean for this request (if any).
     * @param request  The HTTP request we are processing.
     * @param response The HTTP response we are creating.
     *
     * @throws Exception if an exception occurs.
     */
    public ActionForward execute(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        StreamInfo info = getStreamInfo(mapping, form, request, response);
        String contentType = info.getContentType();
        InputStream stream = info.getInputStream();

        try {
            response.setContentType(contentType);
            copy(stream, response.getOutputStream());
        } finally {
            if (stream != null) {
                stream.close();
            }
        }

        // Tell Struts that we are done with the response.
        return null;
    }

    /**
     * Copy bytes from an <code>InputStream</code> to an
     * <code>OutputStream</code>.
     *
     * @param input  The <code>InputStream</code> to read from.
     * @param output The <code>OutputStream</code> to write to.
     *
     * @return the number of bytes copied
     *
     * @throws IOException In case of an I/O problem
     */
    public int copy(InputStream input, OutputStream output)
            throws IOException {
        byte[] buffer = new byte[getBufferSize()];
        int count = 0;
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }

    /**
     * The information on a file, or other stream, to be downloaded by the
     * <code>DownloadAction</code>.
     */
    public static interface StreamInfo {

        /**
         * Returns the content type of the stream to be downloaded.
         *
         * @return The content type of the stream.
         */
        public abstract String getContentType();

        /**
         * Returns an input stream on the content to be downloaded. This stream
         * will be closed by the <code>DownloadAction</code>.
         *
         * @return The input stream for the content to be downloaded.
         */
        public abstract InputStream getInputStream() throws IOException;
    }

    /**
     * A concrete implementation of the <code>StreamInfo</code> interface which
     * simplifies the downloading of a file from the disk.
     */
    public static class FileStreamInfo implements StreamInfo {

        /**
         * The content type for this stream.
         */
        private String contentType;

        /**
         * The file to be downloaded.
         */
        private File file;

        /**
         * Constructs an instance of this class, based on the supplied
         * parameters.
         *
         * @param contentType The content type of the file.
         * @param file        The file to be downloaded.
         */
        public FileStreamInfo(String contentType, File file) {
            this.contentType = contentType;
            this.file = file;
        }

        /**
         * Returns the content type of the stream to be downloaded.
         *
         * @return The content type of the stream.
         */
        public String getContentType() {
            return this.contentType;
        }

        /**
         * Returns an input stream on the file to be downloaded. This stream
         * will be closed by the <code>DownloadAction</code>.
         *
         * @return The input stream for the file to be downloaded.
         */
        public InputStream getInputStream() throws IOException {
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fis);
            return bis;
        }
    }

    /**
     * A concrete implementation of the <code>StreamInfo</code> interface which
     * simplifies the downloading of a web application resource.
     */
    public static class ResourceStreamInfo implements StreamInfo {

        /**
         * The content type for this stream.
         */
        private String contentType;

        /**
         * The servlet context for the resource to be downloaded.
         */
        private ServletContext context;

        /**
         * The path to the resource to be downloaded.
         */
        private String path;

        /**
         * Constructs an instance of this class, based on the supplied
         * parameters.
         *
         * @param contentType The content type of the file.
         * @param context     The servlet context for the resource.
         * @param path        The path to the resource to be downloaded.
         */
        public ResourceStreamInfo(String contentType, ServletContext context,
                String path) {
            this.contentType = contentType;
            this.context = context;
            this.path = path;
        }

        /**
         * Returns the content type of the stream to be downloaded.
         *
         * @return The content type of the stream.
         */
        public String getContentType() {
            return this.contentType;
        }

        /**
         * Returns an input stream on the resource to be downloaded. This stream
         * will be closed by the <code>DownloadAction</code>.
         *
         * @return The input stream for the resource to be downloaded.
         */
        public InputStream getInputStream() throws IOException {
            return context.getResourceAsStream(path);
        }
    }
}
