View Javadoc

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