View Javadoc

1   /*
2    * $Id: StreamResult.java 651946 2008-04-27 13:41:38Z apetrelli $
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  
22  package org.apache.struts2.dispatcher;
23  
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  
27  import javax.servlet.http.HttpServletResponse;
28  
29  import com.opensymphony.xwork2.ActionInvocation;
30  import com.opensymphony.xwork2.util.logging.Logger;
31  import com.opensymphony.xwork2.util.logging.LoggerFactory;
32  import com.opensymphony.xwork2.util.ValueStack;
33  
34  /***
35   * <!-- START SNIPPET: description -->
36   *
37   * A custom Result type for sending raw data (via an InputStream) directly to the
38   * HttpServletResponse. Very useful for allowing users to download content.
39   *
40   * <!-- END SNIPPET: description -->
41   * <p/>
42   * <b>This result type takes the following parameters:</b>
43   *
44   * <!-- START SNIPPET: params -->
45   *
46   * <ul>
47   *
48   * <li><b>contentType</b> - the stream mime-type as sent to the web browser
49   * (default = <code>text/plain</code>).</li>
50   *
51   * <li><b>contentLength</b> - the stream length in bytes (the browser displays a
52   * progress bar).</li>
53   *
54   * <li><b>contentDisposition</b> - the content disposition header value for
55   * specifing the file name (default = <code>inline</code>, values are typically
56   * <i>filename="document.pdf"</i>.</li>
57   *
58   * <li><b>inputName</b> - the name of the InputStream property from the chained
59   * action (default = <code>inputStream</code>).</li>
60   *
61   * <li><b>bufferSize</b> - the size of the buffer to copy from input to output
62   * (default = <code>1024</code>).</li>
63   *
64   * </ul>
65   *
66   * <p>These parameters can also be set by exposing a similarly named getter method on your Action.  For example, you can
67   * provide <code>getContentType()</code> to override that parameter for the current action.</p>
68   *
69   * <!-- END SNIPPET: params -->
70   *
71   * <b>Example:</b>
72   *
73   * <pre><!-- START SNIPPET: example -->
74   * &lt;result name="success" type="stream"&gt;
75   *   &lt;param name="contentType"&gt;image/jpeg&lt;/param&gt;
76   *   &lt;param name="inputName"&gt;imageStream&lt;/param&gt;
77   *   &lt;param name="contentDisposition"&gt;filename="document.pdf"&lt;/param&gt;
78   *   &lt;param name="bufferSize"&gt;1024&lt;/param&gt;
79   * &lt;/result&gt;
80   * <!-- END SNIPPET: example --></pre>
81   *
82   */
83  public class StreamResult extends StrutsResultSupport {
84  
85      private static final long serialVersionUID = -1468409635999059850L;
86  
87      protected static final Logger LOG = LoggerFactory.getLogger(StreamResult.class);
88  
89      public static final String DEFAULT_PARAM = "inputName";
90  
91      protected String contentType = "text/plain";
92      protected String contentLength;
93      protected String contentDisposition = "inline";
94      protected String inputName = "inputStream";
95      protected InputStream inputStream;
96      protected int bufferSize = 1024;
97  
98      public StreamResult() {
99          super();
100     }
101 
102     public StreamResult(InputStream in) {
103         this.inputStream = in;
104     }
105 
106     /***
107      * @return Returns the bufferSize.
108      */
109     public int getBufferSize() {
110         return (bufferSize);
111     }
112 
113     /***
114      * @param bufferSize The bufferSize to set.
115      */
116     public void setBufferSize(int bufferSize) {
117         this.bufferSize = bufferSize;
118     }
119 
120     /***
121      * @return Returns the contentType.
122      */
123     public String getContentType() {
124         return (contentType);
125     }
126 
127     /***
128      * @param contentType The contentType to set.
129      */
130     public void setContentType(String contentType) {
131         this.contentType = contentType;
132     }
133 
134     /***
135      * @return Returns the contentLength.
136      */
137     public String getContentLength() {
138         return contentLength;
139     }
140 
141     /***
142      * @param contentLength The contentLength to set.
143      */
144     public void setContentLength(String contentLength) {
145         this.contentLength = contentLength;
146     }
147 
148     /***
149      * @return Returns the Content-disposition header value.
150      */
151     public String getContentDisposition() {
152         return contentDisposition;
153     }
154 
155     /***
156      * @param contentDisposition the Content-disposition header value to use.
157      */
158     public void setContentDisposition(String contentDisposition) {
159         this.contentDisposition = contentDisposition;
160     }
161 
162     /***
163      * @return Returns the inputName.
164      */
165     public String getInputName() {
166         return (inputName);
167     }
168 
169     /***
170      * @param inputName The inputName to set.
171      */
172     public void setInputName(String inputName) {
173         this.inputName = inputName;
174     }
175 
176     /***
177      * @see org.apache.struts2.dispatcher.StrutsResultSupport#doExecute(java.lang.String, com.opensymphony.xwork2.ActionInvocation)
178      */
179     protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
180 
181         // Override any parameters using values on the stack
182         resolveParamsFromStack(invocation.getStack());
183 
184         OutputStream oOutput = null;
185 
186         try {
187             if (inputStream == null) {
188                 // Find the inputstream from the invocation variable stack
189                 inputStream = (InputStream) invocation.getStack().findValue(conditionalParse(inputName, invocation));
190             }
191 
192             if (inputStream == null) {
193                 String msg = ("Can not find a java.io.InputStream with the name [" + inputName + "] in the invocation stack. " +
194                     "Check the <param name=\"inputName\"> tag specified for this action.");
195                 LOG.error(msg);
196                 throw new IllegalArgumentException(msg);
197             }
198 
199             // Find the Response in context
200             HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
201 
202             // Set the content type
203             oResponse.setContentType(conditionalParse(contentType, invocation));
204 
205             // Set the content length
206             if (contentLength != null) {
207                 String _contentLength = conditionalParse(contentLength, invocation);
208                 int _contentLengthAsInt = -1;
209                 try {
210                     _contentLengthAsInt = Integer.parseInt(_contentLength);
211                     if (_contentLengthAsInt >= 0) {
212                         oResponse.setContentLength(_contentLengthAsInt);
213                     }
214                 }
215                 catch(NumberFormatException e) {
216                     LOG.warn("failed to recongnize "+_contentLength+" as a number, contentLength header will not be set", e);
217                 }
218             }
219 
220             // Set the content-disposition
221             if (contentDisposition != null) {
222                 oResponse.addHeader("Content-disposition", conditionalParse(contentDisposition, invocation));
223             }
224 
225             // Get the outputstream
226             oOutput = oResponse.getOutputStream();
227 
228             if (LOG.isDebugEnabled()) {
229                 LOG.debug("Streaming result [" + inputName + "] type=[" + contentType + "] length=[" + contentLength +
230                     "] content-disposition=[" + contentDisposition + "]");
231             }
232 
233             // Copy input to output
234             LOG.debug("Streaming to output buffer +++ START +++");
235             byte[] oBuff = new byte[bufferSize];
236             int iSize;
237             while (-1 != (iSize = inputStream.read(oBuff))) {
238                 oOutput.write(oBuff, 0, iSize);
239             }
240             LOG.debug("Streaming to output buffer +++ END +++");
241 
242             // Flush
243             oOutput.flush();
244         }
245         finally {
246             if (inputStream != null) inputStream.close();
247             if (oOutput != null) oOutput.close();
248         }
249     }
250 
251     /***
252      * Tries to lookup the parameters on the stack.  Will override any existing parameters
253      *
254      * @param stack The current value stack
255      */
256     protected void resolveParamsFromStack(ValueStack stack) {
257         String disposition = stack.findString("contentDisposition");
258         if (disposition != null) {
259             setContentDisposition(disposition);
260         }
261 
262         String contentType = stack.findString("contentType");
263         if (contentType != null) {
264             setContentType(contentType);
265         }
266 
267         String inputName = stack.findString("inputName");
268         if (inputName != null) {
269             setInputName(inputName);
270         }
271 
272         String contentLength = stack.findString("contentLength");
273         if (contentLength != null) {
274             setContentLength(contentLength);
275         }
276 
277         Integer bufferSize = (Integer) stack.findValue("bufferSize", Integer.class);
278         if (bufferSize != null) {
279             setBufferSize(bufferSize.intValue());
280         }
281     }
282 
283 }