package au.com.vaadinutils.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.vaadin.server.Constants; import com.vaadin.server.DownloadStream; import com.vaadin.server.VaadinRequest; import com.vaadin.server.VaadinResponse; public class PartialDownloadStream extends DownloadStream { private static final long serialVersionUID = 1L; private long contentLength; Logger logger = LogManager.getLogger(); public PartialDownloadStream(InputStream stream, String contentType, String fileName) { super(stream, contentType, fileName); // TODO Auto-generated constructor stub } @Override 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; } Integer start = null; Integer end = null; if (request.getHeader("Range") != null) { String rangeParam = request.getHeader("Range"); String[] range = rangeParam.split("=")[1].split("-"); start = Integer.parseInt(range[0]); if (range.length > 1) { end = Integer.parseInt(range[1]); } // Content-Range: bytes 42-1233/1234 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); } // Download from given stream final InputStream data = getStream(); if (data == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } OutputStream out = null; try { // Sets content type response.setContentType(getContentType()); // Sets cache headers response.setCacheTime(getCacheTime()); // suggest local filename from DownloadStream if // Content-Disposition // not explicitly set String contentDispositionValue = getParameter("Content-Disposition"); if (contentDispositionValue == null) { contentDispositionValue = "filename=\"" + getFileName() + "\""; response.setHeader("Content-Disposition", contentDispositionValue); } // Copy download stream parameters directly // to HTTP headers. final Iterator<String> i = getParameterNames(); if (i != null) { while (i.hasNext()) { final String param = i.next(); response.setHeader(param, getParameter(param)); } } // Content-Range: bytes 42-1233/1234 if (start != null && end != null) { logger.warn("untested, may be broken"); response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + (contentLength)); response.setHeader("Content-Length", String.valueOf(end - start)); } if (start != null && end == null) { response.setHeader("Content-Range", "bytes " + start + "-" + (contentLength - 1) + "/" + (contentLength)); response.setHeader("Content-Length", String.valueOf(contentLength - start)); } 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(); if (start == null) { start = 0; } long totalWritten = 0; long counter = 0; while ((bytesRead = data.read(buffer)) > 0) { int bufferStart = 0; int bufferEnd = bytesRead; if (counter + bytesRead >= start) { if (counter < start && counter + bytesRead >= start) { bufferStart = (int) (start - counter); } out.write(buffer, bufferStart, bufferEnd); totalWritten += bufferEnd - bufferStart; if (totalWritten >= buffer.length) { // Avoid chunked encoding for small resources out.flush(); } } counter += bytesRead; } if (totalWritten != contentLength - (start)) { logger.error("Error {}", (contentLength - (start)) - totalWritten); } } catch (IOException e) { logger.warn(e.getMessage()); } catch (Exception e) { logger.error(e); } finally { tryToCloseStream(out); tryToCloseStream(data); } } /** * Helper method that tries to close an output stream and ignores any * exceptions. * * @param out * the output stream to close, <code>null</code> 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, <code>null</code> 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 } } public void setContentLength(long contentLength) { this.contentLength = contentLength; } }