annis.gui.requesthandler.BinaryRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for annis.gui.requesthandler.BinaryRequestHandler.java

Source

/*
 * Copyright 2013 Corpuslinguistic working group Humboldt University Berlin.
 *
 * 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 annis.gui.requesthandler;

import annis.libgui.Helper;
import annis.service.objects.AnnisBinaryMetaData;
import com.google.common.base.Preconditions;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.vaadin.server.RequestHandler;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinServletResponse;
import com.vaadin.server.VaadinSession;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This request handler provides binary-files with a stream of partial-content. 
 * The first GET-request is answered with the status-code 206 Partial Content.
 * 
 * @author Thomas Krause <krauseto@hu-berlin.de>
 * @author benjamin
 */
public class BinaryRequestHandler implements RequestHandler {

    private final static Logger log = LoggerFactory.getLogger(BinaryRequestHandler.class);

    private static final int BUFFER_SIZE = 0x1000; //4K

    private final static Escaper urlPathEscape = UrlEscapers.urlPathSegmentEscaper();

    @Override
    public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response)
            throws IOException {
        if (request.getPathInfo() != null && request.getPathInfo().startsWith("/Binary")) {
            if ("GET".equalsIgnoreCase(request.getMethod())) {
                sendResponse(session, request, response, true);
                return true;
            } else if ("HEAD".equalsIgnoreCase(request.getMethod())) {
                sendResponse(session, request, response, false);
                return true;
            }
        }
        return false;
    }

    public void sendResponse(VaadinSession session, VaadinRequest request, VaadinResponse pureResponse,
            boolean sendContent) throws IOException {
        if (!(pureResponse instanceof VaadinServletResponse)) {
            pureResponse.sendError(500, "Binary requests only work with servlets");
        }

        VaadinServletResponse response = (VaadinServletResponse) pureResponse;

        Map<String, String[]> binaryParameter = request.getParameterMap();
        String toplevelCorpusName = binaryParameter.get("toplevelCorpusName")[0];
        String documentName = binaryParameter.get("documentName")[0];

        String mimeType = null;
        if (binaryParameter.containsKey("mime")) {
            mimeType = binaryParameter.get("mime")[0];
        }
        try {
            // always set the buffer size to the same one we will use for coyping, 
            // otherwise we won't notice any client disconnection
            response.reset();
            response.setCacheTime(-1);
            response.resetBuffer();
            response.setBufferSize(BUFFER_SIZE); // 4K

            String requestedRangeRaw = request.getHeader("Range");

            WebResource binaryRes = Helper.getAnnisWebResource().path("query").path("corpora")
                    .path(urlPathEscape.escape(toplevelCorpusName)).path(urlPathEscape.escape(documentName))
                    .path("binary");

            WebResource metaBinaryRes = Helper.getAnnisWebResource().path("meta").path("binary")
                    .path(urlPathEscape.escape(toplevelCorpusName)).path(urlPathEscape.escape(documentName));

            // tell client that we support byte ranges
            response.setHeader("Accept-Ranges", "bytes");

            Preconditions.checkNotNull(mimeType, "No mime type given (parameter \"mime\"");

            AnnisBinaryMetaData meta = getMatchingMetadataFromService(metaBinaryRes, mimeType);
            if (meta == null) {
                response.sendError(404, "Binary file not found");
                return;
            }

            ContentRange fullRange = new ContentRange(0, meta.getLength() - 1, meta.getLength());

            ContentRange r = fullRange;
            try {
                if (requestedRangeRaw != null) {
                    List<ContentRange> requestedRanges = ContentRange.parseFromHeader(requestedRangeRaw,
                            meta.getLength(), 1);

                    if (!requestedRanges.isEmpty()) {
                        r = requestedRanges.get(0);
                    }
                }

                long contentLength = (r.getEnd() - r.getStart() + 1);

                boolean useContentRange = !fullRange.equals(r);

                response.setContentType(meta.getMimeType());
                if (useContentRange) {
                    response.setHeader("Content-Range", r.toString());
                }
                response.setContentLength((int) contentLength);
                response.setStatus(useContentRange ? 206 : 200);

                response.flushBuffer();
                if (sendContent) {
                    try (OutputStream out = response.getOutputStream();) {
                        writeFromServiceToClient(r.getStart(), contentLength, binaryRes, out, mimeType);
                    }
                }

            } catch (ContentRange.InvalidRangeException ex) {
                response.setHeader("Content-Range", "bytes */" + meta.getLength());
                response.sendError(416, "Requested range not satisfiable: " + ex.getMessage());
                return;
            }

        } catch (IOException ex) {
            log.error("IOException in BinaryRequestHandler", ex);
            response.setStatus(500);
        } catch (ClientHandlerException ex) {
            log.error(null, ex);
            response.setStatus(500);
        } catch (UniformInterfaceException ex) {
            log.error(null, ex);
            response.setStatus(500);
        }
    }

    private AnnisBinaryMetaData getMatchingMetadataFromService(WebResource metaBinaryRes, String mimeType) {
        List<AnnisBinaryMetaData> allMeta = metaBinaryRes.get(new AnnisBinaryMetaDataListType());

        AnnisBinaryMetaData bm = allMeta.get(0);
        for (AnnisBinaryMetaData m : allMeta) {
            if (mimeType != null && mimeType.equals(m.getMimeType())) {
                bm = m;
                break;
            }
        }
        return bm;
    }

    private void writeFromServiceToClient(long offset, long length, WebResource binaryRes, OutputStream out,
            String mimeType) {

        InputStream entityStream = null;
        try {
            ClientResponse response = binaryRes.path("" + offset).path("" + length).accept(mimeType)
                    .get(ClientResponse.class);

            entityStream = response.getEntityInputStream();

            long copiedBytes = copy(entityStream, out);
            Validate.isTrue(copiedBytes == length, "only copied " + copiedBytes + " bytes instead of " + length);
        } catch (IOException ex) {
            log.debug("writing to client failed", ex);
        } finally {
            if (entityStream != null) {
                try { // always close the entity stream in order to free resources at the service
                    entityStream.close();
                } catch (IOException ex) {
                    log.error(null, ex);
                }
            }
        }
    }

    private static long copy(InputStream from, OutputStream to) throws IOException {
        checkNotNull(from);
        checkNotNull(to);
        byte[] buf = new byte[BUFFER_SIZE];
        long total = 0;
        while (true) {
            int r = from.read(buf);
            if (r == -1) {
                break;
            }
            to.write(buf, 0, r);
            to.flush();
            total += r;
        }
        return total;
    }

    private static class AnnisBinaryMetaDataListType extends GenericType<List<AnnisBinaryMetaData>> {

        public AnnisBinaryMetaDataListType() {
        }
    }

}