/* * $Id: $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * 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 BaseAction { /** * 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 = 4096; /** * 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. * @return The forward to which control should be transferred, or * <code>null</code> if the response has been completed. * @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. */ 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. * @throws IOException if an error occurs */ 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. * @throws IOException if an error occurs */ 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. * @throws IOException if an error occurs */ public InputStream getInputStream() throws IOException { return context.getResourceAsStream(path); } } }