View Javadoc

    1   /*
    2    * $Id: StreamResult.java 495228 2007-01-11 13:57:55Z ddewolf $
    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 send 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 }