FileDownloadServlet.java

// license-header java merge-point
// Generated by andromda-jsf cartridge (utils\download\FileDownloadServlet.java.vsl) DO NOT EDIT!
package org.andromda.presentation.jsf;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This servlet is used to download files.  It can be used by
 * including it an output link, like the following:
 *
 *  <h:outputLink value="/my-jsf-app-context/fileDownload?action=myControllerBean.myControllerOperation&amp;prompt=true&amp;fileName=exportResultFileName&amp;outputName=exportResult">
 *      <h:outputText value="#{messages['export']}"/>
 *  </h:outputLink>
 *
 *  The following parameters may be specified:
 *  <ul>
 *  <li>fileName:</li> the name of the property on the the action form that has the name of your download file.
 *  <li>outputName:</li> the name of the property on the action form that has the output to download.
 *  <li>contentType:</li> any explicit content type to be used for the download (if not specified, it will be guessed
 *                        from the extension of the specified file).
 *  <li>action:</li> the optional action to execute before downloading is attempted.
 *  </ul>
 */
public class FileDownloadServlet
    extends HttpServlet
{
    private static final Log logger = LogFactory.getLog(FileDownloadServlet.class);

    private static final long serialVersionUID = 1L;

    /**
     * The name of an action on a controller to execute before attempt to render the download.
     */
    private static final String ACTION = "action";

    /**
     * Whether or not to prompt with a "save as" dialog, or just render the download in the browser.
     */
    private static final String PROMPT = "prompt";

    /**
     * The name of the property on the action form that has the name of the file to use when downloading.
     */
    private static final String FILE_NAME = "fileName";

    /**
     * The name of the property on the action form that will contain the output.
     */
    private static final String OUTPUT = "outputName";

    /**
     * The contentType parameter specifies what content type to render the download as
     */
    private static final String CONTENT_TYPE = "contentType";


    private static final int BUFFER_SIZE = 4096;

    /**
     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void doGet(final HttpServletRequest request, final HttpServletResponse response)
        throws ServletException,
            IOException
    {
        try
        {
            final String action = request.getParameter(ACTION);
            final FacesContext context = FacesContextUtils.getFacesContext(request, response);
            if (action != null && action.trim().length() > 0)
            {
                final MethodBinding methodBinding = context.getApplication().createMethodBinding("#{" + action + "}", null);
                methodBinding.invoke(context, null);
            }

            final Object form = context.getApplication().getVariableResolver().resolveVariable(context, "form");
            final Boolean prompt = Boolean.valueOf(request.getParameter(PROMPT));
            final String fileNameProperty = request.getParameter(FILE_NAME);
            final String outputProperty = request.getParameter(OUTPUT);
            if (form != null && outputProperty != null && fileNameProperty.trim().length() > 0)
            {
                final OutputStream stream = response.getOutputStream();

                // - reset the response to clear out any any headers (i.e. so
                //   the user doesn't get "unable to open..." when using IE.)
                response.reset();

                Object output = PropertyUtils.getProperty(form, outputProperty);
                final String fileName = ObjectUtils.toString(PropertyUtils.getProperty(form, fileNameProperty));
                final String contentType = this.getContentType(context, request.getParameter(CONTENT_TYPE), fileName);

                if (prompt.booleanValue() && fileName != null && fileName.trim().length() > 0)
                {
                    response.addHeader(
                        "Content-disposition",
                        "attachment; filename=\"" + fileName + '"');
                }

                // - for IE we need to set the content type, content length and buffer size and
                //   then the flush the response right away because it seems as if there is any lag time
                //   IE just displays a blank page. With mozilla based clients reports display correctly regardless.
                if (contentType != null && contentType.length() > 0)
                {
                    response.setContentType(contentType);
                }
                if (output instanceof String)
                {
                    output = ((String)output).getBytes();
                }
                if (output instanceof byte[])
                {
                    byte[] file = (byte[])output;
                    response.setBufferSize(file.length);
                    response.setContentLength(file.length);
                    response.flushBuffer();
                    stream.write(file);
                }
                else if (output instanceof InputStream)
                {
                    final InputStream report = (InputStream)output;
                    final byte[] buffer = new byte[BUFFER_SIZE];
                    response.setBufferSize(BUFFER_SIZE);
                    response.flushBuffer();
                    for (int ctr = 0; (ctr = report.read(buffer)) > 0;)
                    {
                        stream.write(buffer, 0, ctr);
                    }
                }
                stream.flush();
            }
            if (form != null)
            {
                // - remove the output now that we're done with it (in case its large)
                PropertyUtils.setProperty(form, outputProperty, null);
            }
        }
        catch (Throwable throwable)
        {
            throw new ServletException(throwable);
        }
    }

    /**
     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException,
            IOException
    {
        doGet(request, response);
    }

    /**
     * The name of the attribute that stores the content type.
     */
    public static final String CONTENT_TYPE_ATTRIBUTE = "contentType";

    /**
     * Gets the explicity content type to render the file in.
     *
     * @return Returns the contentType.
     */
    private String getContentType(final FacesContext context, final String contentType, final String fileName)
    {
        String foundContentType = contentType;
        // - if the content type is still null, lets guess
        if (contentType == null || contentType.length() == 0)
        {
            if (fileName != null && fileName.trim().length() > 0)
            {
                int lastDotIndex = fileName.lastIndexOf('.');
                if (lastDotIndex != -1)
                {
                    final String extension = fileName.substring(
                            lastDotIndex,
                            fileName.length());
                    foundContentType = CONTENT_TYPES.getProperty(extension);
                }
            }
        }
        return foundContentType;
    }

    /**
     * Stores the default content types.
     */
    static final Properties CONTENT_TYPES = new Properties();

    /**
     * Load up the default content types
     */
    static
    {
        final String fileName = "contenttypes.properties";
        final InputStream stream = FileDownloadServlet.class.getResourceAsStream(fileName);
        if (stream == null)
        {
            logger.error("Could not load file from '" + fileName + "'");
        }
        else
        {
            try
            {
                CONTENT_TYPES.load(stream);
            }
            catch (final Throwable throwable)
            {
                logger.error(throwable.getMessage(),throwable);
            }
        }
        try
        {
            if (stream != null)
            {
                stream.close();
            }
        }
        catch (final Throwable throwable)
        {
            // - ignore
        }
    }
}