View Javadoc
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 }