org.nuxeo.ecm.platform.ui.web.download.BlobDownloadServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.platform.ui.web.download.BlobDownloadServlet.java

Source

/*
 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 * $Id$
 */

package org.nuxeo.ecm.platform.ui.web.download;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.impl.blob.StringBlob;
import org.nuxeo.ecm.core.storage.sql.coremodel.SQLBlob;
import org.nuxeo.ecm.platform.web.common.ServletHelper;
import org.nuxeo.ecm.platform.web.common.exceptionhandling.ExceptionHelper;
import org.nuxeo.ecm.platform.web.common.requestcontroller.filter.BufferingServletOutputStream;
import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;

/**
 * Simple download servlet used for big blobs that can not be downloaded from
 * within the JSF context (because of buffered ResponseWrapper).
 *
 * @author tiry
 */
public class BlobDownloadServlet extends HttpServlet {

    private static final String NXBIGBLOB_PREFIX = "nxbigblob";

    protected static final int BUFFER_SIZE = 1024 * 512;

    protected static final int MIN_BUFFER_SIZE = 1024 * 64;

    protected static final Blob BLOB_NOT_FOUND = new StringBlob("404");

    private static final long serialVersionUID = 986876871L;

    private static final Log log = LogFactory.getLog(BlobDownloadServlet.class);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String requestURI;
        try {
            requestURI = new URI(req.getRequestURI()).getPath();
        } catch (URISyntaxException e1) {
            requestURI = req.getRequestURI();
        }

        if (requestURI.contains("/" + NXBIGBLOB_PREFIX + "/")) {
            handleDownloadBlob(req, resp, requestURI);
        }
    }

    private Blob resolveBlob(HttpServletRequest req, HttpServletResponse resp, String requestURI)
            throws ServletException {

        String blobId = requestURI.replace(VirtualHostHelper.getContextPath(req) + "/" + NXBIGBLOB_PREFIX + "/",
                "");

        HttpSession session = req.getSession(false);
        if (session == null) {
            return null;
        }

        Blob blob = (Blob) session.getAttribute(blobId);
        if (blob != null) {
            session.removeAttribute(blobId);
            return blob;
        }

        return null;
    }

    private boolean isBlobFound(Blob blob, HttpServletResponse resp) throws ServletException {
        if (blob == null) {
            try {
                resp.sendError(HttpServletResponse.SC_NO_CONTENT, "No Blob found");
                return false;
            } catch (IOException e) {
                throw new ServletException(e);
            }
        } else if (BLOB_NOT_FOUND.equals(blob)) {
            try {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No Blob found");
                return false;
            } catch (IOException e) {
                throw new ServletException(e);
            }
        }
        return true;
    }

    private void handleDownloadBlob(HttpServletRequest req, HttpServletResponse resp, String requestURI)
            throws ServletException {
        Blob blob = resolveBlob(req, resp, requestURI);
        if (!isBlobFound(blob, resp)) {
            return;
        }

        try {
            downloadBlob(req, resp, blob, null);
        } catch (IOException e) {
            throw new ServletException(e);
        }
    }

    void downloadBlob(HttpServletRequest req, HttpServletResponse resp, Blob blob, String fileName)
            throws IOException, ServletException {
        InputStream in = blob.getStream();
        OutputStream out = resp.getOutputStream();
        try {

            if (fileName == null || fileName.length() == 0) {
                if (blob.getFilename() != null && blob.getFilename().length() > 0) {
                    fileName = blob.getFilename();
                } else {
                    fileName = "file";
                }
            }

            resp.setHeader("Content-Disposition", ServletHelper.getRFC2231ContentDisposition(req, fileName));
            resp.setContentType(blob.getMimeType());

            long fileSize = blob.getLength();
            if (fileSize > 0) {
                String range = req.getHeader("Range");
                ByteRange byteRange = null;
                if (range != null) {
                    try {
                        byteRange = parseRange(range, fileSize);
                    } catch (ClientException e) {
                        log.error(e.getMessage(), e);
                    }
                }
                if (byteRange != null) {
                    resp.setHeader("Accept-Ranges", "bytes");
                    resp.setHeader("Content-Range",
                            "bytes " + byteRange.getStart() + "-" + byteRange.getEnd() + "/" + fileSize);
                    long length = byteRange.getLength();
                    if (length < Integer.MAX_VALUE) {
                        resp.setContentLength((int) length);
                    }
                    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                    writeStream(in, out, byteRange);
                } else {
                    if (fileSize < Integer.MAX_VALUE) {
                        resp.setContentLength((int) fileSize);
                    }
                    writeStream(in, out, new ByteRange(0, fileSize - 1));
                }
            }

        } catch (IOException ioe) {
            handleClientDisconnect(ioe);
        } catch (Exception e) {
            throw new ServletException(e);
        } finally {
            if (resp != null) {
                try {
                    resp.flushBuffer();
                } catch (IOException ioe) {
                    handleClientDisconnect(ioe);
                }
            }
            if (in != null) {
                in.close();
            }
        }
    }

    public void handleClientDisconnect(IOException ioe) throws IOException {
        if (ExceptionHelper.isClientAbortError(ioe)) {
            log.debug("Client disconnected: " + ioe.getMessage());
        } else {
            // this is a real unexpected problem, let the traditional error
            // management handle this case
            throw ioe;
        }
    }

    public static void writeStream(InputStream in, OutputStream out, ByteRange range) throws IOException {
        BufferingServletOutputStream.stopBuffering(out);
        byte[] buffer = new byte[BUFFER_SIZE];
        long read;
        long offset = range.getStart();
        in.skip(offset);
        while (offset <= range.getEnd() && (read = in.read(buffer)) != -1) {
            read = Math.min(read, range.getEnd() - offset + 1);
            out.write(buffer, 0, (int) read);
            out.flush();
            offset += read;
        }

    }

    public static ByteRange parseRange(String range, long fileSize) throws ClientException {
        // Do no support multiple ranges
        if (!range.startsWith("bytes=") || range.indexOf(',') >= 0) {
            throw new ClientException("Cannot parse range : " + range);
        }
        int sepIndex = range.indexOf('-', 6);
        if (sepIndex < 0) {
            throw new ClientException("Cannot parse range : " + range);
        }
        String start = range.substring(6, sepIndex).trim();
        String end = range.substring(sepIndex + 1).trim();
        long rangeStart = 0;
        long rangeEnd = fileSize - 1;
        if (start.isEmpty()) {
            if (end.isEmpty()) {
                throw new ClientException("Cannot parse range : " + range);
            }
            rangeStart = fileSize - Integer.parseInt(end);
            if (rangeStart < 0) {
                rangeStart = 0;
            }
        } else {
            rangeStart = Integer.parseInt(start);
            if (!end.isEmpty()) {
                rangeEnd = Integer.parseInt(end);
            }
        }
        if (rangeStart > rangeEnd) {
            throw new ClientException("Cannot parse range : " + range);
        }

        return new ByteRange(rangeStart, rangeEnd);
    }

    public static class ByteRange {

        private long start;

        private long end;

        public ByteRange(long rangeStart, long rangeEnd) {
            start = rangeStart;
            end = rangeEnd;
        }

        public long getStart() {
            return start;
        }

        public long getEnd() {
            return end;
        }

        public long getLength() {
            return end - start + 1;
        }
    }

}