All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.stripes.action.StreamingResolution Maven / Gradle / Ivy

/* Copyright 2005-2006 Tim Fennell
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.sourceforge.stripes.action;

import net.sourceforge.stripes.exception.StripesRuntimeException;
import net.sourceforge.stripes.util.Log;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 

Resolution for streaming data back to the client (in place of forwarding the user to * another page). Designed to be used for streaming non-page data such as generated images/charts * and XML islands.

* *

Optionally supports the use of a file name which, if set, will cause a * Content-Disposition header to be written to the output, resulting in a "Save As" type * dialog box appearing in the user's browser. If you do not wish to supply a file name, but * wish to achieve this behaviour, simple supply a file name of "".

* *

StreamingResolution is designed to be subclassed where necessary to provide streaming * output where the data being streamed is not contained in an InputStream or Reader. This * would normally be done using an anonymous inner class as follows:

* *
 *return new StreamingResolution("text/xml") {
 *    public void stream(HttpServletResponse response) throws Exception {
 *        // custom output generation code
 *        response.getWriter().write(...);
 *        // or
 *        response.getOutputStream().write(...);
 *    }
 *}.setFilename("your-filename.xml");
 *
* * @author Tim Fennell */ public class StreamingResolution implements Resolution { /** Date format string for RFC 822 dates. */ private static final String RFC_822_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z"; private static final Log log = Log.getInstance(StreamingResolution.class); private InputStream inputStream; private Reader reader; private String filename; private String contentType; private String characterEncoding; private long lastModified = -1; private long length = -1; private boolean attachment; /** * Constructor only to be used when subclassing the StreamingResolution (usually using * an anonymous inner class. If this constructor is used, and stream() is not overridden * then an exception will be thrown! * * @param contentType the content type of the data in the stream (e.g. image/png) */ public StreamingResolution(String contentType) { this.contentType = contentType; } /** * Constructor that builds a StreamingResolution that will stream binary data back to the * client and identify the data as being of the specified content type. * * @param contentType the content type of the data in the stream (e.g. image/png) * @param inputStream an InputStream from which to read the data to return to the client */ public StreamingResolution(String contentType, InputStream inputStream) { this.contentType = contentType; this.inputStream = inputStream; } /** * Constructor that builds a StreamingResolution that will stream character data back to the * client and identify the data as being of the specified content type. * * @param contentType the content type of the data in the stream (e.g. text/xml) * @param reader a Reader from which to read the character data to return to the client */ public StreamingResolution(String contentType, Reader reader) { this.contentType = contentType; this.reader = reader; } /** * Constructor that builds a StreamingResolution that will stream character data from a String * back to the client and identify the data as being of the specified content type. * * @param contentType the content type of the data in the stream (e.g. text/xml) * @param output a String to stream back to the client */ public StreamingResolution(String contentType, String output) { this(contentType, new StringReader(output)); } /** * Sets the filename that will be the default name suggested when the user is prompted * to save the file/stream being sent back. If the stream is not for saving by the user * (i.e. it should be displayed or used by the browser) this value should not be set. * * @param filename the default filename the user will see * @return StreamingResolution so that this method call can be chained to the constructor * and returned */ public StreamingResolution setFilename(String filename) { this.filename = filename; setAttachment(filename != null); return this; } /** * Sets the character encoding that will be set on the request when executing this * resolution. If none is set, then the current character encoding (either the one * selected by the LocalePicker or the container default one) will be used. * * @param characterEncoding the character encoding to use instead of the default */ public void setCharacterEncoding(String characterEncoding) { this.characterEncoding = characterEncoding; } /** * Sets the modification-date timestamp. If this property is set, the browser may be able to * apply it to the downloaded file. If this property is unset, the modification-date parameter * will be omitted. * * @param lastModified The date-time (as a long) that the file was last modified. Optional. * @return StreamingResolution so that this method call can be chained to the constructor and * returned. */ public StreamingResolution setLastModified(long lastModified) { this.lastModified = lastModified; return this; } /** * Sets the file length. If this property is set, the file size will be reported in the HTTP * header. This may help with file download progress indicators. If this property is unset, the * size parameter will be omitted. * * @param length The length of the file in bytes. * @return StreamingResolution so that this method call can be chained to the constructor and * returned. */ public StreamingResolution setLength(long length) { this.length = length; return this; } /** * Indicates whether to use content-disposition attachment headers or not. (Defaults to true). * * @param attachment Whether the content should be treated as an attachment, or a direct * download. * @return StreamingResolution so that this method call can be chained to the constructor and * returned. */ public StreamingResolution setAttachment(boolean attachment) { this.attachment = attachment; return this; } /** * Streams data from the InputStream or Reader to the response's OutputStream or PrinterWriter, * using a moderately sized buffer to ensure that the operation is reasonable efficient. * Once the InputStream or Reader signaled the end of the stream, close() is called on it. * * @param request the HttpServletRequest being processed * @param response the paired HttpServletResponse * @throws IOException if there is a problem accessing one of the streams or reader/writer * objects used. */ final public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { applyHeaders(response); stream(response); } /** * Sets the response headers, based on what is known about the file or stream being handled. * * @param response the current HttpServletResponse */ protected void applyHeaders(HttpServletResponse response) { response.setContentType(this.contentType); if (this.characterEncoding != null) { response.setCharacterEncoding(characterEncoding); } // Set Content-Length header if (length >= 0) { // Odd that ServletResponse.setContentLength is limited to int. // requires downcast from long to int e.g. // response.setContentLength((int)length); // Workaround to allow large files: response.addHeader("Content-Length", Long.toString(length)); } // Set Last-Modified header if (lastModified >= 0) { response.setDateHeader("Last-Modified", lastModified); } // For Content-Disposition spec, see http://www.ietf.org/rfc/rfc2183.txt if (attachment || filename != null) { // Value of filename should be RFC 2047 encoded here (see RFC 2616) but few browsers // support that, so just escape the quote for now String escaped = this.filename.replace("\"", "\\\""); StringBuilder header = new StringBuilder(attachment ? "attachment" : "inline").append( ";filename=\"").append(escaped).append("\""); if (lastModified >= 0) { SimpleDateFormat format = new SimpleDateFormat(RFC_822_DATE_FORMAT); String value = format.format(new Date(lastModified)); header.append(";modification-date=\"").append(value).append("\""); } if (length >= 0) { header.append(";size=").append(length); } response.setHeader("Content-Disposition", header.toString()); } } /** *

* Does the actual streaming of data through the response. If subclassed, this method should be * overridden to stream back data other than data supplied by an InputStream or a Reader * supplied to a constructor. *

* *

* If an InputStream or Reader was supplied to a constructor, this implementation uses a * moderately sized buffer to stream data from it to the response to make the operation * reasonably efficient, and closes the InputStream or the Reader. If an IOException occurs when * closing it, that exception will be logged as a warning, and not thrown to avoid * masking a possibly previously thrown exception. *

* * @param response the HttpServletResponse from which either the output stream or writer can be * obtained * @throws Exception if any problems arise when streaming data */ protected void stream(HttpServletResponse response) throws Exception { int length = 0; if (this.reader != null) { char[] buffer = new char[512]; try { PrintWriter out = response.getWriter(); while ( (length = this.reader.read(buffer)) != -1 ) { out.write(buffer, 0, length); } } finally { try { this.reader.close(); } catch (Exception e) { log.warn("Error closing reader", e); } } } else if (this.inputStream != null) { byte[] buffer = new byte[512]; try { ServletOutputStream out = response.getOutputStream(); while ( (length = this.inputStream.read(buffer)) != -1) { out.write(buffer, 0, length); } } finally { try { this.inputStream.close(); } catch (Exception e) { log.warn("Error closing input stream", e); } } } else { throw new StripesRuntimeException("A StreamingResolution was constructed without " + "supplying a Reader or InputStream, but stream() was not overridden. Please " + "either supply a source of streaming data, or override the stream() method."); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy