com.vaadin.server.DownloadStream Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2023 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import com.vaadin.util.EncodeUtil;
/**
* Downloadable stream.
*
* Note that the methods in a DownloadStream are called without locking the
* session to prevent locking the session during long file downloads. If your
* DownloadStream uses anything from the session, you must handle the locking.
*
*
* @author Vaadin Ltd.
* @since 3.0
*/
@SuppressWarnings("serial")
public class DownloadStream implements Serializable {
public static final String CONTENT_DISPOSITION = "Content-Disposition";
/**
* Maximum cache time.
*/
public static final long MAX_CACHETIME = Long.MAX_VALUE;
/**
* Default cache time.
*/
public static final long DEFAULT_CACHETIME = 1000 * 60 * 60 * 24;
private InputStream stream;
private String contentType;
private String fileName;
private Map params;
private long cacheTime = DEFAULT_CACHETIME;
private int bufferSize = 0;
/**
* Creates a new instance of DownloadStream.
*/
public DownloadStream(InputStream stream, String contentType,
String fileName) {
setStream(stream);
setContentType(contentType);
setFileName(fileName);
}
/**
* Gets downloadable stream.
*
* @return output stream.
*/
public InputStream getStream() {
return stream;
}
/**
* Sets the stream.
*
* @param stream
* The stream to set
*/
public void setStream(InputStream stream) {
this.stream = stream;
}
/**
* Gets stream content type.
*
* @return type of the stream content.
*/
public String getContentType() {
return contentType;
}
/**
* Sets stream content type.
*
* @param contentType
* the contentType to set
*/
public void setContentType(String contentType) {
this.contentType = contentType;
}
/**
* Returns the file name.
*
* @return the name of the file.
*/
public String getFileName() {
return fileName;
}
/**
* Sets the file name.
*
* @param fileName
* the file name to set.
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* Sets a parameter for download stream. Parameters are optional information
* about the downloadable stream and their meaning depends on the used
* adapter. For example in WebAdapter they are interpreted as HTTP response
* headers.
*
* If the parameters by this name exists, the old value is replaced.
*
* @param name
* the Name of the parameter to set.
* @param value
* the Value of the parameter to set.
*/
public void setParameter(String name, String value) {
if (params == null) {
params = new HashMap<>();
}
params.put(name, value);
}
/**
* Gets a paramater for download stream. Parameters are optional information
* about the downloadable stream and their meaning depends on the used
* adapter. For example in WebAdapter they are interpreted as HTTP response
* headers.
*
* @param name
* the Name of the parameter to set.
* @return Value of the parameter or null if the parameter does not exist.
*/
public String getParameter(String name) {
if (params != null) {
return params.get(name);
}
return null;
}
/**
* Gets the names of the parameters.
*
* @return Iterator of names or null if no parameters are set.
*/
public Iterator getParameterNames() {
if (params != null) {
return params.keySet().iterator();
}
return null;
}
/**
* Gets length of cache expiration time. This gives the adapter the
* possibility cache streams sent to the client. The caching may be made in
* adapter or at the client if the client supports caching. Default is
* DEFAULT_CACHETIME
.
*
* @return Cache time in milliseconds
*/
public long getCacheTime() {
return cacheTime;
}
/**
* Sets length of cache expiration time. This gives the adapter the
* possibility cache streams sent to the client. The caching may be made in
* adapter or at the client if the client supports caching. Zero or negative
* value disables the caching of this stream.
*
* @param cacheTime
* the cache time in milliseconds.
*/
public void setCacheTime(long cacheTime) {
this.cacheTime = cacheTime;
}
/**
* Gets the size of the download buffer.
*
* @return The size of the buffer in bytes.
*/
public int getBufferSize() {
return bufferSize;
}
/**
* Sets the size of the download buffer.
*
* @param bufferSize
* the size of the buffer in bytes.
*
* @since 7.0
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
/**
* Writes this download stream to a Vaadin response. This takes care of
* setting response headers according to what is defined in this download
* stream ({@link #getContentType()}, {@link #getCacheTime()},
* {@link #getFileName()}) and transferring the data from the stream (
* {@link #getStream()}) to the response. Defined parameters (
* {@link #getParameterNames()}) are also included as headers in the
* response. If there's is a parameter named Location
, a
* redirect (302 Moved temporarily) is sent instead of the contents of this
* stream.
*
* @param request
* the request for which the response should be written
* @param response
* the Vaadin response to write this download stream to
*
* @throws IOException
* passed through from the Vaadin response
*
* @since 7.0
*/
public void writeResponse(VaadinRequest request, VaadinResponse response)
throws IOException {
if (getParameter("Location") != null) {
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.setHeader("Location", getParameter("Location"));
return;
}
// Download from given stream
final InputStream data = getStream();
if (data == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (data != null) {
OutputStream out = null;
try {
// Sets content type
response.setContentType(getContentType());
// Sets cache headers
response.setCacheTime(getCacheTime());
// Copy download stream parameters directly
// to HTTP headers.
final Iterator i = getParameterNames();
if (i != null) {
while (i.hasNext()) {
final String param = i.next();
response.setHeader(param, getParameter(param));
}
}
// Content-Disposition: attachment generally forces download
String contentDisposition = getParameter(CONTENT_DISPOSITION);
if (contentDisposition == null) {
contentDisposition = getContentDispositionFilename(
getFileName());
}
response.setHeader(CONTENT_DISPOSITION, contentDisposition);
int bufferSize = getBufferSize();
if (bufferSize <= 0 || bufferSize > Constants.MAX_BUFFER_SIZE) {
bufferSize = Constants.DEFAULT_BUFFER_SIZE;
}
final byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
out = response.getOutputStream();
long totalWritten = 0;
while ((bytesRead = data.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
totalWritten += bytesRead;
if (totalWritten >= buffer.length) {
// Avoid chunked encoding for small resources
out.flush();
}
}
} finally {
tryToCloseStream(out);
tryToCloseStream(data);
}
}
}
/**
* Returns the filename formatted for inclusion in a Content-Disposition
* header. Includes both a plain version of the name and a UTF-8 version
*
* @since 7.4.8
* @param filename
* The filename to include
* @return A value for inclusion in a Content-Disposition header
*/
public static String getContentDispositionFilename(String filename) {
String encodedFilename = EncodeUtil.rfc5987Encode(filename);
return String.format("filename=\"%s\"; filename*=utf-8''%s",
encodedFilename, encodedFilename);
}
/**
* Helper method that tries to close an output stream and ignores any
* exceptions.
*
* @param out
* the output stream to close, null
is also
* supported
*/
static void tryToCloseStream(OutputStream out) {
try {
// try to close output stream (e.g. file handle)
if (out != null) {
out.close();
}
} catch (IOException e1) {
// NOP
}
}
/**
* Helper method that tries to close an input stream and ignores any
* exceptions.
*
* @param in
* the input stream to close, null
is also supported
*/
static void tryToCloseStream(InputStream in) {
try {
// try to close output stream (e.g. file handle)
if (in != null) {
in.close();
}
} catch (IOException e1) {
// NOP
}
}
}