/* * Copyright 2015-2016 OpenCB * * 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 org.opencb.opencga.server.rest.utils; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.apache.commons.lang3.time.StopWatch; import org.opencb.commons.datastore.core.QueryResult; import org.opencb.opencga.catalog.managers.AbstractManager; import org.opencb.opencga.catalog.models.File; import org.opencb.opencga.catalog.models.acls.permissions.FileAclEntry; import org.opencb.opencga.core.exception.VersionException; import org.opencb.opencga.server.rest.OpenCGAWSServer; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; import javax.ws.rs.core.*; import java.io.DataInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.List; import java.util.concurrent.TimeUnit; @Path("/{version}/utils") @Produces("application/json") public class FileRanges extends OpenCGAWSServer { public FileRanges(@Context UriInfo uriInfo, @Context HttpServletRequest httpServletRequest) throws IOException, VersionException { super(uriInfo, httpServletRequest); } @GET @Path("/ranges/{file}") @ApiOperation(value = "Fetchs alignment files using HTTP Ranges protocol") @Produces("text/plain") public Response getRanges(@Context HttpHeaders headers, @ApiParam(value = "File id, name or path") @PathParam("file") String fileIdStr, @ApiParam(value = "Study [[user@]project:]study where study and project can be either the id or alias") @QueryParam("study") String studyStr) { DataInputStream stream = null; try { AbstractManager.MyResourceId resource = catalogManager.getFileManager().getId(fileIdStr, studyStr, sessionId); catalogManager.getAuthorizationManager().checkFilePermission(resource.getResourceId(), resource.getUser(), FileAclEntry.FilePermissions.DOWNLOAD); QueryResult<File> queryResult = catalogManager.getFile(resource.getResourceId(), this.queryOptions, sessionId); File file = queryResult.getResult().get(0); List<String> rangeList = headers.getRequestHeader("range"); if (rangeList != null) { long from; long to; String[] acceptedRanges = rangeList.get(0).split("=")[1].split("-"); from = Long.parseLong(acceptedRanges[0]); to = Long.parseLong(acceptedRanges[1]); int length = (int) (to - from) + 1; ByteBuffer buf = ByteBuffer.allocate(length); logger.debug("from: {} , to: {}, length:{}", from, to, length); StopWatch t = StopWatch.createStarted(); java.nio.file.Path filePath = Paths.get(file.getUri()); try (FileChannel fc = (FileChannel.open(filePath, StandardOpenOption.READ))) { fc.position(from); fc.read(buf); } t.stop(); logger.debug("Skip {}B and read {}B in {}s", from, length, t.getTime(TimeUnit.MILLISECONDS) / 1000.0); return Response.ok(buf.array(), MediaType.APPLICATION_OCTET_STREAM_TYPE) .header("Accept-Ranges", "bytes") .header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Headers", "x-requested-with, content-type, range") .header("Access-Control-Allow-Credentials", "true") .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") .header("Content-Range", "bytes " + from + "-" + to + "/" + file.getSize()) .header("Content-length", to - from + 1) .status(Response.Status.PARTIAL_CONTENT).build(); } else { stream = catalogManager.downloadFile(resource.getResourceId(), sessionId); return createOkResponse(stream, MediaType.APPLICATION_OCTET_STREAM_TYPE, file.getName()); } } catch (Exception e) { if (stream != null) { try { stream.close(); } catch (IOException ignore) { } } return createErrorResponse(e); } } }