diff --git a/src/main/java/com/google/gcloud/examples/StorageExample.java b/src/main/java/com/google/gcloud/examples/StorageExample.java index 8b1a79b37212..b6f54efa1d93 100644 --- a/src/main/java/com/google/gcloud/examples/StorageExample.java +++ b/src/main/java/com/google/gcloud/examples/StorageExample.java @@ -46,7 +46,7 @@ /** * An example of using the Google Cloud Storage. *

- * This example demonstrates a simple/typical usage. + * This example demonstrates a simple/typical storage usage. *

* Steps needed for running the example: *

    @@ -57,9 +57,14 @@ * -Dexec.args="[] list []| info [ []]| * download [local_file]| upload []| * delete +| cp | - * compose + "} + * compose + | update_metadata [key=value]*"} * *
+ * + * The first parameter is an optional project_id (logged-in project will be used if not supplied). + * Second parameter is a Storage operation (list, delete, compose,...) to demonstrate the its + * usage. Any other arguments are specific to the operation. + * See each action's run method for the specific Storage interaction. */ public class StorageExample { @@ -112,24 +117,36 @@ public String params() { } } + /** + * This class demonstrates how to retrieve Bucket or Blob metadata. + * If more than one blob is supplied a Batch operation would be used to get all blobs metadata + * in a single RPC. + * + * @see Objects: get + */ private static class InfoAction extends BlobsAction { @Override public void run(StorageService storage, Blob... blobs) { - - if (blobs.length == 1) { if (blobs[0].name().isEmpty()) { - System.out.println(storage.get(blobs[0].bucket())); + // get Bucket + Bucket bucket = storage.get(blobs[0].bucket()); + System.out.println("Bucket info: " + bucket); } else { - System.out.println(storage.get(blobs[0].bucket(), blobs[0].name())); + // get Blob + Blob blob = storage.get(blobs[0].bucket(), blobs[0].name()); + System.out.println("Blob info: " + blob); } } else { + // use batch to get multiple blobs. BatchRequest.Builder batch = BatchRequest.builder(); for (Blob blob : blobs) { batch.get(blob.bucket(), blob.name()); } BatchResponse response = storage.apply(batch.build()); - System.out.println(response.gets()); + for (BatchResponse.Result result : response.gets()) { + System.out.println(result.get()); + } } } @@ -147,22 +164,45 @@ public String params() { } } + /** + * This class demonstrates how to delete a blob. + * If more than one blob is supplied a Batch operation would be used to delete all requested + * blobs in a single RPC. + * + * @see Objects: delete + */ private static class DeleteAction extends BlobsAction { @Override public void run(StorageService storage, Blob... blobs) { if (blobs.length == 1) { - System.out.println(storage.delete(blobs[0].bucket(), blobs[0].name())); + boolean wasDeleted = storage.delete(blobs[0].bucket(), blobs[0].name()); + if (wasDeleted) { + System.out.println("Blob " + blobs[0] + " was deleted"); + } } else { + // use batch operation BatchRequest.Builder batch = BatchRequest.builder(); for (Blob blob : blobs) { batch.delete(blob.bucket(), blob.name()); } + int index = 0; BatchResponse response = storage.apply(batch.build()); - System.out.println(response.deletes()); + for (BatchResponse.Result result : response.deletes()) { + if (result.get()) { + // request order is maintained + System.out.println("Blob " + blobs[index] + " was deleted"); + } + index++; + } } } } + /** + * This class demonstrates how to list buckets or a bucket's blobs. + * + * @see Objects: list + */ private static class ListAction extends StorageAction { @Override @@ -179,10 +219,12 @@ String parse(String... args) { @Override public void run(StorageService storage, String bucket) { if (bucket == null) { + // list buckets for (Bucket b : storage.list()) { System.out.println(b); } } else { + // list a bucket's blobs for (Blob b : storage.list(bucket)) { System.out.println(b); } @@ -195,13 +237,24 @@ public String params() { } } + /** + * This class demonstrates how to create a new Blob or to update its content. + * + * @see Objects: insert + */ private static class UploadAction extends StorageAction> { @Override public void run(StorageService storage, Tuple tuple) throws Exception { - if (Files.size(tuple.x()) > 1024) { - try (BlobWriteChannel writer = storage.writer(tuple.y())) { + run(storage, tuple.x(), tuple.y()); + } + + private void run(StorageService storage, Path uploadFrom, Blob blob) throws IOException { + if (Files.size(uploadFrom) > 1_000_000) { + // When content is not available or large (1MB or more) it is recommended + // to write it in chunks via the blob's channel writer. + try (BlobWriteChannel writer = storage.writer(blob)) { byte[] buffer = new byte[1024]; - try (InputStream input = Files.newInputStream(tuple.x())) { + try (InputStream input = Files.newInputStream(uploadFrom)) { int limit; while ((limit = input.read(buffer)) >= 0) { try { @@ -213,9 +266,11 @@ public void run(StorageService storage, Tuple tuple) throws Exceptio } } } else { - byte[] bytes = Files.readAllBytes(tuple.x()); - System.out.println(storage.create(tuple.y(), bytes)); + byte[] bytes = Files.readAllBytes(uploadFrom); + // create the blob in one request. + storage.create(blob, bytes); } + System.out.println("Blob was created"); } @Override @@ -235,22 +290,37 @@ public String params() { } } + /** + * This class demonstrates how read a blob's content. + * The example will dump the content to a local file if one was given or write + * it to stdout otherwise. + * + * @see Objects: get + */ private static class DownloadAction extends StorageAction> { @Override public void run(StorageService storage, Tuple tuple) throws IOException { - Blob blob = storage.get(tuple.x().bucket(), tuple.x().name()); + run(storage, tuple.x().bucket(), tuple.x().name(), tuple.y()); + } + + private void run(StorageService storage, String bucket, String blobName, Path downloadTo) + throws IOException { + Blob blob = storage.get(bucket, blobName); if (blob == null) { System.out.println("No such object"); return; } PrintStream writeTo = System.out; - if (tuple.y() != null) { - writeTo = new PrintStream(new FileOutputStream(tuple.y().toFile())); + if (downloadTo != null) { + writeTo = new PrintStream(new FileOutputStream(downloadTo.toFile())); } - if (blob.size() < 1024) { - writeTo.write(storage.load(blob.bucket(), blob.name())); + if (blob.size() < 1_000_000) { + // Blob is small read all its content in one request + byte[] content = storage.load(blob.bucket(), blob.name()); + writeTo.write(content); } else { + // When Blob size is big or unknown use the blob's channel reader. try (BlobReadChannel reader = storage.reader(blob.bucket(), blob.name())) { WritableByteChannel channel = Channels.newChannel(writeTo); ByteBuffer bytes = ByteBuffer.allocate(64 * 1024); @@ -261,7 +331,7 @@ public void run(StorageService storage, Tuple tuple) throws IOExcept } } } - if (tuple.y() == null) { + if (downloadTo == null) { writeTo.println(); } else { writeTo.close(); @@ -291,10 +361,16 @@ public String params() { } } + /** + * This class demonstrates how to use the copy command. + * + * @see Objects: copy + */ private static class CopyAction extends StorageAction { @Override public void run(StorageService storage, CopyRequest request) { - System.out.println(storage.copy(request)); + Blob copiedBlob = storage.copy(request); + System.out.println("Copied " + copiedBlob); } @Override @@ -311,10 +387,16 @@ public String params() { } } + /** + * This class demonstrates how to use the compose command. + * + * @see Objects: compose + */ private static class ComposeAction extends StorageAction { @Override public void run(StorageService storage, ComposeRequest request) { - System.out.println(storage.compose(request)); + Blob composedBlob = storage.compose(request); + System.out.println("Composed " + composedBlob); } @Override @@ -336,6 +418,54 @@ public String params() { } } + /** + * This class demonstrates how to update a blob's metadata. + * + * @see Objects: update + */ + private static class UpdateMetadata extends StorageAction>> { + + @Override + public void run(StorageService storage, Tuple> tuple) + throws IOException { + run(storage, tuple.x().bucket(), tuple.x().name(), tuple.y()); + } + + private void run(StorageService storage, String bucket, String blobName, + Map metadata) { + Blob blob = storage.get(bucket, blobName); + if (blob == null) { + System.out.println("No such object"); + return; + } + blob = storage.update(blob.toBuilder().metadata(metadata).build()); + System.out.println("Updated " + blob); + } + + @Override + Tuple> parse(String... args) { + if (args.length < 2) { + throw new IllegalArgumentException(); + } + Blob blob = Blob.of(args[0], args[1]); + Map metadata = new HashMap<>(); + for (int i = 2; i < args.length; i++) { + int idx = args[i].indexOf('='); + if (idx < 0) { + metadata.put(args[i], ""); + } else { + metadata.put(args[i].substring(0, idx), args[i].substring(idx + 1)); + } + } + return Tuple.of(blob, metadata); + } + + @Override + public String params() { + return " [local_file]"; + } + } + static { ACTIONS.put("info", new InfoAction()); ACTIONS.put("delete", new DeleteAction()); @@ -344,6 +474,7 @@ public String params() { ACTIONS.put("download", new DownloadAction()); ACTIONS.put("cp", new CopyAction()); ACTIONS.put("compose", new ComposeAction()); + ACTIONS.put("update_metadata", new UpdateMetadata()); } public static void printUsage() { @@ -378,7 +509,7 @@ public static void main(String... args) throws Exception { args = Arrays.copyOfRange(args, 1, args.length); } if (action == null) { - System.out.println("Unrecognized action '" + args[1] + "'"); + System.out.println("Unrecognized action."); printUsage(); return; } diff --git a/src/main/java/com/google/gcloud/storage/Acl.java b/src/main/java/com/google/gcloud/storage/Acl.java index 138ba4f3fa3a..b5bb685334c1 100644 --- a/src/main/java/com/google/gcloud/storage/Acl.java +++ b/src/main/java/com/google/gcloud/storage/Acl.java @@ -20,6 +20,7 @@ import com.google.api.services.storage.model.ObjectAccessControl; import java.io.Serializable; +import java.util.Objects; /** * Access Control List on for buckets or blobs. @@ -59,6 +60,19 @@ protected String value() { return value; } + @Override + public int hashCode() { + return Objects.hash(type, value); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { + return false; + } + return Objects.equals(toPb(), ((Entity)obj).toPb()); + } + @Override public String toString() { return toPb(); @@ -91,13 +105,12 @@ static Entity fromPb(String entity) { } } - public static class Domain extends Entity { + public static final class Domain extends Entity { private static final long serialVersionUID = -3033025857280447253L; public Domain(String domain) { super(Type.DOMAIN, domain); - } public String domain() { @@ -105,7 +118,7 @@ public String domain() { } } - public static class Group extends Entity { + public static final class Group extends Entity { private static final long serialVersionUID = -1660987136294408826L; @@ -118,7 +131,7 @@ public String email() { } } - public static class User extends Entity { + public static final class User extends Entity { private static final long serialVersionUID = 3076518036392737008L; private static final String ALL_USERS = "allUsers"; @@ -154,7 +167,7 @@ public static User ofAllAuthenticatedUsers() { } } - public static class Project extends Entity { + public static final class Project extends Entity { private static final long serialVersionUID = 7933776866530023027L; @@ -180,7 +193,7 @@ public String projectId() { } } - public static class RawEntity extends Entity { + public static final class RawEntity extends Entity { private static final long serialVersionUID = 3966205614223053950L; diff --git a/src/main/java/com/google/gcloud/storage/BatchRequest.java b/src/main/java/com/google/gcloud/storage/BatchRequest.java index 9fbee876cf45..6f62d5c51ae4 100644 --- a/src/main/java/com/google/gcloud/storage/BatchRequest.java +++ b/src/main/java/com/google/gcloud/storage/BatchRequest.java @@ -17,51 +17,56 @@ package com.google.gcloud.storage; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.gcloud.storage.StorageService.BlobSourceOption; import com.google.gcloud.storage.StorageService.BlobTargetOption; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; /** * Google storage batch request. */ -public class BatchRequest implements Serializable { +public final class BatchRequest implements Serializable { private static final long serialVersionUID = -1527992265939800345L; - private final Map toDelete; - private final Map toUpdate; - private final Map toGet; + private final Map> toDelete; + private final Map> toUpdate; + private final Map> toGet; public static class Builder { - private Map toDelete = new LinkedHashMap<>(); - private Map toUpdate = new LinkedHashMap<>(); - private Map toGet = new LinkedHashMap<>(); + private Map> toDelete = new LinkedHashMap<>(); + private Map> toUpdate = new LinkedHashMap<>(); + private Map> toGet = new LinkedHashMap<>(); private Builder() {} /** * Delete the given blob. */ - public void delete(String bucket, String blob, BlobSourceOption... options) { - toDelete.put(Blob.of(bucket, blob), options); + public Builder delete(String bucket, String blob, BlobSourceOption... options) { + toDelete.put(Blob.of(bucket, blob), Lists.newArrayList(options)); + return this; } /** * Update the given blob. */ - public void update(Blob blob, BlobTargetOption... options) { - toUpdate.put(blob, options); + public Builder update(Blob blob, BlobTargetOption... options) { + toUpdate.put(blob, Lists.newArrayList(options)); + return this; } /** * Retrieve metadata for the given blob. */ - public void get(String bucket, String blob, BlobSourceOption... options) { - toGet.put(Blob.of(bucket, blob), options); + public Builder get(String bucket, String blob, BlobSourceOption... options) { + toGet.put(Blob.of(bucket, blob), Lists.newArrayList(options)); + return this; } public BatchRequest build() { @@ -75,15 +80,31 @@ private BatchRequest(Builder builder) { toGet = ImmutableMap.copyOf(builder.toGet); } - Map toDelete() { + @Override + public int hashCode() { + return Objects.hash(toDelete, toUpdate, toGet); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BatchRequest)) { + return false; + } + BatchRequest other = (BatchRequest) obj; + return Objects.equals(toDelete, other.toDelete) + && Objects.equals(toUpdate, other.toUpdate) + && Objects.equals(toGet, other.toGet); + } + + Map> toDelete() { return toDelete; } - Map toUpdate() { + Map> toUpdate() { return toUpdate; } - Map toGet() { + Map> toGet() { return toGet; } diff --git a/src/main/java/com/google/gcloud/storage/BatchResponse.java b/src/main/java/com/google/gcloud/storage/BatchResponse.java index fab4d964e1c6..f0675e348f72 100644 --- a/src/main/java/com/google/gcloud/storage/BatchResponse.java +++ b/src/main/java/com/google/gcloud/storage/BatchResponse.java @@ -21,11 +21,12 @@ import java.io.Serializable; import java.util.List; +import java.util.Objects; /** * Google Storage batch response. */ -public class BatchResponse implements Serializable { +public final class BatchResponse implements Serializable { private static final long serialVersionUID = 1057416839397037706L; @@ -57,7 +58,7 @@ public static class Result implements Serializable { * * @throws StorageServiceException if failed */ - public T result() throws StorageServiceException { + public T get() throws StorageServiceException { if (failed()) { throw failure(); } @@ -78,6 +79,21 @@ public boolean failed() { return exception != null; } + @Override + public int hashCode() { + return Objects.hash(value, exception); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Result)) { + return false; + } + Result other = (Result) obj; + return Objects.equals(value, other.value) + && Objects.equals(exception, other.exception); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -99,6 +115,22 @@ static Result empty() { this.getResult = ImmutableList.copyOf(getResult); } + @Override + public int hashCode() { + return Objects.hash(deleteResult, updateResult, getResult); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BatchResponse)) { + return false; + } + BatchResponse other = (BatchResponse) obj; + return Objects.equals(deleteResult, other.deleteResult) + && Objects.equals(updateResult, other.updateResult) + && Objects.equals(updateResult, other.updateResult); + } + /** * Returns the results for the delete operations using the request order. */ diff --git a/src/main/java/com/google/gcloud/storage/Blob.java b/src/main/java/com/google/gcloud/storage/Blob.java index 49d763b0f65a..31b88981187d 100644 --- a/src/main/java/com/google/gcloud/storage/Blob.java +++ b/src/main/java/com/google/gcloud/storage/Blob.java @@ -16,8 +16,10 @@ package com.google.gcloud.storage; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.client.util.Data; import com.google.api.client.util.DateTime; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.StorageObject; @@ -32,13 +34,14 @@ import java.math.BigInteger; import java.util.List; import java.util.Map; +import java.util.Objects; /** * A Google Storage object. * * @see Concepts and Terminology */ -public class Blob implements Serializable { +public final class Blob implements Serializable { private static final long serialVersionUID = 2228487739943277159L; @@ -123,22 +126,22 @@ public Builder name(String name) { } public Builder contentType(String contentType) { - this.contentType = contentType; + this.contentType = firstNonNull(contentType, Data.nullOf(String.class)); return this; } - Builder contentDisposition(String contentDisposition) { - this.contentDisposition = contentDisposition; + public Builder contentDisposition(String contentDisposition) { + this.contentDisposition = firstNonNull(contentDisposition, Data.nullOf(String.class)); return this; } - Builder contentLanguage(String contentLanguage) { - this.contentLanguage = contentLanguage; + public Builder contentLanguage(String contentLanguage) { + this.contentLanguage = firstNonNull(contentLanguage, Data.nullOf(String.class)); return this; } public Builder contentEncoding(String contentEncoding) { - this.contentEncoding = contentEncoding; + this.contentEncoding = firstNonNull(contentEncoding, Data.nullOf(String.class)); return this; } @@ -148,7 +151,7 @@ Builder componentCount(Integer componentCount) { } public Builder cacheControl(String cacheControl) { - this.cacheControl = cacheControl; + this.cacheControl = firstNonNull(cacheControl, Data.nullOf(String.class)); return this; } @@ -157,12 +160,12 @@ public Builder acl(List acl) { return this; } - public Builder owner(Acl.Entity owner) { + Builder owner(Acl.Entity owner) { this.owner = owner; return this; } - public Builder size(Long size) { + Builder size(Long size) { this.size = size; return this; } @@ -177,17 +180,17 @@ Builder selfLink(String selfLink) { return this; } - Builder md5(String md5) { - this.md5 = md5; + public Builder md5(String md5) { + this.md5 = firstNonNull(md5, Data.nullOf(String.class)); return this; } public Builder crc32c(String crc32c) { - this.crc32c = crc32c; + this.crc32c = firstNonNull(crc32c, Data.nullOf(String.class)); return this; } - public Builder mediaLink(String mediaLink) { + Builder mediaLink(String mediaLink) { this.mediaLink = mediaLink; return this; } @@ -197,22 +200,22 @@ public Builder metadata(Map metadata) { return this; } - public Builder generation(Long generation) { + Builder generation(Long generation) { this.generation = generation; return this; } - public Builder metageneration(Long metageneration) { + Builder metageneration(Long metageneration) { this.metageneration = metageneration; return this; } - public Builder deleteTime(Long deleteTime) { + Builder deleteTime(Long deleteTime) { this.deleteTime = deleteTime; return this; } - public Builder updateTime(Long updateTime) { + Builder updateTime(Long updateTime) { this.updateTime = updateTime; return this; } @@ -262,7 +265,7 @@ public String name() { } public String cacheControl() { - return cacheControl; + return Data.isNull(cacheControl) ? null : cacheControl; } public List acl() { @@ -278,19 +281,19 @@ public Long size() { } public String contentType() { - return contentType; + return Data.isNull(contentType) ? null : contentType; } public String contentEncoding() { - return contentEncoding; + return Data.isNull(contentEncoding) ? null : contentEncoding; } public String contentDisposition() { - return contentDisposition; + return Data.isNull(contentDisposition) ? null : contentDisposition; } public String contentLanguage() { - return contentEncoding; + return Data.isNull(contentLanguage) ? null : contentLanguage; } public Integer componentCount() { @@ -306,11 +309,11 @@ public String selfLink() { } public String md5() { - return md5; + return Data.isNull(md5) ? null : md5; } public String crc32c() { - return crc32c; + return Data.isNull(crc32c) ? null : crc32c; } public String mediaLink() { @@ -365,7 +368,13 @@ public Builder toBuilder() { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("bucket", bucket).add("name", name).toString(); + return MoreObjects.toStringHelper(this) + .add("bucket", bucket()) + .add("name", name()) + .add("size", size()) + .add("content-type", contentType()) + .add("metadata", metadata()) + .toString(); } public static Blob of(String bucket, String name) { @@ -380,6 +389,19 @@ public static Builder builder(String bucket, String name) { return new Builder().bucket(bucket).name(name); } + @Override + public int hashCode() { + return Objects.hash(bucket, name); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Blob)) { + return false; + } + return Objects.equals(toPb(), ((Blob) obj).toPb()); + } + StorageObject toPb() { StorageObject storageObject = new StorageObject(); if (acl != null) { diff --git a/src/main/java/com/google/gcloud/storage/Bucket.java b/src/main/java/com/google/gcloud/storage/Bucket.java index f6be9a78b2c6..793ae0199d5c 100644 --- a/src/main/java/com/google/gcloud/storage/Bucket.java +++ b/src/main/java/com/google/gcloud/storage/Bucket.java @@ -17,9 +17,11 @@ package com.google.gcloud.storage; import static com.google.api.client.repackaged.com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.Lists.transform; import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.Data; import com.google.api.client.util.DateTime; import com.google.api.services.storage.model.Bucket.Lifecycle; import com.google.api.services.storage.model.Bucket.Lifecycle.Rule; @@ -39,6 +41,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.List; +import java.util.Objects; /** * A Google Storage bucket. @@ -66,7 +69,6 @@ public final class Bucket implements Serializable { private final Location location; private final StorageClass storageClass; - static final Function FROM_PB_FUNCTION = new Function() { @Override @@ -247,6 +249,9 @@ public static final class StorageClass implements Serializable { private static final long serialVersionUID = 374002156285326563L; private static final ImmutableMap STRING_TO_OPTION; + private static final StorageClass NULL_VALUE = + new StorageClass(Data.nullOf(String.class)); + private final String value; public enum Option { @@ -298,6 +303,8 @@ public static final class Location implements Serializable { private static final long serialVersionUID = 9073107666838637662L; private static final ImmutableMap STRING_TO_OPTION; + private static final Location NULL_VALUE = new Location(Data.nullOf(String.class)); + private final String value; public enum Option { @@ -391,7 +398,7 @@ Builder selfLink(String selfLink) { } public Builder versioningEnabled(Boolean enable) { - this.versioningEnabled = enable; + this.versioningEnabled = firstNonNull(enable, Data.nullOf(Boolean.class)); return this; } @@ -411,12 +418,12 @@ public Builder deleteRules(Iterable rules) { } public Builder storageClass(StorageClass storageClass) { - this.storageClass = storageClass; + this.storageClass = firstNonNull(storageClass, StorageClass.NULL_VALUE); return this; } public Builder location(Location location) { - this.location = location; + this.location = firstNonNull(location, Location.NULL_VALUE); return this; } @@ -492,7 +499,7 @@ public String selfLink() { } public Boolean versioningEnabled() { - return versioningEnabled; + return Data.isNull(versioningEnabled) ? null : versioningEnabled; } public String indexPage() { @@ -520,11 +527,11 @@ public Long metageneration() { } public Location location() { - return location; + return location == null || Data.isNull(location.value) ? null : location; } public StorageClass storageClass() { - return storageClass; + return storageClass == null || Data.isNull(storageClass.value) ? null : storageClass; } public List cors() { @@ -559,9 +566,24 @@ public Builder toBuilder() { .deleteRules(deleteRules); } + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Bucket)) { + return false; + } + return Objects.equals(toPb(), ((Bucket) obj).toPb()); + } + @Override public String toString() { - return MoreObjects.toStringHelper(this).add("name", name).toString(); + return MoreObjects.toStringHelper(this) + .add("name", name()) + .toString(); } public static Bucket of(String name) { diff --git a/src/main/java/com/google/gcloud/storage/Cors.java b/src/main/java/com/google/gcloud/storage/Cors.java index 366c8d9223bd..9cedbc0d3b4c 100644 --- a/src/main/java/com/google/gcloud/storage/Cors.java +++ b/src/main/java/com/google/gcloud/storage/Cors.java @@ -29,6 +29,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.util.Objects; /** * Cross-Origin Resource Sharing (CORS) configuration for a bucket. @@ -60,7 +61,7 @@ public enum Method { ANY, GET, HEAD, PUT, POST, DELETE } - public static class Origin implements Serializable { + public static final class Origin implements Serializable { private static final long serialVersionUID = -4447958124895577993L; private static final String ANY_URI = "*"; @@ -91,6 +92,19 @@ public static Origin of(String value) { return new Origin(value); } + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Origin)) { + return false; + } + return value.equals(((Origin)obj).value); + } + @Override public String toString() { return value(); @@ -166,6 +180,23 @@ public Builder toBuilder() { .responseHeaders(responseHeaders); } + @Override + public int hashCode() { + return Objects.hash(maxAgeSeconds, methods, origins, responseHeaders); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Cors)) { + return false; + } + Cors other = (Cors) obj; + return Objects.equals(maxAgeSeconds, other.maxAgeSeconds) + && Objects.equals(methods, other.methods) + && Objects.equals(origins, other.origins) + && Objects.equals(responseHeaders, other.responseHeaders); + } + public static Builder builder() { return new Builder(); } diff --git a/src/main/java/com/google/gcloud/storage/ListResult.java b/src/main/java/com/google/gcloud/storage/ListResult.java index 180e5f7a4155..dd843020376e 100644 --- a/src/main/java/com/google/gcloud/storage/ListResult.java +++ b/src/main/java/com/google/gcloud/storage/ListResult.java @@ -18,11 +18,12 @@ import java.io.Serializable; import java.util.Iterator; +import java.util.Objects; /** * Google Cloud storage list result. */ -public class ListResult implements Iterable, Serializable { +public final class ListResult implements Iterable, Serializable { private static final long serialVersionUID = -6937287874908527950L; @@ -42,4 +43,19 @@ public String nextPageCursor() { public Iterator iterator() { return results.iterator(); } + + @Override + public int hashCode() { + return Objects.hash(cursor, results); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ListResult)) { + return false; + } + ListResult other = (ListResult) obj; + return Objects.equals(cursor, other.cursor) + && Objects.equals(results, other.results); + } } diff --git a/src/main/java/com/google/gcloud/storage/StorageService.java b/src/main/java/com/google/gcloud/storage/StorageService.java index 62c62ad8baaa..9e95b0f1281d 100644 --- a/src/main/java/com/google/gcloud/storage/StorageService.java +++ b/src/main/java/com/google/gcloud/storage/StorageService.java @@ -121,6 +121,10 @@ public static BlobTargetOption predefinedAcl(PredefinedAcl acl) { return new BlobTargetOption(StorageRpc.Option.PREDEFINED_ACL, acl.entry()); } + public static BlobTargetOption doesNotExists() { + return new BlobTargetOption(StorageRpc.Option.IF_GENERATION_MATCH, 0); + } + public static BlobTargetOption generationMatch() { return new BlobTargetOption(StorageRpc.Option.IF_GENERATION_MATCH); } diff --git a/src/main/java/com/google/gcloud/storage/StorageServiceImpl.java b/src/main/java/com/google/gcloud/storage/StorageServiceImpl.java index 42299f2bc6bd..1f66401105d0 100644 --- a/src/main/java/com/google/gcloud/storage/StorageServiceImpl.java +++ b/src/main/java/com/google/gcloud/storage/StorageServiceImpl.java @@ -28,6 +28,7 @@ import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_GENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_METAGENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_METAGENERATION_NOT_MATCH; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.util.concurrent.Executors.callable; import com.google.api.services.storage.model.StorageObject; @@ -131,7 +132,7 @@ public com.google.api.services.storage.model.Bucket call() { try { return storageRpc.get(bucketPb, optionsMap); } catch (StorageServiceException ex) { - if (ex.code() == 404) { + if (ex.code() == HTTP_NOT_FOUND) { return null; } throw ex; @@ -151,7 +152,7 @@ public StorageObject call() { try { return storageRpc.get(storedObject, optionsMap); } catch (StorageServiceException ex) { - if (ex.code() == 404) { + if (ex.code() == HTTP_NOT_FOUND) { return null; } throw ex; @@ -301,24 +302,27 @@ public byte[] call() { public BatchResponse apply(BatchRequest batchRequest) { List>> toDelete = Lists.newArrayListWithCapacity(batchRequest.toDelete().size()); - for (Map.Entry entry : batchRequest.toDelete().entrySet()) { + for (Map.Entry> entry : batchRequest.toDelete().entrySet()) { Blob blob = entry.getKey(); - Map optionsMap = optionMap(blob, entry.getValue()); + Map optionsMap = + optionMap(blob.generation(), blob.metageneration(), entry.getValue()); StorageObject storageObject = blob.toPb(); toDelete.add(Tuple.>of(storageObject, optionsMap)); } List>> toUpdate = Lists.newArrayListWithCapacity(batchRequest.toUpdate().size()); - for (Map.Entry entry : batchRequest.toUpdate().entrySet()) { + for (Map.Entry> entry : batchRequest.toUpdate().entrySet()) { Blob blob = entry.getKey(); - Map optionsMap = optionMap(blob, entry.getValue()); + Map optionsMap = + optionMap(blob.generation(), blob.metageneration(), entry.getValue()); toUpdate.add(Tuple.>of(blob.toPb(), optionsMap)); } List>> toGet = Lists.newArrayListWithCapacity(batchRequest.toGet().size()); - for (Map.Entry entry : batchRequest.toGet().entrySet()) { + for (Map.Entry> entry : batchRequest.toGet().entrySet()) { Blob blob = entry.getKey(); - Map optionsMap = optionMap(blob, entry.getValue()); + Map optionsMap = + optionMap(blob.generation(), blob.metageneration(), entry.getValue()); toGet.add(Tuple.>of(blob.toPb(), optionsMap)); } StorageRpc.BatchResponse response = @@ -328,7 +332,7 @@ public BatchResponse apply(BatchRequest batchRequest) { List> updates = transformBatchResult( toUpdate, response.updates, Blob.FROM_PB_FUNCTION); List> gets = transformBatchResult( - toGet, response.gets, Blob.FROM_PB_FUNCTION, 404); + toGet, response.gets, Blob.FROM_PB_FUNCTION, HTTP_NOT_FOUND); return new BatchResponse(deletes, updates, gets); } diff --git a/src/test/java/com/google/gcloud/storage/SerializationTest.java b/src/test/java/com/google/gcloud/storage/SerializationTest.java index 7e3cb3e89258..365462d3f69a 100644 --- a/src/test/java/com/google/gcloud/storage/SerializationTest.java +++ b/src/test/java/com/google/gcloud/storage/SerializationTest.java @@ -17,9 +17,11 @@ package com.google.gcloud.storage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import com.google.gcloud.AuthCredentials; import com.google.gcloud.RetryParams; +import com.google.gcloud.storage.Acl.Project.ProjectRole; import org.junit.Test; @@ -28,9 +30,40 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collections; public class SerializationTest { + private static final Acl.Domain ACL_DOMAIN = new Acl.Domain("domain"); + private static final Acl.Group ACL_GROUP = new Acl.Group("group"); + private static final Acl.Project ACL_PROJECT_ = new Acl.Project(ProjectRole.VIEWERS, "pid"); + private static final Acl.User ACL_USER = new Acl.User("user"); + private static final Acl.RawEntity ACL_RAW = new Acl.RawEntity("raw"); + private static final Blob BLOB = Blob.of("b", "n"); + private static final Bucket BUCKET = Bucket.of("b"); + private static final Cors.Origin ORIGIN = Cors.Origin.any(); + private static final Cors CORS = + Cors.builder().maxAgeSeconds(1).origins(Collections.singleton(ORIGIN)).build(); + private static final BatchRequest BATCH_REQUEST = BatchRequest.builder().delete("B", "N").build(); + private static final BatchResponse BATCH_RESPONSE = new BatchResponse( + Collections.singletonList(new BatchResponse.Result<>(true)), + Collections.>emptyList(), + Collections.>emptyList()); + private static final ListResult LIST_RESULT = + new ListResult<>("c", Collections.singletonList(Blob.of("b", "n"))); + private static StorageService.BlobListOption BLOB_LIST_OPTIONS = + StorageService.BlobListOption.maxResults(100); + private static StorageService.BlobSourceOption BLOB_SOURCE_OPTIONS = + StorageService.BlobSourceOption.generationMatch(1); + private static StorageService.BlobTargetOption BLOB_TARGET_OPTIONS = + StorageService.BlobTargetOption.generationMatch(); + private static StorageService.BucketListOption BUCKET_LIST_OPTIONS = + StorageService.BucketListOption.prefix("bla"); + private static StorageService.BucketSourceOption BUCKET_SOURCE_OPTIONS = + StorageService.BucketSourceOption.metagenerationMatch(1); + private static StorageService.BucketTargetOption BUCKET_TARGET_OPTIONS = + StorageService.BucketTargetOption.metagenerationNotMatch(); @Test public void testServiceOptions() throws Exception { @@ -52,8 +85,18 @@ public void testServiceOptions() throws Exception { } @Test - public void testTypes() throws Exception { - // todo: implement + public void testModelAndRequests() throws Exception { + Serializable[] objects = {ACL_DOMAIN, ACL_GROUP, ACL_PROJECT_, ACL_USER, ACL_RAW, BLOB, BUCKET, + ORIGIN, CORS, BATCH_REQUEST,BATCH_RESPONSE, LIST_RESULT, BLOB_LIST_OPTIONS, + BLOB_SOURCE_OPTIONS, BLOB_TARGET_OPTIONS, BUCKET_LIST_OPTIONS, BUCKET_SOURCE_OPTIONS, + BUCKET_TARGET_OPTIONS}; + for (Serializable obj : objects) { + Object copy = serializeAndDeserialize(obj); + assertEquals(obj, obj); + assertEquals(obj, copy); + assertNotSame(obj, copy); + assertEquals(copy, copy); + } } @SuppressWarnings("unchecked")