001: /*
002: * $Id: StreamResult.java 570513 2007-08-28 18:14:00Z jholmes $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.dispatcher;
022:
023: import java.io.InputStream;
024: import java.io.OutputStream;
025:
026: import javax.servlet.http.HttpServletResponse;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import com.opensymphony.xwork2.ActionInvocation;
032:
033: /**
034: * <!-- START SNIPPET: description -->
035: *
036: * A custom Result type for sending raw data (via an InputStream) directly to the
037: * HttpServletResponse. Very useful for allowing users to download content.
038: *
039: * <!-- END SNIPPET: description -->
040: * <p/>
041: * <b>This result type takes the following parameters:</b>
042: *
043: * <!-- START SNIPPET: params -->
044: *
045: * <ul>
046: *
047: * <li><b>contentType</b> - the stream mime-type as sent to the web browser
048: * (default = <code>text/plain</code>).</li>
049: *
050: * <li><b>contentLength</b> - the stream length in bytes (the browser displays a
051: * progress bar).</li>
052: *
053: * <li><b>contentDispostion</b> - the content disposition header value for
054: * specifing the file name (default = <code>inline</code>, values are typically
055: * <i>filename="document.pdf"</i>.</li>
056: *
057: * <li><b>inputName</b> - the name of the InputStream property from the chained
058: * action (default = <code>inputStream</code>).</li>
059: *
060: * <li><b>bufferSize</b> - the size of the buffer to copy from input to output
061: * (default = <code>1024</code>).</li>
062: *
063: * </ul>
064: *
065: * <!-- END SNIPPET: params -->
066: *
067: * <b>Example:</b>
068: *
069: * <pre><!-- START SNIPPET: example -->
070: * <result name="success" type="stream">
071: * <param name="contentType">image/jpeg</param>
072: * <param name="inputName">imageStream</param>
073: * <param name="contentDisposition">filename="document.pdf"</param>
074: * <param name="bufferSize">1024</param>
075: * </result>
076: * <!-- END SNIPPET: example --></pre>
077: *
078: */
079: public class StreamResult extends StrutsResultSupport {
080:
081: private static final long serialVersionUID = -1468409635999059850L;
082:
083: protected static final Log log = LogFactory
084: .getLog(StreamResult.class);
085:
086: public static final String DEFAULT_PARAM = "inputName";
087:
088: protected String contentType = "text/plain";
089: protected String contentLength;
090: protected String contentDisposition = "inline";
091: protected String inputName = "inputStream";
092: protected InputStream inputStream;
093: protected int bufferSize = 1024;
094:
095: public StreamResult() {
096: super ();
097: }
098:
099: public StreamResult(InputStream in) {
100: this .inputStream = in;
101: }
102:
103: /**
104: * @return Returns the bufferSize.
105: */
106: public int getBufferSize() {
107: return (bufferSize);
108: }
109:
110: /**
111: * @param bufferSize The bufferSize to set.
112: */
113: public void setBufferSize(int bufferSize) {
114: this .bufferSize = bufferSize;
115: }
116:
117: /**
118: * @return Returns the contentType.
119: */
120: public String getContentType() {
121: return (contentType);
122: }
123:
124: /**
125: * @param contentType The contentType to set.
126: */
127: public void setContentType(String contentType) {
128: this .contentType = contentType;
129: }
130:
131: /**
132: * @return Returns the contentLength.
133: */
134: public String getContentLength() {
135: return contentLength;
136: }
137:
138: /**
139: * @param contentLength The contentLength to set.
140: */
141: public void setContentLength(String contentLength) {
142: this .contentLength = contentLength;
143: }
144:
145: /**
146: * @return Returns the Content-disposition header value.
147: */
148: public String getContentDisposition() {
149: return contentDisposition;
150: }
151:
152: /**
153: * @param contentDisposition the Content-disposition header value to use.
154: */
155: public void setContentDisposition(String contentDisposition) {
156: this .contentDisposition = contentDisposition;
157: }
158:
159: /**
160: * @return Returns the inputName.
161: */
162: public String getInputName() {
163: return (inputName);
164: }
165:
166: /**
167: * @param inputName The inputName to set.
168: */
169: public void setInputName(String inputName) {
170: this .inputName = inputName;
171: }
172:
173: /**
174: * @see org.apache.struts2.dispatcher.StrutsResultSupport#doExecute(java.lang.String, com.opensymphony.xwork2.ActionInvocation)
175: */
176: protected void doExecute(String finalLocation,
177: ActionInvocation invocation) throws Exception {
178:
179: OutputStream oOutput = null;
180:
181: try {
182: if (inputStream == null) {
183: // Find the inputstream from the invocation variable stack
184: inputStream = (InputStream) invocation
185: .getStack()
186: .findValue(
187: conditionalParse(inputName, invocation));
188: }
189:
190: if (inputStream == null) {
191: String msg = ("Can not find a java.io.InputStream with the name ["
192: + inputName + "] in the invocation stack. " + "Check the <param name=\"inputName\"> tag specified for this action.");
193: log.error(msg);
194: throw new IllegalArgumentException(msg);
195: }
196:
197: // Find the Response in context
198: HttpServletResponse oResponse = (HttpServletResponse) invocation
199: .getInvocationContext().get(HTTP_RESPONSE);
200:
201: // Set the content type
202: oResponse.setContentType(conditionalParse(contentType,
203: invocation));
204:
205: // Set the content length
206: if (contentLength != null) {
207: String _contentLength = conditionalParse(contentLength,
208: invocation);
209: int _contentLengthAsInt = -1;
210: try {
211: _contentLengthAsInt = Integer
212: .parseInt(_contentLength);
213: if (_contentLengthAsInt >= 0) {
214: oResponse.setContentLength(_contentLengthAsInt);
215: }
216: } catch (NumberFormatException e) {
217: log
218: .warn(
219: "failed to recongnize "
220: + _contentLength
221: + " as a number, contentLength header will not be set",
222: e);
223: }
224: }
225:
226: // Set the content-disposition
227: if (contentDisposition != null) {
228: oResponse
229: .addHeader("Content-disposition",
230: conditionalParse(contentDisposition,
231: invocation));
232: }
233:
234: // Get the outputstream
235: oOutput = oResponse.getOutputStream();
236:
237: if (log.isDebugEnabled()) {
238: log.debug("Streaming result [" + inputName + "] type=["
239: + contentType + "] length=[" + contentLength
240: + "] content-disposition=["
241: + contentDisposition + "]");
242: }
243:
244: // Copy input to output
245: log.debug("Streaming to output buffer +++ START +++");
246: byte[] oBuff = new byte[bufferSize];
247: int iSize;
248: while (-1 != (iSize = inputStream.read(oBuff))) {
249: oOutput.write(oBuff, 0, iSize);
250: }
251: log.debug("Streaming to output buffer +++ END +++");
252:
253: // Flush
254: oOutput.flush();
255: } finally {
256: if (inputStream != null)
257: inputStream.close();
258: if (oOutput != null)
259: oOutput.close();
260: }
261: }
262:
263: }
|