defaultAcl() {
* Returns a builder for the current bucket.
*/
public Builder toBuilder() {
- return new Builder()
- .name(name)
- .id(id)
- .createTime(createTime)
- .etag(etag)
- .metageneration(metageneration)
- .cors(cors)
- .acl(acl)
- .defaultAcl(defaultAcl)
- .location(location)
- .storageClass(storageClass)
- .owner(owner)
- .selfLink(selfLink)
- .versioningEnabled(versioningEnabled)
- .indexPage(indexPage)
- .notFoundPage(notFoundPage)
- .deleteRules(deleteRules);
+ return new Builder(this);
}
@Override
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java
index 2e95e69aa445..6e1c33b137fd 100644
--- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java
+++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java
@@ -33,6 +33,7 @@
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -473,14 +474,32 @@ class BlobSourceOption extends Option {
private static final long serialVersionUID = -3712768261070182991L;
- private BlobSourceOption(StorageRpc.Option rpcOption, long value) {
+ private BlobSourceOption(StorageRpc.Option rpcOption, Long value) {
super(rpcOption, value);
}
/**
* Returns an option for blob's data generation match. If this option is used the request will
- * fail if blob's generation does not match the provided value.
+ * fail if blob's generation does not match. The generation value to compare with the actual
+ * blob's generation is taken from a source {@link BlobId} object. When this option is passed
+ * to a {@link Storage} method and {@link BlobId#generation()} is {@code null} or no
+ * {@link BlobId} is provided an exception is thrown.
*/
+ public static BlobSourceOption generationMatch() {
+ return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_MATCH, null);
+ }
+
+ /**
+ * Returns an option for blob's data generation mismatch. If this option is used the request
+ * will fail if blob's generation matches. The generation value to compare with the actual
+ * blob's generation is taken from a source {@link BlobId} object. When this option is passed
+ * to a {@link Storage} method and {@link BlobId#generation()} is {@code null} or no
+ * {@link BlobId} is provided an exception is thrown.
+ */
+ public static BlobSourceOption generationNotMatch() {
+ return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_NOT_MATCH, null);
+ }
+
public static BlobSourceOption generationMatch(long generation) {
return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_MATCH, generation);
}
@@ -517,7 +536,7 @@ class BlobGetOption extends Option {
private static final long serialVersionUID = 803817709703661480L;
- private BlobGetOption(StorageRpc.Option rpcOption, long value) {
+ private BlobGetOption(StorageRpc.Option rpcOption, Long value) {
super(rpcOption, value);
}
@@ -527,8 +546,26 @@ private BlobGetOption(StorageRpc.Option rpcOption, String value) {
/**
* Returns an option for blob's data generation match. If this option is used the request will
- * fail if blob's generation does not match the provided value.
+ * fail if blob's generation does not match. The generation value to compare with the actual
+ * blob's generation is taken from a source {@link BlobId} object. When this option is passed
+ * to a {@link Storage} method and {@link BlobId#generation()} is {@code null} or no
+ * {@link BlobId} is provided an exception is thrown.
+ */
+ public static BlobGetOption generationMatch() {
+ return new BlobGetOption(StorageRpc.Option.IF_GENERATION_MATCH, (Long) null);
+ }
+
+ /**
+ * Returns an option for blob's data generation mismatch. If this option is used the request
+ * will fail if blob's generation matches. The generation value to compare with the actual
+ * blob's generation is taken from a source {@link BlobId} object. When this option is passed
+ * to a {@link Storage} method and {@link BlobId#generation()} is {@code null} or no
+ * {@link BlobId} is provided an exception is thrown.
*/
+ public static BlobGetOption generationNotMatch() {
+ return new BlobGetOption(StorageRpc.Option.IF_GENERATION_NOT_MATCH, (Long) null);
+ }
+
public static BlobGetOption generationMatch(long generation) {
return new BlobGetOption(StorageRpc.Option.IF_GENERATION_MATCH, generation);
}
@@ -1280,7 +1317,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
/**
* Delete the requested bucket.
*
- * @return true if bucket was deleted
+ * @return {@code true} if bucket was deleted, {@code false} if it was not found
* @throws StorageException upon failure
*/
boolean delete(String bucket, BucketSourceOption... options);
@@ -1288,7 +1325,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
/**
* Delete the requested blob.
*
- * @return true if blob was deleted
+ * @return {@code true} if blob was deleted, {@code false} if it was not found
* @throws StorageException upon failure
*/
boolean delete(String bucket, String blob, BlobSourceOption... options);
@@ -1296,7 +1333,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
/**
* Delete the requested blob.
*
- * @return true if blob was deleted
+ * @return {@code true} if blob was deleted, {@code false} if it was not found
* @throws StorageException upon failure
*/
boolean delete(BlobId blob, BlobSourceOption... options);
@@ -1304,7 +1341,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
/**
* Delete the requested blob.
*
- * @return true if blob was deleted
+ * @return {@code true} if blob was deleted, {@code false} if it was not found
* @throws StorageException upon failure
*/
boolean delete(BlobId blob);
@@ -1369,14 +1406,29 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
BatchResponse apply(BatchRequest batchRequest);
/**
- * Return a channel for reading the blob's content.
+ * Return a channel for reading the blob's content. The blob's latest generation is read. If the
+ * blob changes while reading (i.e. {@link BlobInfo#etag()} changes), subsequent calls to
+ * {@code blobReadChannel.read(ByteBuffer)} may throw {@link StorageException}.
+ *
+ * The {@link BlobSourceOption#generationMatch(long)} option can be provided to ensure that
+ * {@code blobReadChannel.read(ByteBuffer)} calls will throw {@link StorageException} if blob`s
+ * generation differs from the expected one.
*
* @throws StorageException upon failure
*/
BlobReadChannel reader(String bucket, String blob, BlobSourceOption... options);
/**
- * Return a channel for reading the blob's content.
+ * Return a channel for reading the blob's content. If {@code blob.generation()} is set
+ * data corresponding to that generation is read. If {@code blob.generation()} is {@code null}
+ * the blob's latest generation is read. If the blob changes while reading (i.e.
+ * {@link BlobInfo#etag()} changes), subsequent calls to {@code blobReadChannel.read(ByteBuffer)}
+ * may throw {@link StorageException}.
+ *
+ *
The {@link BlobSourceOption#generationMatch()} and
+ * {@link BlobSourceOption#generationMatch(long)} options can be used to ensure that
+ * {@code blobReadChannel.read(ByteBuffer)} calls will throw {@link StorageException} if the
+ * blob`s generation differs from the expected one.
*
* @throws StorageException upon failure
*/
@@ -1442,8 +1494,8 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
*
* @param blobIds blobs to delete
* @return an immutable list of booleans. If a blob has been deleted the corresponding item in the
- * list is {@code true}. If deletion failed or access to the resource was denied the item is
- * {@code false}.
+ * list is {@code true}. If a blob was not found, deletion failed or access to the resource
+ * was denied the corresponding item is {@code false}.
* @throws StorageException upon failure
*/
List delete(BlobId... blobIds);
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java
index 4c85113e940e..91a408657847 100644
--- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java
+++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java
@@ -28,7 +28,6 @@
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.nio.charset.StandardCharsets.UTF_8;
import com.google.api.services.storage.model.StorageObject;
@@ -43,6 +42,8 @@
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Ints;
+import com.google.gcloud.AuthCredentials;
+import com.google.gcloud.AuthCredentials.ApplicationDefaultAuthCredentials;
import com.google.gcloud.AuthCredentials.ServiceAccountAuthCredentials;
import com.google.gcloud.PageImpl;
import com.google.gcloud.BaseService;
@@ -175,14 +176,7 @@ public BucketInfo get(String bucket, BucketGetOption... options) {
new Callable() {
@Override
public com.google.api.services.storage.model.Bucket call() {
- try {
- return storageRpc.get(bucketPb, optionsMap);
- } catch (StorageException ex) {
- if (ex.code() == HTTP_NOT_FOUND) {
- return null;
- }
- throw ex;
- }
+ return storageRpc.get(bucketPb, optionsMap);
}
}, options().retryParams(), EXCEPTION_HANDLER);
return answer == null ? null : BucketInfo.fromPb(answer);
@@ -199,19 +193,12 @@ public BlobInfo get(String bucket, String blob, BlobGetOption... options) {
@Override
public BlobInfo get(BlobId blob, BlobGetOption... options) {
final StorageObject storedObject = blob.toPb();
- final Map optionsMap = optionMap(options);
+ final Map optionsMap = optionMap(blob, options);
try {
StorageObject storageObject = runWithRetries(new Callable() {
@Override
public StorageObject call() {
- try {
- return storageRpc.get(storedObject, optionsMap);
- } catch (StorageException ex) {
- if (ex.code() == HTTP_NOT_FOUND) {
- return null;
- }
- throw ex;
- }
+ return storageRpc.get(storedObject, optionsMap);
}
}, options().retryParams(), EXCEPTION_HANDLER);
return storageObject == null ? null : BlobInfo.fromPb(storageObject);
@@ -405,7 +392,7 @@ public boolean delete(String bucket, String blob, BlobSourceOption... options) {
@Override
public boolean delete(BlobId blob, BlobSourceOption... options) {
final StorageObject storageObject = blob.toPb();
- final Map optionsMap = optionMap(options);
+ final Map optionsMap = optionMap(blob, options);
try {
return runWithRetries(new Callable() {
@Override
@@ -428,8 +415,9 @@ public BlobInfo compose(final ComposeRequest composeRequest) {
final List sources =
Lists.newArrayListWithCapacity(composeRequest.sourceBlobs().size());
for (ComposeRequest.SourceBlob sourceBlob : composeRequest.sourceBlobs()) {
- sources.add(BlobInfo.builder(composeRequest.target().bucket(), sourceBlob.name())
- .generation(sourceBlob.generation()).build().toPb());
+ sources.add(BlobInfo.builder(
+ BlobId.of(composeRequest.target().bucket(), sourceBlob.name(), sourceBlob.generation()))
+ .build().toPb());
}
final StorageObject target = composeRequest.target().toPb();
final Map targetOptions = optionMap(composeRequest.target().generation(),
@@ -450,7 +438,7 @@ public StorageObject call() {
public CopyWriter copy(final CopyRequest copyRequest) {
final StorageObject source = copyRequest.source().toPb();
final Map sourceOptions =
- optionMap(null, null, copyRequest.sourceOptions(), true);
+ optionMap(copyRequest.source().generation(), null, copyRequest.sourceOptions(), true);
final StorageObject target = copyRequest.target().toPb();
final Map targetOptions = optionMap(copyRequest.target().generation(),
copyRequest.target().metageneration(), copyRequest.targetOptions());
@@ -476,7 +464,7 @@ public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... optio
@Override
public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) {
final StorageObject storageObject = blob.toPb();
- final Map optionsMap = optionMap(options);
+ final Map optionsMap = optionMap(blob, options);
try {
return runWithRetries(new Callable() {
@Override
@@ -495,7 +483,7 @@ public BatchResponse apply(BatchRequest batchRequest) {
Lists.newArrayListWithCapacity(batchRequest.toDelete().size());
for (Map.Entry> entry : batchRequest.toDelete().entrySet()) {
BlobId blob = entry.getKey();
- Map optionsMap = optionMap(null, null, entry.getValue());
+ Map optionsMap = optionMap(blob.generation(), null, entry.getValue());
StorageObject storageObject = blob.toPb();
toDelete.add(Tuple.>of(storageObject, optionsMap));
}
@@ -512,7 +500,7 @@ public BatchResponse apply(BatchRequest batchRequest) {
Lists.newArrayListWithCapacity(batchRequest.toGet().size());
for (Map.Entry> entry : batchRequest.toGet().entrySet()) {
BlobId blob = entry.getKey();
- Map optionsMap = optionMap(null, null, entry.getValue());
+ Map optionsMap = optionMap(blob.generation(), null, entry.getValue());
toGet.add(Tuple.>of(blob.toPb(), optionsMap));
}
StorageRpc.BatchResponse response =
@@ -522,28 +510,23 @@ public BatchResponse apply(BatchRequest batchRequest) {
List> updates = transformBatchResult(
toUpdate, response.updates, BlobInfo.FROM_PB_FUNCTION);
List> gets = transformBatchResult(
- toGet, response.gets, BlobInfo.FROM_PB_FUNCTION, HTTP_NOT_FOUND);
+ toGet, response.gets, BlobInfo.FROM_PB_FUNCTION);
return new BatchResponse(deletes, updates, gets);
}
private List> transformBatchResult(
Iterable>> request,
- Map> results, Function transform,
- int... nullOnErrorCodes) {
- Set nullOnErrorCodesSet = Sets.newHashSet(Ints.asList(nullOnErrorCodes));
+ Map> results, Function transform) {
List> response = Lists.newArrayListWithCapacity(results.size());
for (Tuple tuple : request) {
Tuple result = results.get(tuple.x());
- if (result.x() != null) {
- response.add(BatchResponse.Result.of(transform.apply(result.x())));
+ I object = result.x();
+ StorageException exception = result.y();
+ if (exception != null) {
+ response.add(new BatchResponse.Result(exception));
} else {
- StorageException exception = result.y();
- if (nullOnErrorCodesSet.contains(exception.code())) {
- //noinspection unchecked
- response.add(BatchResponse.Result.empty());
- } else {
- response.add(new BatchResponse.Result(exception));
- }
+ response.add(object != null ?
+ BatchResponse.Result.of(transform.apply(object)) : BatchResponse.Result.empty());
}
}
return response;
@@ -557,7 +540,7 @@ public BlobReadChannel reader(String bucket, String blob, BlobSourceOption... op
@Override
public BlobReadChannel reader(BlobId blob, BlobSourceOption... options) {
- Map optionsMap = optionMap(options);
+ Map optionsMap = optionMap(blob, options);
return new BlobReadChannelImpl(options(), blob, optionsMap);
}
@@ -583,9 +566,15 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
ServiceAccountAuthCredentials cred =
(ServiceAccountAuthCredentials) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED);
if (cred == null) {
- checkArgument(options().authCredentials() instanceof ServiceAccountAuthCredentials,
- "Signing key was not provided and could not be derived");
- cred = (ServiceAccountAuthCredentials) this.options().authCredentials();
+ AuthCredentials serviceCred = this.options().authCredentials();
+ if (serviceCred instanceof ServiceAccountAuthCredentials) {
+ cred = (ServiceAccountAuthCredentials) serviceCred;
+ } else {
+ if (serviceCred instanceof ApplicationDefaultAuthCredentials) {
+ cred = ((ApplicationDefaultAuthCredentials) serviceCred).toServiceAccountCredentials();
+ }
+ }
+ checkArgument(cred != null, "Signing key was not provided and could not be derived");
}
// construct signature - see https://cloud.google.com/storage/docs/access-control#Signed-URLs
StringBuilder stBuilder = new StringBuilder();
@@ -741,4 +730,8 @@ private static void addToOptionMap(StorageRpc.Option getOption, StorageRpc.O
private Map optionMap(BlobInfo blobInfo, Option... options) {
return optionMap(blobInfo.generation(), blobInfo.metageneration(), options);
}
+
+ private Map optionMap(BlobId blobId, Option... options) {
+ return optionMap(blobId.generation(), null, options);
+ }
}
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java
index 2a09631be40a..137afd38b6ae 100644
--- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java
+++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java
@@ -17,10 +17,9 @@
/**
* A client to Google Cloud Storage.
*
- * A simple usage example:
+ *
Here's a simple usage example for using gcloud-java from App/Compute Engine:
*
{@code
- * StorageOptions options = StorageOptions.builder().projectId("project").build();
- * Storage storage = options.service();
+ * Storage storage = StorageOptions.defaultInstance().service();
* BlobId blobId = BlobId.of("bucket", "blob_name");
* Blob blob = Blob.load(storage, blobId);
* if (blob == null) {
@@ -35,6 +34,11 @@
* channel.close();
* }}
*
+ * When using gcloud-java from outside of App/Compute Engine, you have to specify a
+ * project ID and
+ * provide
+ * credentials.
* @see Google Cloud Storage
*/
package com.google.gcloud.storage;
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java
index b15768cffa98..f5cdae83f999 100644
--- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java
+++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java
@@ -45,8 +45,6 @@ public class RemoteGcsHelper {
private static final Logger log = Logger.getLogger(RemoteGcsHelper.class.getName());
private static final String BUCKET_NAME_PREFIX = "gcloud-test-bucket-temp-";
- private static final String PROJECT_ID_ENV_VAR = "GCLOUD_TESTS_PROJECT_ID";
- private static final String PRIVATE_KEY_ENV_VAR = "GCLOUD_TESTS_KEY";
private final StorageOptions options;
private RemoteGcsHelper(StorageOptions options) {
@@ -107,13 +105,7 @@ public static RemoteGcsHelper create(String projectId, InputStream keyStream)
StorageOptions storageOptions = StorageOptions.builder()
.authCredentials(AuthCredentials.createForJson(keyStream))
.projectId(projectId)
- .retryParams(RetryParams.builder()
- .retryMaxAttempts(10)
- .retryMinAttempts(6)
- .maxRetryDelayMillis(30000)
- .totalRetryPeriodMillis(120000)
- .initialRetryDelayMillis(250)
- .build())
+ .retryParams(retryParams())
.connectTimeout(60000)
.readTimeout(60000)
.build();
@@ -145,41 +137,30 @@ public static RemoteGcsHelper create(String projectId, String keyPath)
log.log(Level.WARNING, ex.getMessage());
}
throw GcsHelperException.translate(ex);
- } catch (IOException ex) {
- if (log.isLoggable(Level.WARNING)) {
- log.log(Level.WARNING, ex.getMessage());
- }
- throw GcsHelperException.translate(ex);
}
}
/**
- * Creates a {@code RemoteGcsHelper} object. Project id and path to JSON key are read from two
- * environment variables: {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY}.
- *
- * @return A {@code RemoteGcsHelper} object for the provided options.
- * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if environment
- * variables {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY} are not set or if
- * the file pointed by {@code GCLOUD_TESTS_KEY} does not exist
+ * Creates a {@code RemoteGcsHelper} object using default project id and authentication
+ * credentials.
*/
public static RemoteGcsHelper create() throws GcsHelperException {
- String projectId = System.getenv(PROJECT_ID_ENV_VAR);
- String keyPath = System.getenv(PRIVATE_KEY_ENV_VAR);
- if (projectId == null) {
- String message = "Environment variable " + PROJECT_ID_ENV_VAR + " not set";
- if (log.isLoggable(Level.WARNING)) {
- log.log(Level.WARNING, message);
- }
- throw new GcsHelperException(message);
- }
- if (keyPath == null) {
- String message = "Environment variable " + PRIVATE_KEY_ENV_VAR + " not set";
- if (log.isLoggable(Level.WARNING)) {
- log.log(Level.WARNING, message);
- }
- throw new GcsHelperException(message);
- }
- return create(projectId, keyPath);
+ StorageOptions storageOptions = StorageOptions.builder()
+ .retryParams(retryParams())
+ .connectTimeout(60000)
+ .readTimeout(60000)
+ .build();
+ return new RemoteGcsHelper(storageOptions);
+ }
+
+ private static RetryParams retryParams() {
+ return RetryParams.builder()
+ .retryMaxAttempts(10)
+ .retryMinAttempts(6)
+ .maxRetryDelayMillis(30000)
+ .totalRetryPeriodMillis(120000)
+ .initialRetryDelayMillis(250)
+ .build();
}
private static class DeleteBucketTask implements Callable {
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchRequestTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchRequestTest.java
index 600c8af0d554..63972ff85dfd 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchRequestTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchRequestTest.java
@@ -37,12 +37,12 @@ public class BatchRequestTest {
@Test
public void testBatchRequest() {
BatchRequest request = BatchRequest.builder()
- .delete("b1", "o1")
+ .delete(BlobId.of("b1", "o1", 1L), BlobSourceOption.generationMatch())
.delete("b1", "o2", BlobSourceOption.generationMatch(1),
BlobSourceOption.metagenerationMatch(2))
.update(BlobInfo.builder("b2", "o1").build(), BlobTargetOption.predefinedAcl(PUBLIC_READ))
.update(BlobInfo.builder("b2", "o2").build())
- .get("b3", "o1")
+ .get(BlobId.of("b3", "o1", 1L), BlobGetOption.generationMatch())
.get("b3", "o2", BlobGetOption.generationMatch(1))
.get("b3", "o3")
.build();
@@ -50,11 +50,15 @@ public void testBatchRequest() {
Iterator>> deletes = request
.toDelete().entrySet().iterator();
Entry> delete = deletes.next();
- assertEquals(BlobId.of("b1", "o1"), delete.getKey());
- assertTrue(Iterables.isEmpty(delete.getValue()));
+ assertEquals(BlobId.of("b1", "o1", 1L), delete.getKey());
+ assertEquals(1, Iterables.size(delete.getValue()));
+ assertEquals(BlobSourceOption.generationMatch(), Iterables.getFirst(delete.getValue(), null));
delete = deletes.next();
assertEquals(BlobId.of("b1", "o2"), delete.getKey());
assertEquals(2, Iterables.size(delete.getValue()));
+ assertEquals(BlobSourceOption.generationMatch(1L), Iterables.getFirst(delete.getValue(), null));
+ assertEquals(BlobSourceOption.metagenerationMatch(2L),
+ Iterables.get(delete.getValue(), 1, null));
assertFalse(deletes.hasNext());
Iterator>> updates = request
@@ -71,8 +75,9 @@ public void testBatchRequest() {
Iterator>> gets = request.toGet().entrySet().iterator();
Entry> get = gets.next();
- assertEquals(BlobId.of("b3", "o1"), get.getKey());
- assertTrue(Iterables.isEmpty(get.getValue()));
+ assertEquals(BlobId.of("b3", "o1", 1L), get.getKey());
+ assertEquals(1, Iterables.size(get.getValue()));
+ assertEquals(BlobGetOption.generationMatch(), Iterables.getFirst(get.getValue(), null));
get = gets.next();
assertEquals(BlobId.of("b3", "o2"), get.getKey());
assertEquals(1, Iterables.size(get.getValue()));
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobInfoTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobInfoTest.java
index 70560b0c9a9e..36b027dc7278 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobInfoTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobInfoTest.java
@@ -55,7 +55,7 @@ public class BlobInfoTest {
private static final String SELF_LINK = "http://storage/b/n";
private static final Long SIZE = 1024L;
private static final Long UPDATE_TIME = DELETE_TIME - 1L;
- private static final BlobInfo BLOB_INFO = BlobInfo.builder("b", "n")
+ private static final BlobInfo BLOB_INFO = BlobInfo.builder("b", "n", GENERATION)
.acl(ACL)
.componentCount(COMPONENT_COUNT)
.contentType(CONTENT_TYPE)
@@ -66,7 +66,6 @@ public class BlobInfoTest {
.crc32c(CRC32)
.deleteTime(DELETE_TIME)
.etag(ETAG)
- .generation(GENERATION)
.id(ID)
.md5(MD5)
.mediaLink(MEDIA_LINK)
@@ -85,10 +84,16 @@ public void testToBuilder() {
assertEquals("n2", blobInfo.name());
assertEquals("b2", blobInfo.bucket());
assertEquals(Long.valueOf(200), blobInfo.size());
- blobInfo = blobInfo.toBuilder().blobId(BlobId.of("b", "n")).size(SIZE).build();
+ blobInfo = blobInfo.toBuilder().blobId(BlobId.of("b", "n", GENERATION)).size(SIZE).build();
compareBlobs(BLOB_INFO, blobInfo);
}
+ @Test
+ public void testToBuilderIncomplete() {
+ BlobInfo incompleteBlobInfo = BlobInfo.builder(BlobId.of("b2", "n2")).build();
+ compareBlobs(incompleteBlobInfo, incompleteBlobInfo.toBuilder().build());
+ }
+
@Test
public void testBuilder() {
assertEquals("b", BLOB_INFO.bucket());
@@ -150,6 +155,6 @@ public void testToPbAndFromPb() {
@Test
public void testBlobId() {
- assertEquals(BlobId.of("b", "n"), BLOB_INFO.blobId());
+ assertEquals(BlobId.of("b", "n", GENERATION), BLOB_INFO.blobId());
}
}
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobReadChannelImplTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobReadChannelImplTest.java
index e1f904bf72fe..f99fe893d0d9 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobReadChannelImplTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobReadChannelImplTest.java
@@ -19,7 +19,6 @@
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertArrayEquals;
@@ -46,7 +45,7 @@ public class BlobReadChannelImplTest {
private static final String BUCKET_NAME = "b";
private static final String BLOB_NAME = "n";
- private static final BlobId BLOB_ID = BlobId.of(BUCKET_NAME, BLOB_NAME);
+ private static final BlobId BLOB_ID = BlobId.of(BUCKET_NAME, BLOB_NAME, -1L);
private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of();
private static final int DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024;
private static final int CUSTOM_CHUNK_SIZE = 2 * 1024 * 1024;
@@ -88,7 +87,7 @@ public void testReadBuffered() throws IOException {
ByteBuffer firstReadBuffer = ByteBuffer.allocate(42);
ByteBuffer secondReadBuffer = ByteBuffer.allocate(42);
expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
- .andReturn(result);
+ .andReturn(StorageRpc.Tuple.of("etag", result));
replay(storageRpcMock);
reader.read(firstReadBuffer);
reader.read(secondReadBuffer);
@@ -107,10 +106,11 @@ public void testReadBig() throws IOException {
byte[] secondResult = randomByteArray(DEFAULT_CHUNK_SIZE);
ByteBuffer firstReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
ByteBuffer secondReadBuffer = ByteBuffer.allocate(42);
- storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE);
- expectLastCall().andReturn(firstResult);
- storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, DEFAULT_CHUNK_SIZE, CUSTOM_CHUNK_SIZE);
- expectLastCall().andReturn(secondResult);
+ expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+ .andReturn(StorageRpc.Tuple.of("etag", firstResult));
+ expect(storageRpcMock.read(
+ BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, DEFAULT_CHUNK_SIZE, CUSTOM_CHUNK_SIZE))
+ .andReturn(StorageRpc.Tuple.of("etag", secondResult));
replay(storageRpcMock);
reader.read(firstReadBuffer);
reader.read(secondReadBuffer);
@@ -125,7 +125,7 @@ public void testReadFinish() throws IOException {
byte[] result = {};
ByteBuffer readBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
- .andReturn(result);
+ .andReturn(StorageRpc.Tuple.of("etag", result));
replay(storageRpcMock);
assertEquals(-1, reader.read(readBuffer));
}
@@ -137,7 +137,7 @@ public void testSeek() throws IOException {
byte[] result = randomByteArray(DEFAULT_CHUNK_SIZE);
ByteBuffer readBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 42, DEFAULT_CHUNK_SIZE))
- .andReturn(result);
+ .andReturn(StorageRpc.Tuple.of("etag", result));
replay(storageRpcMock);
reader.read(readBuffer);
assertArrayEquals(result, readBuffer.array());
@@ -166,6 +166,31 @@ public void testReadClosed() {
}
}
+ @Test
+ public void testReadGenerationChanged() throws IOException {
+ BlobId blobId = BlobId.of(BUCKET_NAME, BLOB_NAME);
+ reader = new BlobReadChannelImpl(options, blobId, EMPTY_RPC_OPTIONS);
+ byte[] firstResult = randomByteArray(DEFAULT_CHUNK_SIZE);
+ byte[] secondResult = randomByteArray(DEFAULT_CHUNK_SIZE);
+ ByteBuffer firstReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
+ ByteBuffer secondReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
+ expect(storageRpcMock.read(blobId.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+ .andReturn(StorageRpc.Tuple.of("etag1", firstResult));
+ expect(storageRpcMock.read(
+ blobId.toPb(), EMPTY_RPC_OPTIONS, DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE))
+ .andReturn(StorageRpc.Tuple.of("etag2", firstResult));
+ replay(storageRpcMock);
+ reader.read(firstReadBuffer);
+ try {
+ reader.read(secondReadBuffer);
+ fail("Expected BlobReadChannel read to throw StorageException");
+ } catch (StorageException ex) {
+ StringBuilder messageBuilder = new StringBuilder();
+ messageBuilder.append("Blob ").append(blobId).append(" was updated while reading");
+ assertEquals(messageBuilder.toString(), ex.getMessage());
+ }
+ }
+
@Test
public void testSaveAndRestore() throws IOException, ClassNotFoundException {
byte[] firstResult = randomByteArray(DEFAULT_CHUNK_SIZE);
@@ -173,9 +198,9 @@ public void testSaveAndRestore() throws IOException, ClassNotFoundException {
ByteBuffer firstReadBuffer = ByteBuffer.allocate(42);
ByteBuffer secondReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
- .andReturn(firstResult);
+ .andReturn(StorageRpc.Tuple.of("etag", firstResult));
expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 42, DEFAULT_CHUNK_SIZE))
- .andReturn(secondResult);
+ .andReturn(StorageRpc.Tuple.of("etag", secondResult));
replay(storageRpcMock);
reader = new BlobReadChannelImpl(options, BLOB_ID, EMPTY_RPC_OPTIONS);
reader.read(firstReadBuffer);
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketInfoTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketInfoTest.java
index 4fa420b4b6e1..e0de2b77a899 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketInfoTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketInfoTest.java
@@ -91,7 +91,7 @@ public void testToBuilder() {
@Test
public void testToBuilderIncomplete() {
BucketInfo incompleteBucketInfo = BucketInfo.builder("b").build();
- assertEquals(incompleteBucketInfo.name(), incompleteBucketInfo.toBuilder().build().name());
+ compareBuckets(incompleteBucketInfo, incompleteBucketInfo.toBuilder().build());
}
@Test
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java
index 423e972a8de6..ed58690efde7 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java
@@ -19,11 +19,13 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.api.client.util.Lists;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gcloud.Page;
@@ -48,6 +50,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -63,6 +66,7 @@ public class ITStorageTest {
private static final String CONTENT_TYPE = "text/plain";
private static final byte[] BLOB_BYTE_CONTENT = {0xD, 0xE, 0xA, 0xD};
private static final String BLOB_STRING_CONTENT = "Hello Google Cloud Storage!";
+ private static final int MAX_BATCH_DELETES = 100;
@BeforeClass
public static void beforeClass() {
@@ -129,7 +133,8 @@ public void testCreateBlob() {
BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
BlobInfo remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
assertNotNull(remoteBlob);
- assertEquals(blob.blobId(), remoteBlob.blobId());
+ assertEquals(blob.bucket(), remoteBlob.bucket());
+ assertEquals(blob.name(), remoteBlob.name());
byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
assertTrue(storage.delete(BUCKET, blobName));
@@ -141,7 +146,8 @@ public void testCreateEmptyBlob() {
BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
BlobInfo remoteBlob = storage.create(blob);
assertNotNull(remoteBlob);
- assertEquals(blob.blobId(), remoteBlob.blobId());
+ assertEquals(blob.bucket(), remoteBlob.bucket());
+ assertEquals(blob.name(), remoteBlob.name());
byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
assertArrayEquals(new byte[0], readBytes);
assertTrue(storage.delete(BUCKET, blobName));
@@ -154,7 +160,8 @@ public void testCreateBlobStream() throws UnsupportedEncodingException {
ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8));
BlobInfo remoteBlob = storage.create(blob, stream);
assertNotNull(remoteBlob);
- assertEquals(blob.blobId(), remoteBlob.blobId());
+ assertEquals(blob.bucket(), remoteBlob.bucket());
+ assertEquals(blob.name(), remoteBlob.name());
assertEquals(blob.contentType(), remoteBlob.contentType());
byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
assertEquals(BLOB_STRING_CONTENT, new String(readBytes, UTF_8));
@@ -166,8 +173,9 @@ public void testCreateBlobFail() {
String blobName = "test-create-blob-fail";
BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
assertNotNull(storage.create(blob));
+ BlobInfo wrongGenerationBlob = BlobInfo.builder(BUCKET, blobName, -1L).build();
try {
- storage.create(blob.toBuilder().generation(-1L).build(), BLOB_BYTE_CONTENT,
+ storage.create(wrongGenerationBlob, BLOB_BYTE_CONTENT,
Storage.BlobTargetOption.generationMatch());
fail("StorageException was expected");
} catch (StorageException ex) {
@@ -229,13 +237,39 @@ public void testGetBlobAllSelectedFields() {
assertNotNull(storage.create(blob));
BlobInfo remoteBlob = storage.get(blob.blobId(),
Storage.BlobGetOption.fields(BlobField.values()));
- assertEquals(blob.blobId(), remoteBlob.blobId());
+ assertEquals(blob.bucket(), remoteBlob.bucket());
+ assertEquals(blob.name(), remoteBlob.name());
assertEquals(ImmutableMap.of("k", "v"), remoteBlob.metadata());
assertNotNull(remoteBlob.id());
assertNotNull(remoteBlob.selfLink());
assertTrue(storage.delete(BUCKET, blobName));
}
+ @Test
+ public void testGetBlobFail() {
+ String blobName = "test-get-blob-fail";
+ BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
+ assertNotNull(storage.create(blob));
+ BlobId wrongGenerationBlob = BlobId.of(BUCKET, blobName);
+ try {
+ storage.get(wrongGenerationBlob, Storage.BlobGetOption.generationMatch(-1));
+ fail("StorageException was expected");
+ } catch (StorageException ex) {
+ // expected
+ }
+ assertTrue(storage.delete(BUCKET, blobName));
+ }
+
+ @Test
+ public void testGetBlobFailNonExistingGeneration() {
+ String blobName = "test-get-blob-fail-non-existing-generation";
+ BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
+ assertNotNull(storage.create(blob));
+ BlobId wrongGenerationBlob = BlobId.of(BUCKET, blobName, -1L);
+ assertNull(storage.get(wrongGenerationBlob));
+ assertTrue(storage.delete(BUCKET, blobName));
+ }
+
@Test
public void testListBlobsSelectedFields() {
String[] blobNames = {"test-list-blobs-selected-fields-blob1",
@@ -297,7 +331,8 @@ public void testUpdateBlob() {
assertNotNull(storage.create(blob));
BlobInfo updatedBlob = storage.update(blob.toBuilder().contentType(CONTENT_TYPE).build());
assertNotNull(updatedBlob);
- assertEquals(blob.blobId(), updatedBlob.blobId());
+ assertEquals(blob.name(), updatedBlob.name());
+ assertEquals(blob.bucket(), updatedBlob.bucket());
assertEquals(CONTENT_TYPE, updatedBlob.contentType());
assertTrue(storage.delete(BUCKET, blobName));
}
@@ -316,7 +351,8 @@ public void testUpdateBlobReplaceMetadata() {
assertNotNull(updatedBlob);
assertNull(updatedBlob.metadata());
updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build());
- assertEquals(blob.blobId(), updatedBlob.blobId());
+ assertEquals(blob.name(), updatedBlob.name());
+ assertEquals(blob.bucket(), updatedBlob.bucket());
assertEquals(newMetadata, updatedBlob.metadata());
assertTrue(storage.delete(BUCKET, blobName));
}
@@ -334,7 +370,8 @@ public void testUpdateBlobMergeMetadata() {
assertNotNull(storage.create(blob));
BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build());
assertNotNull(updatedBlob);
- assertEquals(blob.blobId(), updatedBlob.blobId());
+ assertEquals(blob.name(), updatedBlob.name());
+ assertEquals(blob.bucket(), updatedBlob.bucket());
assertEquals(expectedMetadata, updatedBlob.metadata());
assertTrue(storage.delete(BUCKET, blobName));
}
@@ -354,7 +391,8 @@ public void testUpdateBlobUnsetMetadata() {
assertNotNull(storage.create(blob));
BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build());
assertNotNull(updatedBlob);
- assertEquals(blob.blobId(), updatedBlob.blobId());
+ assertEquals(blob.name(), updatedBlob.name());
+ assertEquals(blob.bucket(), updatedBlob.bucket());
assertEquals(expectedMetadata, updatedBlob.metadata());
assertTrue(storage.delete(BUCKET, blobName));
}
@@ -364,9 +402,11 @@ public void testUpdateBlobFail() {
String blobName = "test-update-blob-fail";
BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
assertNotNull(storage.create(blob));
+ BlobInfo wrongGenerationBlob = BlobInfo.builder(BUCKET, blobName, -1L)
+ .contentType(CONTENT_TYPE)
+ .build();
try {
- storage.update(blob.toBuilder().contentType(CONTENT_TYPE).generation(-1L).build(),
- Storage.BlobTargetOption.generationMatch());
+ storage.update(wrongGenerationBlob, Storage.BlobTargetOption.generationMatch());
fail("StorageException was expected");
} catch (StorageException ex) {
// expected
@@ -380,6 +420,14 @@ public void testDeleteNonExistingBlob() {
assertTrue(!storage.delete(BUCKET, blobName));
}
+ @Test
+ public void testDeleteBlobNonExistingGeneration() {
+ String blobName = "test-delete-blob-non-existing-generation";
+ BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
+ assertNotNull(storage.create(blob));
+ assertTrue(!storage.delete(BlobId.of(BUCKET, blobName, -1L)));
+ }
+
@Test
public void testDeleteBlobFail() {
String blobName = "test-delete-blob-fail";
@@ -408,7 +456,8 @@ public void testComposeBlob() {
Storage.ComposeRequest.of(ImmutableList.of(sourceBlobName1, sourceBlobName2), targetBlob);
BlobInfo remoteBlob = storage.compose(req);
assertNotNull(remoteBlob);
- assertEquals(targetBlob.blobId(), remoteBlob.blobId());
+ assertEquals(targetBlob.name(), remoteBlob.name());
+ assertEquals(targetBlob.bucket(), remoteBlob.bucket());
byte[] readBytes = storage.readAllBytes(BUCKET, targetBlobName);
byte[] composedBytes = Arrays.copyOf(BLOB_BYTE_CONTENT, BLOB_BYTE_CONTENT.length * 2);
System.arraycopy(BLOB_BYTE_CONTENT, 0, composedBytes, BLOB_BYTE_CONTENT.length,
@@ -491,12 +540,12 @@ public void testCopyBlobUpdateMetadata() {
@Test
public void testCopyBlobFail() {
String sourceBlobName = "test-copy-blob-source-fail";
- BlobId source = BlobId.of(BUCKET, sourceBlobName);
+ BlobId source = BlobId.of(BUCKET, sourceBlobName, -1L);
assertNotNull(storage.create(BlobInfo.builder(source).build(), BLOB_BYTE_CONTENT));
String targetBlobName = "test-copy-blob-target-fail";
BlobInfo target = BlobInfo.builder(BUCKET, targetBlobName).contentType(CONTENT_TYPE).build();
Storage.CopyRequest req = Storage.CopyRequest.builder()
- .source(source)
+ .source(BUCKET, sourceBlobName)
.sourceOptions(Storage.BlobSourceOption.generationMatch(-1L))
.target(target)
.build();
@@ -506,6 +555,17 @@ public void testCopyBlobFail() {
} catch (StorageException ex) {
// expected
}
+ Storage.CopyRequest req2 = Storage.CopyRequest.builder()
+ .source(source)
+ .sourceOptions(Storage.BlobSourceOption.generationMatch())
+ .target(target)
+ .build();
+ try {
+ storage.copy(req2);
+ fail("StorageException was expected");
+ } catch (StorageException ex) {
+ // expected
+ }
assertTrue(storage.delete(BUCKET, sourceBlobName));
}
@@ -531,8 +591,10 @@ public void testBatchRequest() {
assertEquals(0, updateResponse.gets().size());
BlobInfo remoteUpdatedBlob1 = updateResponse.updates().get(0).get();
BlobInfo remoteUpdatedBlob2 = updateResponse.updates().get(1).get();
- assertEquals(sourceBlob1.blobId(), remoteUpdatedBlob1.blobId());
- assertEquals(sourceBlob2.blobId(), remoteUpdatedBlob2.blobId());
+ assertEquals(sourceBlob1.bucket(), remoteUpdatedBlob1.bucket());
+ assertEquals(sourceBlob1.name(), remoteUpdatedBlob1.name());
+ assertEquals(sourceBlob2.bucket(), remoteUpdatedBlob2.bucket());
+ assertEquals(sourceBlob2.name(), remoteUpdatedBlob2.name());
assertEquals(updatedBlob1.contentType(), remoteUpdatedBlob1.contentType());
assertEquals(updatedBlob2.contentType(), remoteUpdatedBlob2.contentType());
@@ -563,24 +625,78 @@ public void testBatchRequest() {
assertTrue(deleteResponse.deletes().get(1).get());
}
+ @Test
+ public void testBatchRequestManyDeletes() {
+ List blobsToDelete = Lists.newArrayListWithCapacity(2 * MAX_BATCH_DELETES);
+ for (int i = 0; i < 2 * MAX_BATCH_DELETES; i++) {
+ blobsToDelete.add(BlobId.of(BUCKET, "test-batch-request-many-deletes-blob-" + i));
+ }
+ BatchRequest.Builder builder = BatchRequest.builder();
+ for (BlobId blob : blobsToDelete) {
+ builder.delete(blob);
+ }
+ String sourceBlobName1 = "test-batch-request-many-deletes-source-blob-1";
+ String sourceBlobName2 = "test-batch-request-many-deletes-source-blob-2";
+ BlobInfo sourceBlob1 = BlobInfo.builder(BUCKET, sourceBlobName1).build();
+ BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
+ assertNotNull(storage.create(sourceBlob1));
+ assertNotNull(storage.create(sourceBlob2));
+ BlobInfo updatedBlob2 = sourceBlob2.toBuilder().contentType(CONTENT_TYPE).build();
+
+ BatchRequest updateRequest = builder
+ .get(BUCKET, sourceBlobName1)
+ .update(updatedBlob2)
+ .build();
+ BatchResponse response = storage.apply(updateRequest);
+ assertEquals(2 * MAX_BATCH_DELETES, response.deletes().size());
+ assertEquals(1, response.updates().size());
+ assertEquals(1, response.gets().size());
+
+ // Check deletes
+ for (BatchResponse.Result deleteResult : response.deletes()) {
+ assertFalse(deleteResult.failed());
+ assertFalse(deleteResult.get());
+ }
+
+ // Check updates
+ BlobInfo remoteUpdatedBlob2 = response.updates().get(0).get();
+ assertEquals(sourceBlob2.bucket(), remoteUpdatedBlob2.bucket());
+ assertEquals(sourceBlob2.name(), remoteUpdatedBlob2.name());
+ assertEquals(updatedBlob2.contentType(), remoteUpdatedBlob2.contentType());
+
+ // Check gets
+ BlobInfo remoteBlob1 = response.gets().get(0).get();
+ assertEquals(sourceBlob1.bucket(), remoteBlob1.bucket());
+ assertEquals(sourceBlob1.name(), remoteBlob1.name());
+
+ assertTrue(storage.delete(BUCKET, sourceBlobName1));
+ assertTrue(storage.delete(BUCKET, sourceBlobName2));
+ }
+
@Test
public void testBatchRequestFail() {
String blobName = "test-batch-request-blob-fail";
BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
assertNotNull(storage.create(blob));
- BlobInfo updatedBlob = blob.toBuilder().generation(-1L).build();
+ BlobInfo updatedBlob = BlobInfo.builder(BUCKET, blobName, -1L).build();
BatchRequest batchRequest = BatchRequest.builder()
.update(updatedBlob, Storage.BlobTargetOption.generationMatch())
.delete(BUCKET, blobName, Storage.BlobSourceOption.generationMatch(-1L))
+ .delete(BlobId.of(BUCKET, blobName, -1L))
.get(BUCKET, blobName, Storage.BlobGetOption.generationMatch(-1L))
+ .get(BlobId.of(BUCKET, blobName, -1L))
.build();
- BatchResponse updateResponse = storage.apply(batchRequest);
- assertEquals(1, updateResponse.updates().size());
- assertEquals(1, updateResponse.deletes().size());
- assertEquals(1, updateResponse.gets().size());
- assertTrue(updateResponse.updates().get(0).failed());
- assertTrue(updateResponse.gets().get(0).failed());
- assertTrue(updateResponse.deletes().get(0).failed());
+ BatchResponse batchResponse = storage.apply(batchRequest);
+ assertEquals(1, batchResponse.updates().size());
+ assertEquals(2, batchResponse.deletes().size());
+ assertEquals(2, batchResponse.gets().size());
+ assertTrue(batchResponse.updates().get(0).failed());
+ assertTrue(batchResponse.gets().get(0).failed());
+ assertFalse(batchResponse.gets().get(1).failed());
+ assertNull(batchResponse.gets().get(1).get());
+ assertTrue(batchResponse.deletes().get(0).failed());
+ assertFalse(batchResponse.deletes().get(1).failed());
+ assertFalse(batchResponse.deletes().get(1).get());
assertTrue(storage.delete(BUCKET, blobName));
}
@@ -648,13 +764,63 @@ public void testReadChannelFail() throws IOException {
} catch (StorageException ex) {
// expected
}
+ try (BlobReadChannel reader =
+ storage.reader(blob.blobId(), Storage.BlobSourceOption.generationMatch(-1L))) {
+ reader.read(ByteBuffer.allocate(42));
+ fail("StorageException was expected");
+ } catch (StorageException ex) {
+ // expected
+ }
+ BlobId blobIdWrongGeneration = BlobId.of(BUCKET, blobName, -1L);
+ try (BlobReadChannel reader =
+ storage.reader(blobIdWrongGeneration, Storage.BlobSourceOption.generationMatch())) {
+ reader.read(ByteBuffer.allocate(42));
+ fail("StorageException was expected");
+ } catch (StorageException ex) {
+ // expected
+ }
+ assertTrue(storage.delete(BUCKET, blobName));
+ }
+
+ @Test
+ public void testReadChannelFailUpdatedGeneration() throws IOException {
+ String blobName = "test-read-blob-fail-updated-generation";
+ BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
+ Random random = new Random();
+ int chunkSize = 1024;
+ int blobSize = 2 * chunkSize;
+ byte[] content = new byte[blobSize];
+ random.nextBytes(content);
+ BlobInfo remoteBlob = storage.create(blob, content);
+ assertNotNull(remoteBlob);
+ assertEquals(blobSize, (long) remoteBlob.size());
+ try (BlobReadChannel reader = storage.reader(blob.blobId())) {
+ reader.chunkSize(chunkSize);
+ ByteBuffer readBytes = ByteBuffer.allocate(chunkSize);
+ int numReadBytes = reader.read(readBytes);
+ assertEquals(chunkSize, numReadBytes);
+ assertArrayEquals(Arrays.copyOf(content, chunkSize), readBytes.array());
+ try (BlobWriteChannel writer = storage.writer(blob)) {
+ byte[] newContent = new byte[blobSize];
+ random.nextBytes(newContent);
+ int numWrittenBytes = writer.write(ByteBuffer.wrap(newContent));
+ assertEquals(blobSize, numWrittenBytes);
+ }
+ readBytes = ByteBuffer.allocate(chunkSize);
+ reader.read(readBytes);
+ fail("StorageException was expected");
+ } catch(StorageException ex) {
+ StringBuilder messageBuilder = new StringBuilder();
+ messageBuilder.append("Blob ").append(blob.blobId()).append(" was updated while reading");
+ assertEquals(messageBuilder.toString(), ex.getMessage());
+ }
assertTrue(storage.delete(BUCKET, blobName));
}
@Test
public void testWriteChannelFail() throws IOException {
String blobName = "test-write-channel-blob-fail";
- BlobInfo blob = BlobInfo.builder(BUCKET, blobName).generation(-1L).build();
+ BlobInfo blob = BlobInfo.builder(BUCKET, blobName, -1L).build();
try {
try (BlobWriteChannel writer =
storage.writer(blob, Storage.BlobWriteOption.generationMatch())) {
@@ -707,7 +873,8 @@ public void testPostSignedUrl() throws IOException {
connection.connect();
BlobInfo remoteBlob = storage.get(BUCKET, blobName);
assertNotNull(remoteBlob);
- assertEquals(blob.blobId(), remoteBlob.blobId());
+ assertEquals(blob.bucket(), remoteBlob.bucket());
+ assertEquals(blob.name(), remoteBlob.name());
assertTrue(storage.delete(BUCKET, blobName));
}
@@ -720,8 +887,10 @@ public void testGetBlobs() {
assertNotNull(storage.create(sourceBlob1));
assertNotNull(storage.create(sourceBlob2));
List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId());
- assertEquals(sourceBlob1.blobId(), remoteBlobs.get(0).blobId());
- assertEquals(sourceBlob2.blobId(), remoteBlobs.get(1).blobId());
+ assertEquals(sourceBlob1.bucket(), remoteBlobs.get(0).bucket());
+ assertEquals(sourceBlob1.name(), remoteBlobs.get(0).name());
+ assertEquals(sourceBlob2.bucket(), remoteBlobs.get(1).bucket());
+ assertEquals(sourceBlob2.name(), remoteBlobs.get(1).name());
assertTrue(storage.delete(BUCKET, sourceBlobName1));
assertTrue(storage.delete(BUCKET, sourceBlobName2));
}
@@ -734,7 +903,8 @@ public void testGetBlobsFail() {
BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
assertNotNull(storage.create(sourceBlob1));
List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId());
- assertEquals(sourceBlob1.blobId(), remoteBlobs.get(0).blobId());
+ assertEquals(sourceBlob1.bucket(), remoteBlobs.get(0).bucket());
+ assertEquals(sourceBlob1.name(), remoteBlobs.get(0).name());
assertNull(remoteBlobs.get(1));
assertTrue(storage.delete(BUCKET, sourceBlobName1));
}
@@ -777,9 +947,11 @@ public void testUpdateBlobs() {
List updatedBlobs = storage.update(
remoteBlob1.toBuilder().contentType(CONTENT_TYPE).build(),
remoteBlob2.toBuilder().contentType(CONTENT_TYPE).build());
- assertEquals(sourceBlob1.blobId(), updatedBlobs.get(0).blobId());
+ assertEquals(sourceBlob1.bucket(), updatedBlobs.get(0).bucket());
+ assertEquals(sourceBlob1.name(), updatedBlobs.get(0).name());
assertEquals(CONTENT_TYPE, updatedBlobs.get(0).contentType());
- assertEquals(sourceBlob2.blobId(), updatedBlobs.get(1).blobId());
+ assertEquals(sourceBlob2.bucket(), updatedBlobs.get(1).bucket());
+ assertEquals(sourceBlob2.name(), updatedBlobs.get(1).name());
assertEquals(CONTENT_TYPE, updatedBlobs.get(1).contentType());
assertTrue(storage.delete(BUCKET, sourceBlobName1));
assertTrue(storage.delete(BUCKET, sourceBlobName2));
@@ -796,7 +968,8 @@ public void testUpdateBlobsFail() {
List updatedBlobs = storage.update(
remoteBlob1.toBuilder().contentType(CONTENT_TYPE).build(),
sourceBlob2.toBuilder().contentType(CONTENT_TYPE).build());
- assertEquals(sourceBlob1.blobId(), updatedBlobs.get(0).blobId());
+ assertEquals(sourceBlob1.bucket(), updatedBlobs.get(0).bucket());
+ assertEquals(sourceBlob1.name(), updatedBlobs.get(0).name());
assertEquals(CONTENT_TYPE, updatedBlobs.get(0).contentType());
assertNull(updatedBlobs.get(1));
assertTrue(storage.delete(BUCKET, sourceBlobName1));
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java
index f07c7000813e..32a466a9d551 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java
@@ -89,8 +89,8 @@ public class StorageImplTest {
private static final BucketInfo BUCKET_INFO2 = BucketInfo.builder(BUCKET_NAME2).build();
// BlobInfo objects
- private static final BlobInfo BLOB_INFO1 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME1)
- .metageneration(42L).generation(24L).contentType("application/json").md5("md5string").build();
+ private static final BlobInfo BLOB_INFO1 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME1, 24L)
+ .metageneration(42L).contentType("application/json").md5("md5string").build();
private static final BlobInfo BLOB_INFO2 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME2).build();
private static final BlobInfo BLOB_INFO3 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME3).build();
@@ -157,6 +157,8 @@ public class StorageImplTest {
Storage.BlobGetOption.metagenerationMatch(BLOB_INFO1.metageneration());
private static final Storage.BlobGetOption BLOB_GET_GENERATION =
Storage.BlobGetOption.generationMatch(BLOB_INFO1.generation());
+ private static final Storage.BlobGetOption BLOB_GET_GENERATION_FROM_BLOB_ID =
+ Storage.BlobGetOption.generationMatch();
private static final Storage.BlobGetOption BLOB_GET_FIELDS =
Storage.BlobGetOption.fields(Storage.BlobField.CONTENT_TYPE, Storage.BlobField.CRC32C);
private static final Storage.BlobGetOption BLOB_GET_EMPTY_FIELDS =
@@ -168,6 +170,8 @@ public class StorageImplTest {
Storage.BlobSourceOption.metagenerationMatch(BLOB_INFO1.metageneration());
private static final Storage.BlobSourceOption BLOB_SOURCE_GENERATION =
Storage.BlobSourceOption.generationMatch(BLOB_INFO1.generation());
+ private static final Storage.BlobSourceOption BLOB_SOURCE_GENERATION_FROM_BLOB_ID =
+ Storage.BlobSourceOption.generationMatch();
private static final Map BLOB_SOURCE_OPTIONS = ImmutableMap.of(
StorageRpc.Option.IF_METAGENERATION_MATCH, BLOB_SOURCE_METAGENERATION.value(),
StorageRpc.Option.IF_GENERATION_MATCH, BLOB_SOURCE_GENERATION.value());
@@ -454,6 +458,18 @@ public void testGetBlobWithOptions() {
assertEquals(BLOB_INFO1, blob);
}
+ @Test
+ public void testGetBlobWithOptionsFromBlobId() {
+ EasyMock.expect(
+ storageRpcMock.get(BLOB_INFO1.blobId().toPb(), BLOB_GET_OPTIONS))
+ .andReturn(BLOB_INFO1.toPb());
+ EasyMock.replay(storageRpcMock);
+ storage = options.service();
+ BlobInfo blob =
+ storage.get(BLOB_INFO1.blobId(), BLOB_GET_METAGENERATION, BLOB_GET_GENERATION_FROM_BLOB_ID);
+ assertEquals(BLOB_INFO1, blob);
+ }
+
@Test
public void testGetBlobWithSelectedFields() {
Capture