Package io.fathom.cloud.storage.api.os.resources

Source Code of io.fathom.cloud.storage.api.os.resources.ObjectResource

package io.fathom.cloud.storage.api.os.resources;

import io.fathom.cloud.CloudException;
import io.fathom.cloud.protobuf.CloudCommons.Attributes;
import io.fathom.cloud.protobuf.CloudCommons.KeyValueData;
import io.fathom.cloud.protobuf.FileModel.FileData;
import io.fathom.cloud.server.model.Project;
import io.fathom.cloud.server.model.User;
import io.fathom.cloud.storage.FileBlob;
import io.fathom.cloud.storage.FileServiceInternal;
import io.fathom.cloud.storage.FsBucket;
import io.fathom.cloud.storage.FsFile;
import io.fathom.cloud.storage.services.MimeTypes;

import java.io.File;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;

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

import com.fathomdb.utils.Hex;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.inject.persist.Transactional;

@Path("/openstack/storage/{project}/{bucket}/{name:.+}")
@Transactional
public class ObjectResource extends ObjectstoreResourceBase {
    private static final Logger log = LoggerFactory.getLogger(ObjectResource.class);

    private static final String OBJECT_META_PREFIX = "x-object-meta-";

    @PathParam("bucket")
    String bucketName;

    @PathParam("name")
    String name;

    @Inject
    FileServiceInternal fs;

    @PUT
    public Response createFile(File src) throws Exception {
        // TODO: Check bucket access before upload?
        Project project = getProject();

        // TODO: This doesn't work for even moderate-sized data

        String contentType = httpRequest.getHeader(HttpHeaders.CONTENT_TYPE);

        log.info("Create file {} with contentType {}", name, contentType);
        if (Strings.isNullOrEmpty(contentType)) {
            contentType = MimeTypes.INSTANCE.guessMimeType(name);
            log.info("Content type for file {} guessed as {}", name, contentType);
        }

        Map<String, String> userAttributes = Maps.newHashMap();

        Enumeration<String> headerNames = httpRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();

            // Header names are case-insensitive
            String normalized = headerName.toLowerCase();
            if (normalized.startsWith(OBJECT_META_PREFIX)) {
                String key = headerName.substring(OBJECT_META_PREFIX.length());
                String value = httpRequest.getHeader(headerName);

                userAttributes.put(key, value);
            }
        }

        FileBlob fileData = FileBlob.build(src);

        fs.putFile(project, bucketName, name, fileData, contentType, userAttributes);

        ResponseBuilder response = Response.status(Status.CREATED);
        return response.build();
    }

    @POST
    public Response appendToFile(File src) throws Exception {
        // TODO: Special handler for small data?

        // TODO: Check bucket access before upload?
        Project project = getProject();

        FileBlob fileData = FileBlob.build(src);

        // No position specified
        Long position = null;

        fs.append(project, bucketName, name, position, fileData);

        ResponseBuilder response = Response.status(Status.CREATED);
        return response.build();
    }

    @DELETE
    public Response deleteFile() throws Exception {
        fs.deleteFile(getProject(), bucketName, name);

        ResponseBuilder response = Response.status(Status.NO_CONTENT);
        return response.build();
    }

    public static Response buildReadResponse(HttpServletRequest httpRequest, FileServiceInternal fs, FsFile found) {
        ResponseBuilder response;

        if (found == null) {
            // TODO: Check for meta error page??
            throw new WebApplicationException(Status.NOT_FOUND);
        }

        boolean head = httpRequest.getMethod().equalsIgnoreCase("head");

        boolean includeContentLength = true;

        if (head) {
            response = Response.ok();
        } else {
            String range = httpRequest.getHeader("Range");
            Long from = null;
            Long to = null;

            if (!Strings.isNullOrEmpty(range)) {
                if (range.startsWith("bytes=")) {
                    range = range.substring(6);
                    int dashIndex = range.indexOf('-');
                    if (dashIndex == -1) {
                        throw new IllegalArgumentException();
                    }
                    String fromString = range.substring(0, dashIndex);
                    String toString = range.substring(dashIndex + 1);

                    if (!Strings.isNullOrEmpty(fromString)) {
                        from = Long.valueOf(fromString);
                    }
                    if (!Strings.isNullOrEmpty(toString)) {
                        to = Long.valueOf(toString);
                    }
                    if (from == null && to != null) {
                        // This means the last N bytes
                        from = found.getLength() - to;
                        to = null;
                    }
                } else {
                    throw new UnsupportedOperationException();
                }
            }

            StreamingOutput stream = fs.open(found, from, to);

            if (from != null || to != null) {
                response = Response.status(206);

                long rangeStart = 0;

                if (from != null) {
                    rangeStart = Math.max(from, 0);
                }
                long rangeEnd = found.getLength() - 1;
                if (to != null) {
                    rangeEnd = Math.min(rangeEnd, to - 1);
                }
                String contentRange = "bytes " + rangeStart + "-" + rangeEnd + "/" + found.getLength();

                response.header(HttpHeaders.CONTENT_LENGTH, 1 + (rangeEnd - rangeStart));
                includeContentLength = false;

                response.header("Content-Range", contentRange);
            } else {
                response = Response.ok();
            }

            response.entity(stream);
        }

        setHeaders(found, response);

        // TODO: Support range with head??

        // We do always set the length, even on a HEAD
        // But on a range, it is specially handled!
        if (includeContentLength && found.getData().hasLength()) {
            response.header(HttpHeaders.CONTENT_LENGTH, found.getData().getLength());
        }

        response.header("Server", "JustInoughOpenStack");
        return response.build();
    }

    private FsFile findFile() throws CloudException {
        FsFile found;

        User user = null;

        FsBucket bucket;

        if (!isAuthenticated()) {
            // Check for a public file
            String projectName = getProjectName();
            Project project = new Project(Long.valueOf(projectName));
            bucket = fs.findBucket(user, project, bucketName);
        } else {
            user = getAuth().getUser();
            bucket = fs.findBucket(user, getProject(), bucketName);
        }

        if (bucket == null) {
            return null;
        }

        found = fs.findFileInfo(bucket, name);

        if (found == null && !isAuthenticated()) {
            // X-Container-Meta-Web-Index
            String indexPage = bucket.getMetaWebIndex();
            if (indexPage != null) {
                String index;
                if (!name.endsWith("/")) {
                    index = name + "/" + indexPage;
                } else {
                    index = name + indexPage;
                }

                log.debug("Page not found, so trying index page: {}", index);
                found = fs.findFileInfo(bucket, index);
            }
        }

        return found;
    }

    @HEAD
    public Response getFileHead() throws CloudException {
        FsFile found = findFile();
        return buildReadResponse(httpRequest, fs, found);
    }

    @GET
    public Response getFile() throws CloudException {
        FsFile found = findFile();
        return buildReadResponse(httpRequest, fs, found);
    }

    private static void setHeaders(FsFile found, ResponseBuilder response) {
        FileData data = found.getData();

        if (data.hasLastModified()) {
            response.lastModified(new Date(data.getLastModified()));
        }

        if (data.hasHash()) {
            response.header(HttpHeaders.ETAG, Hex.toHex(data.getHash().toByteArray()));
        }

        if (data.hasContentType()) {
            response.header(HttpHeaders.CONTENT_TYPE, data.getContentType());
        }

        if (data.hasAttributes()) {
            Attributes attributes = data.getAttributes();
            for (KeyValueData entry : attributes.getUserAttributesList()) {
                response.header(OBJECT_META_PREFIX + entry.getKey(), entry.getValue());
            }
        }
    }

}
TOP

Related Classes of io.fathom.cloud.storage.api.os.resources.ObjectResource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.