com.bouncestorage.swiftproxy.v1.ContainerResource.java Source code

Java tutorial

Introduction

Here is the source code for com.bouncestorage.swiftproxy.v1.ContainerResource.java

Source

/*
 * Copyright 2015 Bounce Storage, Inc. <info@bouncestorage.com>
 *
 * 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 com.bouncestorage.swiftproxy.v1;

import static java.util.Objects.requireNonNull;

import static com.google.common.base.Throwables.propagate;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.validation.constraints.NotNull;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import com.bouncestorage.swiftproxy.BlobStoreResource;
import com.bouncestorage.swiftproxy.BounceResourceConfig;
import com.bouncestorage.swiftproxy.Utils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
import com.google.common.base.Strings;

import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.domain.StorageType;
import org.jclouds.blobstore.options.ListContainerOptions;

@Path("/v1/{account}/{container}")
public final class ContainerResource extends BlobStoreResource {

    private void createContainer(String authToken, String container) {
        if (container.length() > InfoResource.CONFIG.swift.max_container_name_length) {
            throw new BadRequestException("container name too long");
        }

        getBlobStore(authToken).get(container).createContainerInLocation(null, container);
    }

    @POST
    public Response postContainer(@NotNull @PathParam("container") String container,
            @HeaderParam("X-Auth-Token") String authToken, @HeaderParam("X-Container-Read") String readACL,
            @HeaderParam("X-Container-write") String writeACL, @HeaderParam("X-Container-Sync-To") String syncTo,
            @HeaderParam("X-Container-Sync-Key") String syncKey,
            @HeaderParam("X-Versions-Location") String versionsLocation,
            @HeaderParam(HttpHeaders.CONTENT_TYPE) String contentType,
            @HeaderParam("X-Detect-Content-Type") boolean detectContentType,
            @HeaderParam(HttpHeaders.IF_NONE_MATCH) String ifNoneMatch) {
        createContainer(authToken, container);
        return Response.status(Response.Status.NO_CONTENT).build();
    }

    @PUT
    public Response putContainer(@NotNull @PathParam("container") String container,
            @HeaderParam("X-Auth-Token") String authToken, @HeaderParam("X-Container-Read") String readACL,
            @HeaderParam("X-Container-write") String writeACL, @HeaderParam("X-Container-Sync-To") String syncTo,
            @HeaderParam("X-Container-Sync-Key") String syncKey,
            @HeaderParam("X-Versions-Location") String versionsLocation,
            @HeaderParam(HttpHeaders.CONTENT_TYPE) String contentType,
            @HeaderParam("X-Detect-Content-Type") boolean detectContentType,
            @HeaderParam(HttpHeaders.IF_NONE_MATCH) String ifNoneMatch) {
        Response.Status status;
        BlobStore store = getBlobStore(authToken).get(container);

        if (store.containerExists(container)) {
            status = Response.Status.ACCEPTED;
        } else {
            createContainer(authToken, container);
            status = Response.Status.CREATED;
        }

        return Response.status(status).build();
    }

    @DELETE
    public Response deleteContainer(@NotNull @PathParam("container") String container,
            @HeaderParam("X-Auth-Token") String authToken) {
        BlobStore store = getBlobStore(authToken).get(container);
        if (!store.containerExists(container)) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }

        if (store.deleteContainerIfEmpty(container)) {
            return Response.noContent().build();
        } else {
            return Response.status(Response.Status.CONFLICT).entity(
                    "<html><h1>Conflict</h1><p>There was a conflict when trying to complete your request.</p></html>")
                    .build();
        }
    }

    @HEAD
    public Response headContainer(@NotNull @PathParam("container") String container,
            @HeaderParam("X-Auth-Token") String authToken,
            @HeaderParam("X-Newest") @DefaultValue("false") boolean newest) {
        BlobStore store = getBlobStore(authToken).get(container);
        if (!store.containerExists(container)) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }

        long objectCount = -1;
        String provider = store.getContext().unwrap().getId();
        if (provider.equals("transient") || provider.equals("openstack-swift")) {
            objectCount = store.countBlobs(container);
        }

        return Response.status(Response.Status.NO_CONTENT).entity("")
                .header("X-Container-Object-Count", objectCount).header("X-Container-Bytes-Used", 0) // TODO: bogus value
                .header("X-Versions-Location", "").header("X-Timestamp", -1).header("X-Trans-Id", -1)
                .header("Accept-Ranges", "bytes").build();
    }

    private String contentType(StorageMetadata meta) {
        if (meta instanceof BlobMetadata) {
            String contentType = ((BlobMetadata) meta).getContentMetadata().getContentType();
            if (contentType != null && !contentType.isEmpty()) {
                return contentType;
            }
        }
        if (meta.getType().equals(StorageType.RELATIVE_PATH) || meta.getName().endsWith("/")) {
            return "application/directory";
        }
        return MediaType.APPLICATION_OCTET_STREAM;
    }

    @GET
    public Response listContainer(@NotNull @PathParam("container") String container,
            @HeaderParam("X-Auth-Token") String authToken, @QueryParam("limit") Integer limit,
            @QueryParam("marker") String marker, @QueryParam("end_marker") String endMarker,
            @QueryParam("format") Optional<String> format, @QueryParam("prefix") String prefixParam,
            @QueryParam("delimiter") String delimiterParam, @QueryParam("path") String path,
            @HeaderParam("X-Newest") @DefaultValue("false") boolean newest,
            @HeaderParam("Accept") Optional<String> accept) {
        BlobStore store = getBlobStore(authToken).get(container);
        if (!store.containerExists(container)) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }

        ListContainerOptions options = new ListContainerOptions();
        if (!Strings.isNullOrEmpty(marker)) {
            options.afterMarker(marker);
        }

        if (Strings.isNullOrEmpty(delimiterParam) && path == null) {
            options.recursive();
        }

        if (!Strings.isNullOrEmpty(delimiterParam)) {
            options.delimiter(delimiterParam);
        }

        if (!Strings.isNullOrEmpty(prefixParam)) {
            options.prefix(prefixParam);
        }

        if (path != null) {
            if (path.equals("/")) {
                options.prefix("/");
                options.delimiter("/");
            } else {
                options.inDirectory(path);
            }
        }

        logger.info("list: {} marker={} prefix={}", options, options.getMarker(), prefixParam);
        List<ObjectEntry> entries = StreamSupport
                .stream(Utils.crawlBlobStore(store, container, options).spliterator(), false)
                .peek(meta -> logger.debug("meta: {}", meta))
                //.filter(meta -> (prefix == null || meta.getName().startsWith(prefix)))
                //.filter(meta -> delimFilter(meta.getName(), delim_filter))
                .filter(meta -> endMarker == null || meta.getName().compareTo(endMarker) < 0)
                .limit(limit == null ? InfoResource.CONFIG.swift.container_listing_limit : limit)
                .map(meta -> new ObjectEntry(meta.getName(), meta.getETag(),
                        meta.getSize() == null ? 0 : meta.getSize(), contentType(meta), meta.getLastModified()))
                .collect(Collectors.toList());

        MediaType formatType;
        if (format.isPresent()) {
            formatType = BounceResourceConfig.getMediaType(format.get());
        } else if (accept.isPresent()) {
            formatType = MediaType.valueOf(accept.get());
        } else {
            formatType = MediaType.TEXT_PLAIN_TYPE;
        }

        if (store.getContext().unwrap().getId().equals("transient")) {
            entries.forEach(entry -> {
                try {
                    entry.name = URLDecoder.decode(entry.name, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    throw propagate(e);
                }
            });
        }

        // XXX semi-bogus value
        long totalBytes = entries.stream().mapToLong(e -> e.bytes).sum();

        ContainerRoot root = new ContainerRoot();
        root.name = container;
        root.object = entries;
        return output(root, entries, formatType).header("X-Container-Object-Count", entries.size())
                .header("X-Container-Bytes-Used", totalBytes).header("X-Timestamp", -1).header("X-Trans-Id", -1)
                .header("Accept-Ranges", "bytes").build();

    }

    @XmlRootElement(name = "container")
    @XmlType
    static class ContainerRoot {
        @XmlElement
        List<ObjectEntry> object;
        @XmlAttribute
        private String name;
    }

    @XmlRootElement(name = "object")
    @XmlType
    static class ObjectEntry {
        static class SwiftDateSerializer extends DateSerializer {
            SwiftDateSerializer() {
                super(false, new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss.SSSSSS"));
            }
        }

        @XmlElement
        String name;
        @XmlElement
        String hash;
        @XmlElement
        long bytes;
        @XmlElement
        String content_type;
        @JsonSerialize(using = SwiftDateSerializer.class)
        @XmlElement
        Date last_modified;

        // dummy
        public ObjectEntry() {
        }

        @JsonCreator
        public ObjectEntry(@JsonProperty("name") String name, @JsonProperty("hash") String hash,
                @JsonProperty("bytes") long bytes, @JsonProperty("content_type") String content_type,
                @JsonProperty("last_modified") Date last_modified) {
            this.name = requireNonNull(name);
            this.hash = hash == null ? "" : Utils.trimETag(hash);
            this.bytes = bytes;
            this.content_type = requireNonNull(content_type);
            this.last_modified = last_modified == null ? Date.from(Instant.EPOCH) : last_modified;
        }

        @Override
        public String toString() {
            return name;
        }
    }
}