1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts.actions;
22
23 import org.apache.struts.action.ActionForm;
24 import org.apache.struts.action.ActionForward;
25 import org.apache.struts.action.ActionMapping;
26
27 import javax.servlet.ServletContext;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30
31 import java.io.BufferedInputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
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 /**
51 * If the <code>getBufferSize()</code> method is not overridden, this is
52 * the buffer size that will be used to transfer the data to the servlet
53 * output stream.
54 */
55 protected static final int DEFAULT_BUFFER_SIZE = 4096;
56
57 /**
58 * Returns the information on the file, or other stream, to be downloaded
59 * by this action. This method must be implemented by an extending class.
60 *
61 * @param mapping The ActionMapping used to select this instance.
62 * @param form The optional ActionForm bean for this request (if
63 * any).
64 * @param request The HTTP request we are processing.
65 * @param response The HTTP response we are creating.
66 * @return The information for the file to be downloaded.
67 * @throws Exception if an exception occurs.
68 */
69 protected abstract StreamInfo getStreamInfo(ActionMapping mapping,
70 ActionForm form, HttpServletRequest request,
71 HttpServletResponse response)
72 throws Exception;
73
74 /**
75 * Returns the size of the buffer to be used in transferring the data to
76 * the servlet output stream. This method may be overridden by an
77 * extending class in order to customize the buffer size.
78 *
79 * @return The size of the transfer buffer, in bytes.
80 */
81 protected int getBufferSize() {
82 return DEFAULT_BUFFER_SIZE;
83 }
84
85 /**
86 * Process the specified HTTP request, and create the corresponding HTTP
87 * response (or forward to another web component that will create it).
88 * Return an <code>ActionForward</code> instance describing where and how
89 * control should be forwarded, or <code>null</code> if the response has
90 * already been completed.
91 *
92 * @param mapping The ActionMapping used to select this instance.
93 * @param form The optional ActionForm bean for this request (if
94 * any).
95 * @param request The HTTP request we are processing.
96 * @param response The HTTP response we are creating.
97 * @return The forward to which control should be transferred, or
98 * <code>null</code> if the response has been completed.
99 * @throws Exception if an exception occurs.
100 */
101 public ActionForward execute(ActionMapping mapping, ActionForm form,
102 HttpServletRequest request, HttpServletResponse response)
103 throws Exception {
104 StreamInfo info = getStreamInfo(mapping, form, request, response);
105 String contentType = info.getContentType();
106 InputStream stream = info.getInputStream();
107
108 try {
109 response.setContentType(contentType);
110 copy(stream, response.getOutputStream());
111 } finally {
112 if (stream != null) {
113 stream.close();
114 }
115 }
116
117
118 return null;
119 }
120
121 /**
122 * Copy bytes from an <code>InputStream</code> to an
123 * <code>OutputStream</code>.
124 *
125 * @param input The <code>InputStream</code> to read from.
126 * @param output The <code>OutputStream</code> to write to.
127 * @return the number of bytes copied
128 * @throws IOException In case of an I/O problem
129 */
130 public int copy(InputStream input, OutputStream output)
131 throws IOException {
132 byte[] buffer = new byte[getBufferSize()];
133 int count = 0;
134 int n = 0;
135
136 while (-1 != (n = input.read(buffer))) {
137 output.write(buffer, 0, n);
138 count += n;
139 }
140
141 return count;
142 }
143
144 /**
145 * The information on a file, or other stream, to be downloaded by the
146 * <code>DownloadAction</code>.
147 */
148 public static interface StreamInfo {
149 /**
150 * Returns the content type of the stream to be downloaded.
151 *
152 * @return The content type of the stream.
153 */
154 String getContentType();
155
156 /**
157 * Returns an input stream on the content to be downloaded. This
158 * stream will be closed by the <code>DownloadAction</code>.
159 *
160 * @return The input stream for the content to be downloaded.
161 * @throws IOException if an error occurs
162 */
163 InputStream getInputStream()
164 throws IOException;
165 }
166
167 /**
168 * A concrete implementation of the <code>StreamInfo</code> interface
169 * which simplifies the downloading of a file from the disk.
170 */
171 public static class FileStreamInfo implements StreamInfo {
172 /**
173 * The content type for this stream.
174 */
175 private String contentType;
176
177 /**
178 * The file to be downloaded.
179 */
180 private File file;
181
182 /**
183 * Constructs an instance of this class, based on the supplied
184 * parameters.
185 *
186 * @param contentType The content type of the file.
187 * @param file The file to be downloaded.
188 */
189 public FileStreamInfo(String contentType, File file) {
190 this.contentType = contentType;
191 this.file = file;
192 }
193
194 /**
195 * Returns the content type of the stream to be downloaded.
196 *
197 * @return The content type of the stream.
198 */
199 public String getContentType() {
200 return this.contentType;
201 }
202
203 /**
204 * Returns an input stream on the file to be downloaded. This stream
205 * will be closed by the <code>DownloadAction</code>.
206 *
207 * @return The input stream for the file to be downloaded.
208 * @throws IOException if an error occurs
209 */
210 public InputStream getInputStream()
211 throws IOException {
212 FileInputStream fis = new FileInputStream(file);
213 BufferedInputStream bis = new BufferedInputStream(fis);
214
215 return bis;
216 }
217 }
218
219 /**
220 * A concrete implementation of the <code>StreamInfo</code> interface
221 * which simplifies the downloading of a web application resource.
222 */
223 public static class ResourceStreamInfo implements StreamInfo {
224 /**
225 * The content type for this stream.
226 */
227 private String contentType;
228
229 /**
230 * The servlet context for the resource to be downloaded.
231 */
232 private ServletContext context;
233
234 /**
235 * The path to the resource to be downloaded.
236 */
237 private String path;
238
239 /**
240 * Constructs an instance of this class, based on the supplied
241 * parameters.
242 *
243 * @param contentType The content type of the file.
244 * @param context The servlet context for the resource.
245 * @param path The path to the resource to be downloaded.
246 */
247 public ResourceStreamInfo(String contentType, ServletContext context,
248 String path) {
249 this.contentType = contentType;
250 this.context = context;
251 this.path = path;
252 }
253
254 /**
255 * Returns the content type of the stream to be downloaded.
256 *
257 * @return The content type of the stream.
258 */
259 public String getContentType() {
260 return this.contentType;
261 }
262
263 /**
264 * Returns an input stream on the resource to be downloaded. This
265 * stream will be closed by the <code>DownloadAction</code>.
266 *
267 * @return The input stream for the resource to be downloaded.
268 * @throws IOException if an error occurs
269 */
270 public InputStream getInputStream()
271 throws IOException {
272 return context.getResourceAsStream(path);
273 }
274 }
275 }