1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 * <result name="success" type="stream">
68 * <param name="contentType">image/jpeg</param>
69 * <param name="inputName">imageStream</param>
70 * <param name="contentDisposition">filename="document.pdf"</param>
71 * <param name="bufferSize">1024</param>
72 * </result>
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
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
188 HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
189
190
191 oResponse.setContentType(conditionalParse(contentType, invocation));
192
193
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
209 if (contentDisposition != null) {
210 oResponse.addHeader("Content-disposition", conditionalParse(contentDisposition, invocation));
211 }
212
213
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
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
231 oOutput.flush();
232 }
233 finally {
234 if (inputStream != null) inputStream.close();
235 if (oOutput != null) oOutput.close();
236 }
237 }
238
239 }