Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add batch get, update, delete methods to Storage and Blob #233

Merged
merged 6 commits into from
Oct 12, 2015
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.google.gcloud.spi;

import static com.google.common.base.MoreObjects.firstNonNull;

import com.google.api.services.storage.model.Bucket;
import com.google.api.services.storage.model.StorageObject;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -106,9 +108,12 @@ class BatchRequest {
public BatchRequest(Iterable<Tuple<StorageObject, Map<Option, ?>>> toDelete,
Iterable<Tuple<StorageObject, Map<Option, ?>>> toUpdate,
Iterable<Tuple<StorageObject, Map<Option, ?>>> toGet) {
this.toDelete = ImmutableList.copyOf(toDelete);
this.toUpdate = ImmutableList.copyOf(toUpdate);
this.toGet = ImmutableList.copyOf(toGet);
this.toDelete = ImmutableList.copyOf(

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

firstNonNull(toDelete, ImmutableList.<Tuple<StorageObject, Map<Option, ?>>>of()));
this.toUpdate = ImmutableList.copyOf(
firstNonNull(toUpdate, ImmutableList.<Tuple<StorageObject, Map<Option, ?>>>of()));
this.toGet = ImmutableList.copyOf(
firstNonNull(toGet, ImmutableList.<Tuple<StorageObject, Map<Option, ?>>>of()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gcloud.storage.Blob.BlobSourceOption.convert;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.gcloud.spi.StorageRpc;
import com.google.gcloud.storage.Storage.BlobTargetOption;
import com.google.gcloud.storage.Storage.CopyRequest;
import com.google.gcloud.storage.Storage.SignUrlOption;

import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
Expand Down Expand Up @@ -256,4 +261,88 @@ public URL signUrl(long expirationTimeInSeconds, SignUrlOption... options) {
public Storage storage() {
return storage;
}

/**
* Gets the requested blobs. If {@code infos.length == 0} an empty list is returned. If

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

* {@code infos.length > 1} a batch request is used to fetch blobs.
*
* @param storage the storage service used to issue the request
* @param infos the blobs to get
* @return a list of {@code Blob} objects. If a blob does not exist the corresponding item in the
* list is {@code null}.
*/
public static List<Blob> get(final Storage storage, BlobInfo... infos) {
checkNotNull(storage);
checkNotNull(infos);
int length = infos.length;
switch (length) {
case 0:
return Collections.emptyList();
case 1:
return Collections.singletonList(
new Blob(storage, storage.get(infos[0].bucket(), infos[0].name())));

This comment was marked as spam.

default:
return Lists.transform(
storage.get(infos[0], infos[1], Arrays.copyOfRange(infos, 2, length)),
new Function<BlobInfo, Blob>() {
@Override
public Blob apply(BlobInfo f) {
return f != null ? new Blob(storage, f) : null;
}
});
}
}

/**
* Updates the requested blobs. If {@code infos.length == 0} an empty list is returned. If
* {@code infos.length > 1} a batch request is used to update blobs.
*
* @param storage the storage service used to issue the request
* @param infos the blobs to update
* @return a list of {@code Blob} objects. If a blob does not exist the corresponding item in the

This comment was marked as spam.

* list is {@code null}.
*/
public static List<Blob> update(final Storage storage, BlobInfo... infos) {
checkNotNull(storage);
checkNotNull(infos);
int length = infos.length;
switch (length) {
case 0:
return Collections.emptyList();

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

case 1:
return Collections.singletonList(new Blob(storage, storage.update(infos[0])));
default:
return Lists.transform(
storage.update(infos[0], infos[1], Arrays.copyOfRange(infos, 2, length)),
new Function<BlobInfo, Blob>() {
@Override
public Blob apply(BlobInfo f) {
return f != null ? new Blob(storage, f) : null;
}
});
}
}

/**
* Deletes the requested blobs. If {@code infos.length == 0} an empty list is returned. If
* {@code infos.length > 1} a batch request is used to delete blobs.
*
* @param storage the storage service used to issue the request
* @param infos the blobs to delete
* @return a list of booleans. If a blob has been deleted the corresponding item in the list is
* {@code true}. If deletion failed the item is {@code false}.
*/
public static List<Boolean> delete(Storage storage, BlobInfo... infos) {
checkNotNull(storage);
checkNotNull(infos);
int length = infos.length;
switch (length) {
case 0:
return Collections.emptyList();
case 1:
return Collections.singletonList(storage.delete(infos[0].bucket(), infos[0].name()));
default:
return storage.delete(infos[0], infos[1], Arrays.copyOfRange(infos, 2, length));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -638,4 +638,37 @@ public static Builder builder() {
* @see <a href="https://cloud.google.com/storage/docs/access-control#Signed-URLs">Signed-URLs</a>
*/
URL signUrl(BlobInfo blobInfo, long expirationTimeInSeconds, SignUrlOption... options);

/**
* Gets the requested blobs. A batch request is used to perform this call.
*
* @param blobInfo1 first blob to get
* @param blobInfo2 second blob to get
* @param blobInfos other blobs to get
* @return a list of {@code BlobInfo} objects. If a blob does not exist the corresponding item in
* the list is {@code null}.
*/
List<BlobInfo> get(BlobInfo blobInfo1, BlobInfo blobInfo2, BlobInfo... blobInfos);

/**
* Updates the requested blobs. A batch request is used to perform this call.
*
* @param blobInfo1 first blob to update
* @param blobInfo2 second blob to update
* @param blobInfos other blobs to update
* @return a list of {@code BlobInfo} objects. If a blob does not exist the corresponding item in
* the list is {@code null}.
*/
List<BlobInfo> update(BlobInfo blobInfo1, BlobInfo blobInfo2, BlobInfo... blobInfos);

/**
* Deletes the requested blobs. A batch request is used to perform this call.
*
* @param blobInfo1 first blob to delete
* @param blobInfo2 second blob to delete
* @param blobInfos other blobs to delete
* @return a list of booleans. If a blob has been deleted the corresponding item in the list is
* {@code true}. If deletion failed the item is {@code false}.
*/
List<Boolean> delete(BlobInfo blobInfo1, BlobInfo blobInfo2, BlobInfo... blobInfos);
}
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,68 @@ public URL signUrl(BlobInfo blobInfo, long expiration, SignUrlOption... options)
}
}

@Override
public List<BlobInfo> get(BlobInfo blobInfo1, BlobInfo blobInfo2, BlobInfo... blobInfos) {

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

List<Tuple<StorageObject, Map<StorageRpc.Option, ?>>> toGet =
Lists.newArrayListWithCapacity(blobInfos.length + 2);
toGet.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo1.toPb(), optionMap()));
toGet.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo2.toPb(), optionMap()));
for (BlobInfo blobInfo : blobInfos) {
toGet.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo.toPb(), optionMap()));
}
StorageRpc.BatchResponse response =

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

storageRpc.batch(new StorageRpc.BatchRequest(null, null, toGet));
return transformBatchResult(toGet, response.gets, BlobInfo.FROM_PB_FUNCTION, (BlobInfo) null);
}

@Override
public List<BlobInfo> update(BlobInfo blobInfo1, BlobInfo blobInfo2, BlobInfo... blobInfos) {
List<Tuple<StorageObject, Map<StorageRpc.Option, ?>>> toUpdate =
Lists.newArrayListWithCapacity(blobInfos.length + 2);
toUpdate.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo1.toPb(), optionMap()));
toUpdate.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo2.toPb(), optionMap()));
for (BlobInfo blobInfo : blobInfos) {
toUpdate
.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo.toPb(), optionMap()));
}
StorageRpc.BatchResponse response =
storageRpc.batch(new StorageRpc.BatchRequest(null, toUpdate, null));
return transformBatchResult(toUpdate, response.updates, BlobInfo.FROM_PB_FUNCTION,
(BlobInfo) null);
}

@Override
public List<Boolean> delete(BlobInfo blobInfo1, BlobInfo blobInfo2, BlobInfo... blobInfos) {
List<Tuple<StorageObject, Map<StorageRpc.Option, ?>>> toDelete =
Lists.newArrayListWithCapacity(blobInfos.length + 2);
toDelete.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo1.toPb(), optionMap()));
toDelete.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo2.toPb(), optionMap()));
for (BlobInfo blobInfo : blobInfos) {
toDelete
.add(Tuple.<StorageObject, Map<StorageRpc.Option, ?>>of(blobInfo.toPb(), optionMap()));
}
StorageRpc.BatchResponse response =
storageRpc.batch(new StorageRpc.BatchRequest(toDelete, null, null));
return transformBatchResult(toDelete, response.deletes, Functions.<Boolean>identity(),
Boolean.FALSE);
}

private <I, O extends Serializable> List<O> transformBatchResult(
Iterable<Tuple<StorageObject, Map<StorageRpc.Option, ?>>> request,
Map<StorageObject, Tuple<I, StorageException>> results, Function<I, O> transform,
O errorValue) {
List<O> response = Lists.newArrayListWithCapacity(results.size());
for (Tuple<StorageObject, ?> tuple : request) {
Tuple<I, StorageException> result = results.get(tuple.x());
if (result.x() != null) {
response.add(transform.apply(result.x()));
} else {
response.add(errorValue);
}
}
return response;
}

private Map<StorageRpc.Option, ?> optionMap(Long generation, Long metaGeneration,
Iterable<? extends Option> options) {
return optionMap(generation, metaGeneration, options, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,25 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import com.google.api.client.util.Lists;
import com.google.gcloud.storage.Storage.CopyRequest;
import org.easymock.Capture;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

public class BlobTest {

private static final BlobInfo BLOB_INFO = BlobInfo.of("b", "n");
private static final BlobInfo[] BLOB_INFO_ARRAY = {BlobInfo.of("b1", "n1"),
BlobInfo.of("b2", "n2"), BlobInfo.of("b3", "n3")};

private Storage storage;
private Blob blob;
Expand Down Expand Up @@ -159,4 +165,129 @@ public void testSignUrl() throws Exception {
replay(storage);
assertEquals(url, blob.signUrl(100));
}

@Test
public void testGetNone() throws Exception {
replay(storage);
assertTrue(Blob.get(storage).isEmpty());

This comment was marked as spam.

}

@Test
public void testGetOne() throws Exception {
expect(storage.get(BLOB_INFO.bucket(), BLOB_INFO.name())).andReturn(BLOB_INFO);
replay(storage);
List<Blob> result = Blob.get(storage, BLOB_INFO);
assertEquals(1, result.size());
assertEquals(BLOB_INFO, result.get(0).info());
}

@Test
public void testGetSome() throws Exception {
List<BlobInfo> blobInfoList = Arrays.asList(BLOB_INFO_ARRAY);
expect(storage.get(BLOB_INFO_ARRAY[0], BLOB_INFO_ARRAY[1],
Arrays.copyOfRange(BLOB_INFO_ARRAY, 2, BLOB_INFO_ARRAY.length))).andReturn(blobInfoList);
replay(storage);
List<Blob> result = Blob.get(storage, BLOB_INFO_ARRAY);
assertEquals(blobInfoList.size(), result.size());
for (int i = 0; i < blobInfoList.size(); i++) {
assertEquals(blobInfoList.get(i), result.get(i).info());
}
}

@Test
public void testGetSomeNull() throws Exception {
List<BlobInfo> blobInfoList = Arrays.asList(BLOB_INFO_ARRAY[0], null, BLOB_INFO_ARRAY[2]);

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

expect(storage.get(BLOB_INFO_ARRAY[0], BLOB_INFO_ARRAY[1],
Arrays.copyOfRange(BLOB_INFO_ARRAY, 2, BLOB_INFO_ARRAY.length))).andReturn(blobInfoList);
replay(storage);
List<Blob> result = Blob.get(storage, BLOB_INFO_ARRAY);
assertEquals(blobInfoList.size(), result.size());
for (int i = 0; i < blobInfoList.size(); i++) {
if (blobInfoList.get(i) != null) {
assertEquals(blobInfoList.get(i), result.get(i).info());
} else {
assertNull(result.get(i));
}
}
}

@Test
public void testUpdateNone() throws Exception {
replay(storage);
assertTrue(Blob.update(storage).isEmpty());
}

@Test
public void testUpdateOne() throws Exception {
BlobInfo updatedBlob = BLOB_INFO.toBuilder().contentType("content").build();
expect(storage.update(BLOB_INFO)).andReturn(updatedBlob);
replay(storage);
List<Blob> result = Blob.update(storage, BLOB_INFO);
assertEquals(1, result.size());
assertEquals(updatedBlob, result.get(0).info());
}

@Test
public void testUpdateSome() throws Exception {
List<BlobInfo> blobInfoList = Lists.newArrayListWithCapacity(BLOB_INFO_ARRAY.length);
for (BlobInfo info : BLOB_INFO_ARRAY) {
blobInfoList.add(info.toBuilder().contentType("content").build());
}
expect(storage.update(BLOB_INFO_ARRAY[0], BLOB_INFO_ARRAY[1],
Arrays.copyOfRange(BLOB_INFO_ARRAY, 2, BLOB_INFO_ARRAY.length))).andReturn(blobInfoList);
replay(storage);
List<Blob> result = Blob.update(storage, BLOB_INFO_ARRAY);
assertEquals(blobInfoList.size(), result.size());
for (int i = 0; i < blobInfoList.size(); i++) {
assertEquals(blobInfoList.get(i), result.get(i).info());
}
}

@Test
public void testUpdateSomeNull() throws Exception {
List<BlobInfo> blobInfoList = Arrays.asList(
BLOB_INFO_ARRAY[0].toBuilder().contentType("content").build(), null,
BLOB_INFO_ARRAY[2].toBuilder().contentType("content").build());
expect(storage.update(BLOB_INFO_ARRAY[0], BLOB_INFO_ARRAY[1],
Arrays.copyOfRange(BLOB_INFO_ARRAY, 2, BLOB_INFO_ARRAY.length))).andReturn(blobInfoList);
replay(storage);
List<Blob> result = Blob.update(storage, BLOB_INFO_ARRAY);
assertEquals(blobInfoList.size(), result.size());
for (int i = 0; i < blobInfoList.size(); i++) {
if (blobInfoList.get(i) != null) {
assertEquals(blobInfoList.get(i), result.get(i).info());
} else {
assertNull(result.get(i));
}
}
}

@Test
public void testDeleteNone() throws Exception {
replay(storage);
assertTrue(Blob.delete(storage).isEmpty());
}

@Test
public void testDeleteOne() throws Exception {
expect(storage.delete(BLOB_INFO.bucket(), BLOB_INFO.name())).andReturn(true);
replay(storage);
List<Boolean> result = Blob.delete(storage, BLOB_INFO);
assertEquals(1, result.size());
assertTrue(result.get(0));
}

@Test
public void testDeleteSome() throws Exception {
List<Boolean> deleleResultList = Arrays.asList(true, true, true);
expect(storage.delete(BLOB_INFO_ARRAY[0], BLOB_INFO_ARRAY[1],
Arrays.copyOfRange(BLOB_INFO_ARRAY, 2, BLOB_INFO_ARRAY.length)))
.andReturn(deleleResultList);
replay(storage);
List<Boolean> result = Blob.delete(storage, BLOB_INFO_ARRAY);
assertEquals(deleleResultList.size(), result.size());
for (int i = 0; i < deleleResultList.size(); i++) {
assertEquals(deleleResultList.get(i), result.get(i));
}
}
}
Loading