View Javadoc

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