1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 * <result name="success" type="stream">
71 * <param name="contentType">image/jpeg</param>
72 * <param name="inputName">imageStream</param>
73 * <param name="contentDisposition">filename="document.pdf"</param>
74 * <param name="bufferSize">1024</param>
75 * </result>
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
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
193 HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
194
195
196 oResponse.setContentType(conditionalParse(contentType, invocation));
197
198
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
214 if (contentDisposition != null) {
215 oResponse.addHeader("Content-disposition", conditionalParse(contentDisposition, invocation));
216 }
217
218
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
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
236 oOutput.flush();
237 }
238 finally {
239 if (inputStream != null) inputStream.close();
240 if (oOutput != null) oOutput.close();
241 }
242 }
243
244 }