de.fhg.iais.cortex.rest.resources.BinaryResource.java Source code

Java tutorial

Introduction

Here is the source code for de.fhg.iais.cortex.rest.resources.BinaryResource.java

Source

package de.fhg.iais.cortex.rest.resources;

/******************************************************************************
 * Copyright 2011 (c) Fraunhofer IAIS Netmedia  http://www.iais.fraunhofer.de *
 * ************************************************************************** *
 * 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.                                             *
 ******************************************************************************/

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.LimitInputStream;
import com.google.inject.Provider;
import com.google.inject.name.Named;

import de.fhg.iais.commons.annotation.UsedBy;
import de.fhg.iais.commons.exception.ParseException;
import de.fhg.iais.cortex.rest.resources.authentication.AbstractSecureResource;
import de.fhg.iais.cortex.services.IAccessService;
import de.fhg.iais.cortex.services.ISearchService;
import de.fhg.iais.cortex.services.authentication.annotation.UseAuthentication;
import de.fhg.iais.cortex.services.authentication.types.Role;
import de.fhg.iais.cortex.services.authentication.types.Role.Profile;
import de.fhg.iais.cortex.storage.Binary;
import de.fhg.iais.cortex.storage.exception.ItemNotFoundException;
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil2;

@Path("/binary")
@UsedBy("rest" + "guice")
public class BinaryResource extends AbstractSecureResource {

    private static final Logger LOG = LoggerFactory.getLogger(BinaryResource.class);

    private final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
    private final HashFunction hf;
    private final long ONE_YEAR_IN_MS = 1000L * 60L * 60L * 24L * 365L;

    private final IAccessService accessService;
    private final MimeUtil2 mimeUtil;

    @Inject
    //    public BinaryResource(IAccessService accessService, Provider<Role> roleProvider, @Named("auth.aas.blacklistedProvider") String blacklist) {
    //        super(accessService, roleProvider, blacklist);
    public BinaryResource(IAccessService accessService, ISearchService searchService, Provider<Role> roleProvider,
            @Named("auth.aas.blacklistedProvider") String blacklist) {
        super(searchService, roleProvider, blacklist);

        this.accessService = accessService;

        this.mimeUtil = new MimeUtil2();
        this.mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.ExtensionMimeDetector");
        this.mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");

        this.hf = Hashing.md5();
    }

    /**
     * Reads a binary from a given item.
     * 
     * @param httpHeaders The headers containing security information.
     * @param identifier The identifier of the AIP.
     * @param file The requested binary file.
     * @return An InputStream containing the binary data.
     * @throws WebApplicationException with the status of ith the status of 404 if the item or the component
     *         do not exist. 500 if something went terribly wrong.
     */
    @Path("{id}/{file: .+}")
    @GET
    @Produces("*/*")
    @UseAuthentication(profile = Profile.RESTRICTED_READ)
    public Response getBinary(@Context HttpHeaders httpHeaders, @PathParam("id") String identifier,
            @PathParam("file") String file, @Context HttpServletRequest request) {

        List<String> range = httpHeaders.getRequestHeader("Range");
        List<String> ifRange = httpHeaders.getRequestHeader("If-Range");

        LOG.debug("Accessing binary ");

        try {
            if (!securityCheck(identifier)) {
                return getResponse();
            }

            Binary binary = this.accessService.getBinary(identifier, file);
            String mediaType = getMediaType(file);

            ResponseBuilder builder = createResponseBuilder(binary, range, file, ifRange);
            builder.type(mediaType);

            return builder.build();
        } catch (ItemNotFoundException e) {
            LOG.info("binary for AIP: '{}/{}'  not found.", identifier, file);
            return notFoundError(e, MediaType.APPLICATION_JSON_TYPE);
        } catch (Exception e) {
            LOG.error("binary for AIP: '" + identifier + "/" + file + "'  not found.", e);
            return serverErrorError(MediaType.APPLICATION_JSON_TYPE, e);
        }
    }

    private String getMediaType(String fileName) {
        Collection<?> mimeTypes = this.mimeUtil.getMimeTypes(fileName);
        MimeType type = MimeUtil2.getMostSpecificMimeType(mimeTypes);
        return type.toString();
    }

    private ResponseBuilder createResponseBuilder(final Binary binary, List<String> range, String file,
            List<String> ifRange) throws IOException {
        ByteRange byteRange;
        try {
            byteRange = ByteRange.parse(range, binary.getLength());
        } catch (ParseException e) {
            return Response.status(416);
        }
        InputStream content = binary.getContent();

        String actualETag = "\"" + this.hf.newHasher().putString(file).hash().toString() + "\"";
        if (byteRange == null) {
            return Response.ok(content).header("Accept-Ranges", "bytes").header("ETag", actualETag)
                    .header("Expires", new Date(System.currentTimeMillis() + this.ONE_YEAR_IN_MS))
                    .header("Date", this.dateFormat.format(new Date()))
                    .header("Content-Length", binary.getLength());
        }
        String expectedETag = null;
        if ((ifRange != null) && !ifRange.isEmpty()) {
            expectedETag = ifRange.get(0);
        }

        if (expectedETag != null) {
            if (!actualETag.equals(expectedETag)) {
                return Response.status(Status.PRECONDITION_FAILED);
            }
        }

        if (!skipBytes(content, 0)) {
            return Response.status(416);
        }

        LimitInputStream entity = new LimitInputStream(content, binary.getLength());

        return Response.status(206).entity(entity).header("Accept-Ranges", "bytes").header("ETag", actualETag)
                .header("Expires", new Date(System.currentTimeMillis() + this.ONE_YEAR_IN_MS))
                .header("Date", this.dateFormat.format(new Date())).header("Content-Length", binary.getLength())
                .header("Connection", "keep-alive").header("Keep-Alive", "timeout=100 max=1000")
                .header("Content-Range", "bytes " + 0 + "-" + (binary.getLength() - 1) + "/" + binary.getLength());
    }

    private boolean skipBytes(InputStream binary, long count) throws IOException {
        long remaining = count;
        while (remaining > 0) {
            long skipped = binary.skip(remaining);
            if (skipped == 0) {
                return false;
            }

            remaining -= skipped;
        }

        return true;
    }
}