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 }