testIamPermissions(
* @throws StorageException upon failure
*/
ServiceAccount getServiceAccount(String projectId);
+
+ /**
+ * Creates the notification for a given bucket.
+ *
+ * Example of creating a notification:
+ *
+ *
{@code
+ * String bucketName = "my-unique-bucket";
+ * String topic = "projects/myProject/topics/myTopic"
+ * NotificationInfo notificationInfo = NotificationInfo.newBuilder(topic)
+ * .setCustomAttributes(ImmutableMap.of("label1", "value1"))
+ * .setEventTypes(NotificationInfo.EventType.OBJECT_FINALIZE)
+ * .setPayloadFormat(NotificationInfo.PayloadFormat.JSON_API_V1)
+ * .build();
+ * Notification notification = storage.createNotification(bucketName, notificationInfo);
+ * }
+ *
+ * @param bucket name of the bucket
+ * @param notificationInfo notification to create
+ * @return the created notification
+ * @throws StorageException upon failure
+ */
+ Notification createNotification(String bucket, NotificationInfo notificationInfo);
+
+ /**
+ * Gets the notification with the specified id.
+ *
+ * Example of getting the notification:
+ *
+ *
{@code
+ * String bucketName = "my-unique-bucket";
+ * String notificationId = "my-unique-notification-id";
+ * Notification notification = storage.getNotification(bucketName, notificationId);
+ * }
+ *
+ * @param bucket name of the bucket
+ * @param notificationId notification ID
+ * @return the {@code Notification} object with the given id or {@code null} if not found
+ * @throws StorageException upon failure
+ */
+ Notification getNotification(String bucket, String notificationId);
+
+ /**
+ * Retrieves the list of notifications associated with the bucket.
+ *
+ * Example of listing the bucket notifications:
+ *
+ *
{@code
+ * String bucketName = "my-unique-bucket";
+ * List notifications = storage.listNotifications(bucketName);
+ * }
+ *
+ * @param bucket name of the bucket
+ * @return a list of {@link Notification} objects added to the bucket.
+ * @throws StorageException upon failure
+ */
+ List listNotifications(String bucket);
+
+ /**
+ * Deletes the notification with the specified id.
+ *
+ * Example of deleting the notification:
+ *
+ *
{@code
+ * String bucketName = "my-unique-bucket";
+ * String notificationId = "my-unique-notification-id";
+ * boolean deleted = storage.deleteNotification(bucketName, notificationId);
+ * if (deleted) {
+ * // the notification was deleted
+ * } else {
+ * // the notification was not found
+ * }
+ * }
+ *
+ * @param bucket name of the bucket
+ * @param notificationId ID of the notification to delete
+ * @return {@code true} if the notification has been deleted, {@code false} if not found
+ * @throws StorageException upon failure
+ */
+ boolean deleteNotification(String bucket, String notificationId);
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index e8263767d..874d997eb 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -16,6 +16,7 @@
package com.google.cloud.storage;
+import static com.google.cloud.RetryHelper.runWithRetries;
import static com.google.cloud.storage.PolicyHelper.convertToApiPolicy;
import static com.google.cloud.storage.SignedUrlEncodingHelper.Rfc3986UriEncode;
import static com.google.cloud.storage.spi.v1.StorageRpc.Option.DELIMITER;
@@ -44,6 +45,7 @@
import com.google.cloud.PageImpl.NextPageFetcher;
import com.google.cloud.Policy;
import com.google.cloud.ReadChannel;
+import com.google.cloud.RetryHelper.RetryHelperException;
import com.google.cloud.Tuple;
import com.google.cloud.WriteChannel;
import com.google.cloud.storage.Acl.Entity;
@@ -1374,6 +1376,98 @@ private U run(ResultRetryAlgorithm> algorithm, Callable c, Function<
return Retrying.run(getOptions(), algorithm, c, f);
}
+ @Override
+ public Notification createNotification(
+ final String bucket, final NotificationInfo notificationInfo) {
+ final com.google.api.services.storage.model.Notification notificationPb =
+ notificationInfo.toPb();
+ try {
+ return Notification.fromPb(
+ this,
+ runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.storage.model.Notification call() {
+ return storageRpc.createNotification(bucket, notificationPb);
+ }
+ },
+ getOptions().getRetrySettings(),
+ EXCEPTION_HANDLER,
+ getOptions().getClock()));
+ } catch (RetryHelperException e) {
+ throw StorageException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Notification getNotification(final String bucket, final String notificationId) {
+ try {
+ com.google.api.services.storage.model.Notification answer =
+ runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.storage.model.Notification call() {
+ return storageRpc.getNotification(bucket, notificationId);
+ }
+ },
+ getOptions().getRetrySettings(),
+ EXCEPTION_HANDLER,
+ getOptions().getClock());
+ return answer == null ? null : Notification.fromPb(this, answer);
+ } catch (RetryHelperException e) {
+ throw StorageException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public List listNotifications(final String bucket) {
+ try {
+ List answer =
+ runWithRetries(
+ new Callable>() {
+ @Override
+ public List call() {
+ return storageRpc.listNotifications(bucket);
+ }
+ },
+ getOptions().getRetrySettings(),
+ EXCEPTION_HANDLER,
+ getOptions().getClock());
+ return answer == null
+ ? ImmutableList.of()
+ : Lists.transform(
+ answer,
+ new com.google.common.base.Function<
+ com.google.api.services.storage.model.Notification, Notification>() {
+ @Override
+ public Notification apply(
+ com.google.api.services.storage.model.Notification notificationPb) {
+ return Notification.fromPb(getOptions().getService(), notificationPb);
+ }
+ });
+ } catch (RetryHelperException e) {
+ throw StorageException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public boolean deleteNotification(final String bucket, final String notificationId) {
+ try {
+ return runWithRetries(
+ new Callable() {
+ @Override
+ public Boolean call() {
+ return storageRpc.deleteNotification(bucket, notificationId);
+ }
+ },
+ getOptions().getRetrySettings(),
+ EXCEPTION_HANDLER,
+ getOptions().getClock());
+ } catch (RetryHelperException e) {
+ throw StorageException.translateAndThrow(e);
+ }
+ }
+
private static void addToOptionMap(
StorageRpc.Option option, T defaultValue, Map map) {
addToOptionMap(option, option, defaultValue, map);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
index 71b952c43..d0c1ff8a9 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
@@ -1602,6 +1602,25 @@ public Notification createNotification(String bucket, Notification notification)
}
}
+ @Override
+ public Notification getNotification(String bucket, String notification) {
+ Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_NOTIFICATION);
+ Scope scope = tracer.withSpan(span);
+ try {
+ return storage.notifications().get(bucket, notification).execute();
+ } catch (IOException ex) {
+ span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage()));
+ StorageException serviceException = translate(ex);
+ if (serviceException.getCode() == HTTP_NOT_FOUND) {
+ return null;
+ }
+ throw serviceException;
+ } finally {
+ scope.close();
+ span.end();
+ }
+ }
+
@Override
public Bucket lockRetentionPolicy(Bucket bucket, Map options) {
Span span = startSpan(HttpStorageRpcSpans.SPAN_LOCK_RETENTION_POLICY);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java
index 4e169d478..3f3d27d94 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java
@@ -89,6 +89,8 @@ class HttpStorageRpcSpans {
static final String SPAN_NAME_LIST_NOTIFICATIONS = getTraceSpanName("listNotifications(String)");
static final String SPAN_NAME_CREATE_NOTIFICATION =
getTraceSpanName("createNotification(String,Notification)");
+ static final String SPAN_NAME_GET_NOTIFICATION =
+ getTraceSpanName("getNotification(String,String)");
static final String SPAN_LOCK_RETENTION_POLICY =
getTraceSpanName("lockRetentionPolicy(String,Long)");
static final String SPAN_NAME_GET_SERVICE_ACCOUNT = getTraceSpanName("getServiceAccount(String)");
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java
index 201078efa..c600723bd 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java
@@ -561,15 +561,15 @@ TestIamPermissionsResponse testIamPermissions(
String bucket, List permissions, Map options);
/**
- * Deletes the notification with the specified name on the specified object.
+ * Deletes the notification with the specified id on the bucket.
*
- * @return {@code true} if the notification was deleted, {@code false} if it was not found
+ * @return {@code true} if the notification has been deleted, {@code false} if not found
* @throws StorageException upon failure
*/
- boolean deleteNotification(String bucket, String notification);
+ boolean deleteNotification(String bucket, String id);
/**
- * List the notifications for the provided bucket.
+ * Retrieves the list of notifications associated with the bucket.
*
* @return a list of {@link Notification} objects that exist on the bucket.
* @throws StorageException upon failure
@@ -577,13 +577,21 @@ TestIamPermissionsResponse testIamPermissions(
List listNotifications(String bucket);
/**
- * Creates a notification with the specified entity on the specified bucket.
+ * Creates the notification for a given bucket.
*
- * @return the notification that was created.
+ * @return the created notification.
* @throws StorageException upon failure
*/
Notification createNotification(String bucket, Notification notification);
+ /**
+ * Gets the notification with the specified id.
+ *
+ * @return the {@code Notification} object with the given id or {@code null} if not found
+ * @throws StorageException upon failure
+ */
+ Notification getNotification(String bucket, String id);
+
/**
* Lock retention policy for the provided bucket.
*
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java
index 43e29d011..81e7eee58 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java
@@ -287,7 +287,7 @@ public TestIamPermissionsResponse testIamPermissions(
}
@Override
- public boolean deleteNotification(String bucket, String notification) {
+ public boolean deleteNotification(String bucket, String id) {
throw new UnsupportedOperationException("Not implemented yet");
}
@@ -301,6 +301,11 @@ public Notification createNotification(String bucket, Notification notification)
throw new UnsupportedOperationException("Not implemented yet");
}
+ @Override
+ public Notification getNotification(String bucket, String id) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
@Override
public Bucket lockRetentionPolicy(Bucket bucket, Map options) {
throw new UnsupportedOperationException("Not implemented yet");
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java
new file mode 100644
index 000000000..fd50120a3
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.storage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.google.cloud.storage.NotificationInfo.EventType;
+import com.google.cloud.storage.NotificationInfo.PayloadFormat;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Test;
+
+public class NotificationInfoTest {
+ private static final String ETAG = "0xFF00";
+ private static final String SELF_LINK = "http://storage/b/n";
+ private static final String OBJECT_NAME_PREFIX = "index.html";
+ private static final String TOPIC = "projects/myProject/topics/topic1";
+ private static final Map CUSTOM_ATTRIBUTES = ImmutableMap.of("label1", "value1");
+ private static final PayloadFormat PAYLOAD_FORMAT = PayloadFormat.JSON_API_V1.JSON_API_V1;
+ private static final EventType[] EVENT_TYPES = {
+ EventType.OBJECT_FINALIZE, EventType.OBJECT_METADATA_UPDATE
+ };
+ private static final NotificationInfo NOTIFICATION_INFO =
+ NotificationInfo.newBuilder(TOPIC)
+ .setEtag(ETAG)
+ .setCustomAttributes(CUSTOM_ATTRIBUTES)
+ .setSelfLink(SELF_LINK)
+ .setEventTypes(EVENT_TYPES)
+ .setObjectNamePrefix(OBJECT_NAME_PREFIX)
+ .setPayloadFormat(PAYLOAD_FORMAT)
+ .build();
+
+ @Test
+ public void testToBuilder() {
+ compareBucketsNotification(NOTIFICATION_INFO, NOTIFICATION_INFO.toBuilder().build());
+ NotificationInfo notificationInfo = NOTIFICATION_INFO.toBuilder().setTopic(TOPIC).build();
+ assertEquals(TOPIC, notificationInfo.getTopic());
+ notificationInfo = notificationInfo.toBuilder().setTopic(TOPIC).build();
+ compareBucketsNotification(NOTIFICATION_INFO, notificationInfo);
+ }
+
+ @Test
+ public void testToBuilderIncomplete() {
+ NotificationInfo incompleteNotificationInfo = Notification.newBuilder(TOPIC).build();
+ compareBucketsNotification(
+ incompleteNotificationInfo, incompleteNotificationInfo.toBuilder().build());
+ }
+
+ @Test
+ public void testOf() {
+ NotificationInfo notificationInfo = NotificationInfo.of(TOPIC);
+ assertEquals(TOPIC, notificationInfo.getTopic());
+ assertNull(notificationInfo.getNotificationId());
+ assertNull(notificationInfo.getCustomAttributes());
+ assertNull(notificationInfo.getEtag());
+ assertNull(notificationInfo.getSelfLink());
+ assertNull(notificationInfo.getEventTypes());
+ assertNull(notificationInfo.getObjectNamePrefix());
+ assertNull(notificationInfo.getPayloadFormat());
+ }
+
+ @Test
+ public void testBuilder() {
+ assertEquals(ETAG, NOTIFICATION_INFO.getEtag());
+ assertNull(NOTIFICATION_INFO.getNotificationId());
+ assertEquals(SELF_LINK, NOTIFICATION_INFO.getSelfLink());
+ assertEquals(OBJECT_NAME_PREFIX, NOTIFICATION_INFO.getObjectNamePrefix());
+ assertEquals(PAYLOAD_FORMAT, NOTIFICATION_INFO.getPayloadFormat());
+ assertEquals(TOPIC, NOTIFICATION_INFO.getTopic());
+ assertEquals(CUSTOM_ATTRIBUTES, NOTIFICATION_INFO.getCustomAttributes());
+ assertEquals(Arrays.asList(EVENT_TYPES), NOTIFICATION_INFO.getEventTypes());
+ }
+
+ @Test
+ public void testToPbAndFromPb() {
+ compareBucketsNotification(
+ NOTIFICATION_INFO, NotificationInfo.fromPb(NOTIFICATION_INFO.toPb()));
+ NotificationInfo notificationInfo =
+ NotificationInfo.of(TOPIC).toBuilder().setPayloadFormat(PayloadFormat.NONE).build();
+ compareBucketsNotification(notificationInfo, Notification.fromPb(notificationInfo.toPb()));
+ }
+
+ private void compareBucketsNotification(NotificationInfo expected, NotificationInfo actual) {
+ assertEquals(expected, actual);
+ assertEquals(expected.getNotificationId(), actual.getNotificationId());
+ assertEquals(expected.getCustomAttributes(), actual.getCustomAttributes());
+ assertEquals(expected.getEtag(), actual.getEtag());
+ assertEquals(expected.getSelfLink(), actual.getSelfLink());
+ assertEquals(expected.getEventTypes(), actual.getEventTypes());
+ assertEquals(expected.getObjectNamePrefix(), actual.getObjectNamePrefix());
+ assertEquals(expected.getPayloadFormat(), actual.getPayloadFormat());
+ assertEquals(expected.getTopic(), actual.getTopic());
+ }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java
new file mode 100644
index 000000000..2f1f7eb3c
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.storage;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+
+import com.google.cloud.storage.NotificationInfo.EventType;
+import com.google.cloud.storage.NotificationInfo.PayloadFormat;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NotificationTest {
+
+ private static final String ETAG = "0xFF00";
+ private static final String SELF_LINK = "http://storage/b/n";
+ private static final String OBJECT_NAME_PREFIX = "index.html";
+ private static final String TOPIC = "projects/myProject/topics/topic1";
+ private static final Map CUSTOM_ATTRIBUTES = ImmutableMap.of("label1", "value1");
+ private static final PayloadFormat PAYLOAD_FORMAT = PayloadFormat.JSON_API_V1.JSON_API_V1;
+ private static final EventType[] EVENT_TYPES = {
+ EventType.OBJECT_FINALIZE, EventType.OBJECT_METADATA_UPDATE
+ };
+ private static final NotificationInfo NOTIFICATION_INFO =
+ NotificationInfo.newBuilder(TOPIC)
+ .setEtag(ETAG)
+ .setCustomAttributes(CUSTOM_ATTRIBUTES)
+ .setSelfLink(SELF_LINK)
+ .setEventTypes(EVENT_TYPES)
+ .setObjectNamePrefix(OBJECT_NAME_PREFIX)
+ .setPayloadFormat(PAYLOAD_FORMAT)
+ .build();
+
+ private Storage storage;
+ private StorageOptions mockOptions = createMock(StorageOptions.class);
+
+ @Before
+ public void setUp() {
+ storage = createStrictMock(Storage.class);
+ }
+
+ @After
+ public void tearDown() {
+ verify(storage);
+ }
+
+ @Test
+ public void testBuilder() {
+ expect(storage.getOptions()).andReturn(mockOptions).times(2);
+ replay(storage);
+ Notification.Builder builder =
+ new Notification.Builder(
+ new Notification(storage, new NotificationInfo.BuilderImpl(NOTIFICATION_INFO)));
+ Notification notification =
+ builder
+ .setEtag(ETAG)
+ .setCustomAttributes(CUSTOM_ATTRIBUTES)
+ .setSelfLink(SELF_LINK)
+ .setEventTypes(EVENT_TYPES)
+ .setObjectNamePrefix(OBJECT_NAME_PREFIX)
+ .setPayloadFormat(PAYLOAD_FORMAT)
+ .build();
+ assertEquals(ETAG, notification.getEtag());
+ assertEquals(SELF_LINK, notification.getSelfLink());
+ assertEquals(OBJECT_NAME_PREFIX, notification.getObjectNamePrefix());
+ assertEquals(PAYLOAD_FORMAT, notification.getPayloadFormat());
+ assertEquals(TOPIC, notification.getTopic());
+ assertEquals(CUSTOM_ATTRIBUTES, notification.getCustomAttributes());
+ assertEquals(Arrays.asList(EVENT_TYPES), notification.getEventTypes());
+ }
+
+ @Test
+ public void testToBuilder() {
+ expect(storage.getOptions()).andReturn(mockOptions).times(2);
+ replay(storage);
+ Notification notification =
+ new Notification(storage, new NotificationInfo.BuilderImpl(NOTIFICATION_INFO));
+ compareBucketNotification(notification, notification.toBuilder().build());
+ }
+
+ @Test
+ public void testFromPb() {
+ expect(storage.getOptions()).andReturn(mockOptions).times(1);
+ replay(storage);
+ compareBucketNotification(
+ NOTIFICATION_INFO, Notification.fromPb(storage, NOTIFICATION_INFO.toPb()));
+ }
+
+ private void compareBucketNotification(NotificationInfo expected, NotificationInfo actual) {
+ assertEquals(expected.getNotificationId(), actual.getNotificationId());
+ assertEquals(expected.getCustomAttributes(), actual.getCustomAttributes());
+ assertEquals(expected.getEtag(), actual.getEtag());
+ assertEquals(expected.getSelfLink(), actual.getSelfLink());
+ assertEquals(expected.getEventTypes(), actual.getEventTypes());
+ assertEquals(expected.getObjectNamePrefix(), actual.getObjectNamePrefix());
+ assertEquals(expected.getPayloadFormat(), actual.getPayloadFormat());
+ assertEquals(expected.getTopic().trim(), actual.getTopic().trim());
+ }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java
index 52c312b47..e69e3fe09 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java
@@ -60,6 +60,7 @@
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
import org.junit.Before;
@@ -366,6 +367,37 @@ public long millisTime() {
.put('~', "~")
.build();
+ // Notification
+ private static final String ETAG = "0xFF00";
+ private static final String GENERATED_ID = "B/N:1";
+ private static final String SELF_LINK = "http://storage/b/n";
+ private static final Notification.EventType[] EVENT_TYPES = {
+ Notification.EventType.OBJECT_FINALIZE, Notification.EventType.OBJECT_METADATA_UPDATE
+ };
+ private static final String OBJECT_NAME_PREFIX = "index.html";
+ private static final Notification.PayloadFormat PAYLOAD_FORMAT =
+ Notification.PayloadFormat.JSON_API_V1.JSON_API_V1;
+ private static final String TOPIC = "projects/myProject/topics/topic1";
+ private static final Map CUSTOM_ATTRIBUTES = ImmutableMap.of("label1", "value1");
+ private static final NotificationInfo NOTIFICATION_INFO_01 =
+ NotificationInfo.newBuilder(TOPIC)
+ .setEtag(ETAG)
+ .setCustomAttributes(CUSTOM_ATTRIBUTES)
+ .setSelfLink(SELF_LINK)
+ .setEventTypes(EVENT_TYPES)
+ .setObjectNamePrefix(OBJECT_NAME_PREFIX)
+ .setPayloadFormat(PAYLOAD_FORMAT)
+ .build();
+ private static final NotificationInfo NOTIFICATION_INFO_02 =
+ NotificationInfo.newBuilder(TOPIC)
+ .setEtag(ETAG)
+ .setCustomAttributes(CUSTOM_ATTRIBUTES)
+ .setSelfLink(SELF_LINK)
+ .setEventTypes(EVENT_TYPES)
+ .setObjectNamePrefix(OBJECT_NAME_PREFIX)
+ .setPayloadFormat(PAYLOAD_FORMAT)
+ .build();
+
private static final String ACCOUNT = "account";
private static PrivateKey privateKey;
private static PublicKey publicKey;
@@ -1643,4 +1675,55 @@ public void testWriterFailure() {
assertSame(STORAGE_FAILURE, e.getCause());
}
}
+
+ @Test
+ public void testCreateNotification() {
+ doReturn(NOTIFICATION_INFO_01.toPb())
+ .when(storageRpcMock)
+ .createNotification(BUCKET_NAME1, NOTIFICATION_INFO_01.toPb());
+ initializeService();
+ Notification notification = storage.createNotification(BUCKET_NAME1, NOTIFICATION_INFO_01);
+ verifyBucketNotification(notification);
+ }
+
+ @Test
+ public void testGetNotification() {
+ doReturn(NOTIFICATION_INFO_01.toPb())
+ .when(storageRpcMock)
+ .getNotification(BUCKET_NAME1, GENERATED_ID);
+ initializeService();
+ Notification notification = storage.getNotification(BUCKET_NAME1, GENERATED_ID);
+ verifyBucketNotification(notification);
+ }
+
+ @Test
+ public void testListNotification() {
+ doReturn(Arrays.asList(NOTIFICATION_INFO_01.toPb(), NOTIFICATION_INFO_02.toPb()))
+ .when(storageRpcMock)
+ .listNotifications(BUCKET_NAME1);
+ initializeService();
+ List notifications = storage.listNotifications(BUCKET_NAME1);
+ assertEquals(2, notifications.size());
+ verifyBucketNotification(notifications.get(0));
+ verifyBucketNotification(notifications.get(1));
+ }
+
+ @Test
+ public void testDeleteNotification() {
+ doReturn(true).when(storageRpcMock).deleteNotification(BUCKET_NAME1, GENERATED_ID);
+ initializeService();
+ Boolean isDeleted = storage.deleteNotification(BUCKET_NAME1, GENERATED_ID);
+ assertEquals(isDeleted, Boolean.TRUE);
+ }
+
+ private void verifyBucketNotification(Notification value) {
+ assertNull(value.getNotificationId());
+ assertEquals(CUSTOM_ATTRIBUTES, value.getCustomAttributes());
+ assertEquals(ETAG, value.getEtag());
+ assertEquals(SELF_LINK, value.getSelfLink());
+ assertEquals(OBJECT_NAME_PREFIX, value.getObjectNamePrefix());
+ assertEquals(PAYLOAD_FORMAT.name(), value.getPayloadFormat().name());
+ assertEquals(TOPIC, value.getTopic());
+ assertEquals(Arrays.asList(EVENT_TYPES), value.getEventTypes());
+ }
}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
index e9fa10a1c..80d1ebc38 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
@@ -41,6 +41,7 @@
import com.google.cloud.Policy;
import com.google.cloud.ReadChannel;
import com.google.cloud.RestorableState;
+import com.google.cloud.ServiceOptions;
import com.google.cloud.TransportOptions;
import com.google.cloud.WriteChannel;
import com.google.cloud.http.HttpTransportOptions;
@@ -54,6 +55,7 @@
import com.google.cloud.kms.v1.KeyManagementServiceGrpc.KeyManagementServiceBlockingStub;
import com.google.cloud.kms.v1.KeyRingName;
import com.google.cloud.kms.v1.LocationName;
+import com.google.cloud.pubsub.v1.TopicAdminClient;
import com.google.cloud.storage.Acl;
import com.google.cloud.storage.Acl.Role;
import com.google.cloud.storage.Acl.User;
@@ -71,6 +73,8 @@
import com.google.cloud.storage.HmacKey;
import com.google.cloud.storage.HmacKey.HmacKeyState;
import com.google.cloud.storage.HttpMethod;
+import com.google.cloud.storage.Notification;
+import com.google.cloud.storage.NotificationInfo;
import com.google.cloud.storage.PostPolicyV4;
import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
import com.google.cloud.storage.Rpo;
@@ -99,6 +103,7 @@
import com.google.common.reflect.AbstractInvocationHandler;
import com.google.common.reflect.Reflection;
import com.google.iam.v1.Binding;
+import com.google.iam.v1.GetIamPolicyRequest;
import com.google.iam.v1.IAMPolicyGrpc;
import com.google.iam.v1.SetIamPolicyRequest;
import io.grpc.ManagedChannel;
@@ -132,6 +137,7 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -162,6 +168,7 @@ public class ITStorageTest {
private static RemoteStorageHelper remoteStorageHelper;
private static Storage storage;
+ private static TopicAdminClient topicAdminClient;
private static String kmsKeyOneResourcePath;
private static String kmsKeyTwoResourcePath;
private static Metadata requestParamsHeader = new Metadata();
@@ -219,6 +226,13 @@ public class ITStorageTest {
@Rule public final TestName testName = new TestName();
@Rule public final DataGeneration dataGeneration = new DataGeneration(new Random(1234567890));
+ private static final String PROJECT = ServiceOptions.getDefaultProjectId();
+ private static final String ID = UUID.randomUUID().toString().substring(0, 8);
+ private static final String TOPIC =
+ String.format("projects/%s/topics/test_topic_foo_%s", PROJECT, ID).trim();
+ private static final Notification.PayloadFormat PAYLOAD_FORMAT =
+ Notification.PayloadFormat.JSON_API_V1.JSON_API_V1;
+ private static final Map CUSTOM_ATTRIBUTES = ImmutableMap.of("label1", "value1");
@BeforeClass
public static void beforeClass() throws IOException {
@@ -239,6 +253,9 @@ public static void beforeClass() throws IOException {
// Prepare KMS KeyRing for CMEK tests
prepareKmsKeys();
+
+ // Configure topic admin client for notification.
+ topicAdminClient = configureTopicAdminClient();
}
private static void unsetRequesterPays() {
@@ -260,6 +277,12 @@ private static void unsetRequesterPays() {
@AfterClass
public static void afterClass() throws ExecutionException, InterruptedException {
if (storage != null) {
+
+ /* Delete the Pub/Sub topic */
+ if (topicAdminClient != null) {
+ topicAdminClient.deleteTopic(TOPIC);
+ topicAdminClient.close();
+ }
// In beforeClass, we make buckets auto-delete blobs older than a day old.
// Here, delete all buckets older than 2 days. They should already be empty and easy.
long cleanTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2);
@@ -415,6 +438,23 @@ private static String ensureKmsKeyExistsForTests(
return kmsKeyResourcePath;
}
+ private static TopicAdminClient configureTopicAdminClient() throws IOException {
+ TopicAdminClient topicAdminClient = TopicAdminClient.create();
+ topicAdminClient.createTopic(TOPIC);
+ GetIamPolicyRequest getIamPolicyRequest =
+ GetIamPolicyRequest.newBuilder().setResource(TOPIC).build();
+ com.google.iam.v1.Policy policy = topicAdminClient.getIamPolicy(getIamPolicyRequest);
+ Binding binding =
+ Binding.newBuilder().setRole("roles/owner").addMembers("allAuthenticatedUsers").build();
+ SetIamPolicyRequest setIamPolicyRequest =
+ SetIamPolicyRequest.newBuilder()
+ .setResource(TOPIC)
+ .setPolicy(policy.toBuilder().addBindings(binding).build())
+ .build();
+ topicAdminClient.setIamPolicy(setIamPolicyRequest);
+ return topicAdminClient;
+ }
+
@Test(timeout = 5000)
public void testListBuckets() throws InterruptedException {
Iterator bucketIterator =
@@ -3788,6 +3828,53 @@ public void testBucketUpdateTime() throws ExecutionException, InterruptedExcepti
}
}
+ @Test
+ public void testNotification() throws InterruptedException, ExecutionException {
+ String bucketName = RemoteStorageHelper.generateBucketName();
+ storage.create(BucketInfo.newBuilder(bucketName).setLocation("us").build());
+ NotificationInfo notificationInfo =
+ NotificationInfo.newBuilder(TOPIC)
+ .setCustomAttributes(CUSTOM_ATTRIBUTES)
+ .setPayloadFormat(PAYLOAD_FORMAT)
+ .build();
+ try {
+ assertThat(storage.listNotifications(bucketName)).isEmpty();
+ Notification notification = storage.createNotification(bucketName, notificationInfo);
+ assertThat(notification.getNotificationId()).isNotNull();
+ assertThat(CUSTOM_ATTRIBUTES).isEqualTo(notification.getCustomAttributes());
+ assertThat(PAYLOAD_FORMAT.name()).isEqualTo(notification.getPayloadFormat().name());
+ assertThat(notification.getTopic().contains(TOPIC)).isTrue();
+
+ // Gets the notification with the specified id.
+ Notification actualNotification =
+ storage.getNotification(bucketName, notification.getNotificationId());
+ assertThat(actualNotification.getNotificationId())
+ .isEqualTo(notification.getNotificationId());
+ assertThat(actualNotification.getTopic().trim()).isEqualTo(notification.getTopic().trim());
+ assertThat(actualNotification.getEtag()).isEqualTo(notification.getEtag());
+ assertThat(actualNotification.getEventTypes()).isEqualTo(notification.getEventTypes());
+ assertThat(actualNotification.getPayloadFormat()).isEqualTo(notification.getPayloadFormat());
+ assertThat(actualNotification.getSelfLink()).isEqualTo(notification.getSelfLink());
+ assertThat(actualNotification.getCustomAttributes())
+ .isEqualTo(notification.getCustomAttributes());
+
+ // Retrieves the list of notifications associated with the bucket.
+ List notifications = storage.listNotifications(bucketName);
+ assertThat(notifications.size()).isEqualTo(1);
+ assertThat(notifications.get(0).getNotificationId())
+ .isEqualTo(actualNotification.getNotificationId());
+
+ // Deletes the notification with the specified id.
+ assertThat(storage.deleteNotification(bucketName, notification.getNotificationId())).isTrue();
+ assertThat(storage.deleteNotification(bucketName, notification.getNotificationId()))
+ .isFalse();
+ assertThat(storage.getNotification(bucketName, notification.getNotificationId())).isNull();
+ assertThat(storage.listNotifications(bucketName)).isEmpty();
+ } finally {
+ RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS);
+ }
+ }
+
@Test
public void testBlobTimeStorageClassUpdated() {
String blobName = "test-blob-with-storage-class";
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/StorageRpcTestBaseTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/StorageRpcTestBaseTest.java
index b798ad572..7feef9f18 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/StorageRpcTestBaseTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/StorageRpcTestBaseTest.java
@@ -577,6 +577,17 @@ public Notification call() {
};
}
+ @Test
+ public void testGetNotification() {
+ rpc =
+ new Callable() {
+ @Override
+ public Notification call() {
+ return STORAGE_RPC.getNotification("bucket", "notification");
+ }
+ };
+ }
+
@Test
public void testLockRetentionPolicy() {
rpc =
diff --git a/pom.xml b/pom.xml
index 321877adb..4fad76dfb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,12 @@
google-api-services-storage
v1-rev20220210-1.32.1