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

Java tutorial

Introduction

Here is the source code for com.bouncestorage.swiftproxy.v1.AccountResource.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.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
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.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
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.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.net.PercentEscaper;

import org.glassfish.grizzly.http.server.Request;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.domain.StorageMetadata;

@Singleton
@Path("/v1/{account}")
public final class AccountResource extends BlobStoreResource {
    private static final PercentEscaper BULK_DELETE_ESCAPER = new PercentEscaper("-_.~:/", false);

    @GET
    public Response getAccount(@NotNull @PathParam("account") String account,
            @QueryParam("limit") Optional<Integer> limit, @QueryParam("marker") Optional<String> marker,
            @QueryParam("end_marker") Optional<String> endMarker, @QueryParam("format") Optional<String> format,
            @QueryParam("prefix") Optional<String> prefix, @QueryParam("delimiter") Optional<String> delimiter,
            @HeaderParam("X-Auth-Token") String authToken,
            @HeaderParam("X-Newest") @DefaultValue("false") boolean newest,
            @HeaderParam("Accept") Optional<String> accept) {
        delimiter.ifPresent(x -> logger.info("delimiter not supported yet"));

        BlobStore blobStore = getBlobStore(authToken).get();
        ArrayList<ContainerEntry> entries = blobStore.list().stream().map(StorageMetadata::getName)
                .filter(name -> marker.map(m -> name.compareTo(m) > 0).orElse(true))
                .filter(name -> endMarker.map(m -> name.compareTo(m) < 0).orElse(true))
                .filter(name -> prefix.map(name::startsWith).orElse(true)).map(ContainerEntry::new)
                .collect(Collectors.toCollection(ArrayList::new));

        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 (blobStore.getContext().unwrap().getId().equals("transient")) {
            entries.sort((a, b) -> a.getName().compareTo(b.getName()));
        }

        long count = entries.size();
        limit.ifPresent((max) -> {
            if (entries.size() > max) {
                entries.subList(max, entries.size()).clear();
            }
        });

        Account root = new Account();
        root.name = account;
        root.container = entries;
        return output(root, entries, formatType).header("X-Account-Container-Count", count)
                .header("X-Account-Object-Count", -1).header("X-Account-Bytes-Used", -1).header("X-Timestamp", -1)
                .header("X-Trans-Id", -1).header("Accept-Ranges", "bytes").build();
    }

    @HEAD
    public Response headAccount(@NotNull @PathParam("account") String account,
            @HeaderParam("X-Auth-Token") String authToken, @HeaderParam("X-Newest") boolean newest) {
        return Response.noContent().header("X-Account-Container-Count", -1).header("X-Account-Object-Count", -1)
                .header("X-Account-Bytes-Used", -1).header("X-Timestamp", -1).header("X-Trans-Id", -1)
                .header("Accept-Ranges", "bytes").build();
    }

    @DELETE
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
    public BulkDeleteResult bulkDeleteDelete(@NotNull @PathParam("account") String account,
            @QueryParam("bulk-delete") String bulkDelete, @HeaderParam("X-Auth-Token") String authToken,
            @Context Request request) throws JsonProcessingException {
        return bulkDelete(account, bulkDelete, authToken, request);
    }

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
    public BulkDeleteResult bulkDelete(@NotNull @PathParam("account") String account,
            @QueryParam("bulk-delete") String bulkDelete, @HeaderParam("X-Auth-Token") String authToken,
            @Context Request request) throws JsonProcessingException {
        if (bulkDelete == null) {
            // TODO: Currently this will match the account delete request as well, which we do not implement
            throw new WebApplicationException(Response.Status.NOT_IMPLEMENTED);
        }

        BlobStore blobStore = getBlobStore(authToken).get();
        if (blobStore == null) {
            throw new WebApplicationException(Response.Status.BAD_REQUEST);
        }

        String line;
        ArrayList<String> objects = new ArrayList<>();

        boolean isTransient = blobStore.getContext().unwrap().getId().equals("transient");
        try (BufferedReader in = new BufferedReader(
                new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) {
            while ((line = in.readLine()) != null) {
                if (isTransient) {
                    // jclouds does not escape things correctly
                    line = BULK_DELETE_ESCAPER.escape(URLDecoder.decode(line, "UTF-8"));
                }
                objects.add(line);
            }
        } catch (IOException e) {
            throw propagate(e);
        }

        BulkDeleteResult result = new BulkDeleteResult();
        for (String objectContainer : objects) {
            try {
                if (objectContainer.startsWith("/")) {
                    objectContainer = objectContainer.substring(1);
                }
                int separatorIndex = objectContainer.indexOf('/');
                if (separatorIndex < 0) {
                    blobStore.deleteContainer(objectContainer.substring(1));
                    result.numberDeleted += 1;
                    continue;
                }
                String container = objectContainer.substring(0, separatorIndex);
                String object = objectContainer.substring(separatorIndex + 1);

                if (!blobStore.blobExists(container, object)) {
                    result.numberNotFound += 1;
                } else {
                    blobStore.removeBlob(container, object);
                    result.numberDeleted += 1;
                }
            } catch (ContainerNotFoundException e) {
                result.numberNotFound += 1;
            } catch (Exception e) {
                e.printStackTrace();
                result.errors.add(objectContainer);
            }
        }

        if (result.errors.isEmpty()) {
            result.responseStatus = Response.Status.OK.toString();
            return result;
        } else {
            ObjectMapper mapper = new ObjectMapper();
            result.responseStatus = Response.Status.BAD_GATEWAY.toString();
            throw new WebApplicationException(mapper.writeValueAsString(result), Response.Status.BAD_GATEWAY);
        }
    }

    @XmlRootElement(name = "account")
    @XmlType
    static class Account {
        @XmlElement
        List<ContainerEntry> container;
        @XmlAttribute
        private String name;
    }

    @XmlRootElement(name = "container")
    @XmlType
    static class ContainerEntry {
        @XmlElement
        private String name;
        @XmlElement
        private long count;
        @XmlElement
        private long bytes;

        // for jackson XML
        public ContainerEntry() {

        }

        @JsonCreator
        public ContainerEntry(@JsonProperty("name") String name) {
            this.name = requireNonNull(name);
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof ContainerEntry && name.equals(((ContainerEntry) other).name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, count, bytes);
        }

        public String getName() {
            return name;
        }

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

    static class BulkDeleteResult {
        @JsonProperty("Response Status")
        String responseStatus;
        @JsonProperty("Errors")
        ArrayList<String> errors;
        @JsonProperty("Number Deleted")
        int numberDeleted;
        @JsonProperty("Number Not Found")
        int numberNotFound;

        BulkDeleteResult() {
            errors = new ArrayList<>();
            numberDeleted = 0;
            numberNotFound = 0;
        }
    }
}