1 /* 2 * $Id: $ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 package org.apache.struts.extras.actions; 22 23 import java.io.BufferedInputStream; 24 import java.io.File; 25 import java.io.FileInputStream; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 30 import jakarta.servlet.ServletContext; 31 import jakarta.servlet.http.HttpServletRequest; 32 import jakarta.servlet.http.HttpServletResponse; 33 34 import org.apache.struts.action.ActionForm; 35 import org.apache.struts.action.ActionForward; 36 import org.apache.struts.action.ActionMapping; 37 38 /** 39 * This is an abstract base class that minimizes the amount of special coding 40 * that needs to be written to download a file. All that is required to use 41 * this class is to extend it and implement the <code>getStreamInfo()</code> 42 * method so that it returns the relevant information for the file (or other 43 * stream) to be downloaded. Optionally, the <code>getBufferSize()</code> 44 * method may be overridden to customize the size of the buffer used to 45 * transfer the file. 46 * 47 * @since Struts 1.2.6 48 */ 49 public abstract class DownloadAction extends BaseAction { 50 private static final long serialVersionUID = -4571392707311277980L; 51 52 /** 53 * If the <code>getBufferSize()</code> method is not overridden, this is 54 * the buffer size that will be used to transfer the data to the servlet 55 * output stream. 56 */ 57 protected static final int DEFAULT_BUFFER_SIZE = 4096; 58 59 /** 60 * Returns the information on the file, or other stream, to be downloaded 61 * by this action. This method must be implemented by an extending class. 62 * 63 * @param mapping The ActionMapping used to select this instance. 64 * @param form The optional ActionForm bean for this request (if 65 * any). 66 * @param request The HTTP request we are processing. 67 * @param response The HTTP response we are creating. 68 * @return The information for the file to be downloaded. 69 * @throws Exception if an exception occurs. 70 */ 71 protected abstract StreamInfo getStreamInfo(ActionMapping mapping, 72 ActionForm form, HttpServletRequest request, 73 HttpServletResponse response) 74 throws Exception; 75 76 /** 77 * Returns the size of the buffer to be used in transferring the data to 78 * the servlet output stream. This method may be overridden by an 79 * extending class in order to customize the buffer size. 80 * 81 * @return The size of the transfer buffer, in bytes. 82 */ 83 protected int getBufferSize() { 84 return DEFAULT_BUFFER_SIZE; 85 } 86 87 /** 88 * Process the specified HTTP request, and create the corresponding HTTP 89 * response (or forward to another web component that will create it). 90 * Return an <code>ActionForward</code> instance describing where and how 91 * control should be forwarded, or <code>null</code> if the response has 92 * already been completed. 93 * 94 * @param mapping The ActionMapping used to select this instance. 95 * @param form The optional ActionForm bean for this request (if 96 * any). 97 * @param request The HTTP request we are processing. 98 * @param response The HTTP response we are creating. 99 * @return The forward to which control should be transferred, or 100 * <code>null</code> if the response has been completed. 101 * @throws Exception if an exception occurs. 102 */ 103 public ActionForward execute(ActionMapping mapping, ActionForm form, 104 HttpServletRequest request, HttpServletResponse response) 105 throws Exception { 106 StreamInfo info = getStreamInfo(mapping, form, request, response); 107 String contentType = info.getContentType(); 108 109 try (InputStream in = info.getInputStream()) { 110 response.setContentType(contentType); 111 copy(in, response.getOutputStream()); 112 } 113 114 // Tell Struts that we are done with the response. 115 return null; 116 } 117 118 /** 119 * Copy bytes from an <code>InputStream</code> to an 120 * <code>OutputStream</code>. 121 * 122 * @param input The <code>InputStream</code> to read from. 123 * @param output The <code>OutputStream</code> to write to. 124 * @return the number of bytes copied 125 * @throws IOException In case of an I/O problem 126 */ 127 public int copy(InputStream input, OutputStream output) 128 throws IOException { 129 byte[] buffer = new byte[getBufferSize()]; 130 int count = 0; 131 int n = 0; 132 133 while (-1 != (n = input.read(buffer))) { 134 output.write(buffer, 0, n); 135 count += n; 136 } 137 138 return count; 139 } 140 141 /** 142 * The information on a file, or other stream, to be downloaded by the 143 * <code>DownloadAction</code>. 144 */ 145 public static interface StreamInfo { 146 /** 147 * Returns the content type of the stream to be downloaded. 148 * 149 * @return The content type of the stream. 150 */ 151 String getContentType(); 152 153 /** 154 * Returns an input stream on the content to be downloaded. This 155 * stream will be closed by the <code>DownloadAction</code>. 156 * 157 * @return The input stream for the content to be downloaded. 158 * @throws IOException if an error occurs 159 */ 160 InputStream getInputStream() 161 throws IOException; 162 } 163 164 /** 165 * A concrete implementation of the <code>StreamInfo</code> interface 166 * which simplifies the downloading of a file from the disk. 167 */ 168 public static class FileStreamInfo implements StreamInfo { 169 /** 170 * The content type for this stream. 171 */ 172 private String contentType; 173 174 /** 175 * The file to be downloaded. 176 */ 177 private File file; 178 179 /** 180 * Constructs an instance of this class, based on the supplied 181 * parameters. 182 * 183 * @param contentType The content type of the file. 184 * @param file The file to be downloaded. 185 */ 186 public FileStreamInfo(String contentType, File file) { 187 this.contentType = contentType; 188 this.file = file; 189 } 190 191 /** 192 * Returns the content type of the stream to be downloaded. 193 * 194 * @return The content type of the stream. 195 */ 196 public String getContentType() { 197 return this.contentType; 198 } 199 200 /** 201 * Returns an input stream on the file to be downloaded. This stream 202 * will be closed by the <code>DownloadAction</code>. 203 * 204 * @return The input stream for the file to be downloaded. 205 * @throws IOException if an error occurs 206 */ 207 public InputStream getInputStream() 208 throws IOException { 209 FileInputStream fis = new FileInputStream(file); 210 BufferedInputStream bis = new BufferedInputStream(fis); 211 212 return bis; 213 } 214 } 215 216 /** 217 * A concrete implementation of the <code>StreamInfo</code> interface 218 * which simplifies the downloading of a web application resource. 219 */ 220 public static class ResourceStreamInfo implements StreamInfo { 221 /** 222 * The content type for this stream. 223 */ 224 private String contentType; 225 226 /** 227 * The servlet context for the resource to be downloaded. 228 */ 229 private ServletContext context; 230 231 /** 232 * The path to the resource to be downloaded. 233 */ 234 private String path; 235 236 /** 237 * Constructs an instance of this class, based on the supplied 238 * parameters. 239 * 240 * @param contentType The content type of the file. 241 * @param context The servlet context for the resource. 242 * @param path The path to the resource to be downloaded. 243 */ 244 public ResourceStreamInfo(String contentType, ServletContext context, 245 String path) { 246 this.contentType = contentType; 247 this.context = context; 248 this.path = path; 249 } 250 251 /** 252 * Returns the content type of the stream to be downloaded. 253 * 254 * @return The content type of the stream. 255 */ 256 public String getContentType() { 257 return this.contentType; 258 } 259 260 /** 261 * Returns an input stream on the resource to be downloaded. This 262 * stream will be closed by the <code>DownloadAction</code>. 263 * 264 * @return The input stream for the resource to be downloaded. 265 * @throws IOException if an error occurs 266 */ 267 public InputStream getInputStream() 268 throws IOException { 269 return context.getResourceAsStream(path); 270 } 271 } 272 }