From 8dfc0cbf8294a7fc426948e22e5c2182da97b630 Mon Sep 17 00:00:00 2001 From: Ajit Thakor <49403056+athakor@users.noreply.github.com> Date: Thu, 25 Jun 2020 20:38:57 +0530 Subject: [PATCH] feat: expose all the methods of notification (#141) * feat: expose all the methods of notification * feat: remove unsed code * feat: fix review changes * build: fix require upper bound dependencies errors * build: fix clirr ignore differences * feat: implement getNotification inside StorageRpcTestBase class and fix the builds checks --- .../clirr-ignored-differences.xml | 25 ++ google-cloud-storage/pom.xml | 5 + .../cloud/storage/NotificationInfo.java | 309 ++++++++++++++++++ .../com/google/cloud/storage/Storage.java | 33 ++ .../com/google/cloud/storage/StorageImpl.java | 74 +++++ .../cloud/storage/spi/v1/HttpStorageRpc.java | 15 + .../storage/spi/v1/HttpStorageRpcSpans.java | 2 + .../cloud/storage/spi/v1/StorageRpc.java | 8 + .../storage/testing/StorageRpcTestBase.java | 5 + .../cloud/storage/NotificationInfoTest.java | 102 ++++++ .../google/cloud/storage/StorageImplTest.java | 90 ++++- .../cloud/storage/it/ITStorageTest.java | 59 ++++ .../testing/StorageRpcTestBaseTest.java | 11 + pom.xml | 6 + 14 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml index ace7a6ef0..2aababb9e 100644 --- a/google-cloud-storage/clirr-ignored-differences.xml +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -26,4 +26,29 @@ com.google.cloud.storage.BucketInfo$Builder deleteLifecycleRules() 7013 + + com/google/cloud/storage/Storage + com.google.api.services.storage.model.Notification createNotification(java.lang.String, com.google.cloud.storage.NotificationInfo) + 7012 + + + com/google/cloud/storage/Storage + com.google.api.services.storage.model.Notification getNotification(java.lang.String, java.lang.String) + 7012 + + + com/google/cloud/storage/Storage + java.util.List listNotifications(java.lang.String) + 7012 + + + com/google/cloud/storage/Storage + boolean deleteNotification(java.lang.String, java.lang.String) + 7012 + + + com/google/cloud/storage/spi/v1/StorageRpc + com.google.api.services.storage.model.Notification getNotification(java.lang.String, java.lang.String) + 7012 + diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 3e2dbb0ba..1be67d6b3 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -155,6 +155,11 @@ truth test + + com.google.cloud + google-cloud-pubsub + test + org.easymock easymock diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java new file mode 100644 index 000000000..34c2d326d --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java @@ -0,0 +1,309 @@ +/* + * 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 com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.pathtemplate.PathTemplate; +import com.google.api.services.storage.model.Notification; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Google Storage Notification metadata; + * + * @see Concepts and + * Terminology + */ +public class NotificationInfo implements Serializable { + + private static final long serialVersionUID = 5725883368559753810L; + private static final PathTemplate PATH_TEMPLATE = + PathTemplate.createWithoutUrlEncoding("projects/{project}/topics/{topic}"); + + public enum PayloadFormat { + JSON_API_V1, + NONE + } + + static final Function FROM_PB_FUNCTION = + new Function() { + @Override + public NotificationInfo apply(Notification pb) { + return NotificationInfo.fromPb(pb); + } + }; + static final Function TO_PB_FUNCTION = + new Function() { + @Override + public Notification apply(NotificationInfo NotificationInfo) { + return NotificationInfo.toPb(); + } + }; + private final String generatedId; + private final String topic; + private final List eventTypes; + private final Map customAttributes; + private final PayloadFormat payloadFormat; + private final String objectNamePrefix; + private final String etag; + private final String selfLink; + + public static final class Builder { + + private String generatedId; + private String topic; + private List eventTypes; + private Map customAttributes; + private PayloadFormat payloadFormat; + private String objectNamePrefix; + private String etag; + private String selfLink; + + Builder(String topic) { + this.topic = topic; + } + + Builder(NotificationInfo NotificationInfo) { + generatedId = NotificationInfo.generatedId; + etag = NotificationInfo.etag; + selfLink = NotificationInfo.selfLink; + topic = NotificationInfo.topic; + eventTypes = NotificationInfo.eventTypes; + customAttributes = NotificationInfo.customAttributes; + payloadFormat = NotificationInfo.payloadFormat; + objectNamePrefix = NotificationInfo.objectNamePrefix; + } + + Builder setGeneratedId(String generatedId) { + this.generatedId = generatedId; + return this; + } + + Builder setSelfLink(String selfLink) { + this.selfLink = selfLink; + return this; + } + + /** The name of the topic. It must have the format "projects/{project}/topics/{topic}". */ + public Builder setTopic(String topic) { + this.topic = topic; + return this; + } + + public Builder setPayloadFormat(PayloadFormat payloadFormat) { + this.payloadFormat = payloadFormat; + return this; + } + + public Builder setObjectNamePrefix(String objectNamePrefix) { + this.objectNamePrefix = objectNamePrefix; + return this; + } + + public Builder setEventTypes(Iterable eventTypes) { + this.eventTypes = eventTypes != null ? ImmutableList.copyOf(eventTypes) : null; + return this; + } + + Builder setEtag(String etag) { + this.etag = etag; + return this; + } + + public Builder setCustomAttributes(Map customAttributes) { + this.customAttributes = + customAttributes != null ? ImmutableMap.copyOf(customAttributes) : null; + return this; + } + + public NotificationInfo build() { + checkNotNull(topic); + return new NotificationInfo(this); + } + } + + NotificationInfo(Builder builder) { + generatedId = builder.generatedId; + etag = builder.etag; + selfLink = builder.selfLink; + topic = builder.topic; + eventTypes = builder.eventTypes; + customAttributes = builder.customAttributes; + payloadFormat = builder.payloadFormat; + objectNamePrefix = builder.objectNamePrefix; + } + + /** Returns the service-generated id for the notification. */ + public String getGeneratedId() { + return generatedId; + } + + /** Returns the topic to which this subscription publishes. */ + public String getTopic() { + return topic; + } + + /** Returns the canonical URI of this topic as a string. */ + public String getSelfLink() { + return selfLink; + } + + /** Returns the desired content of the Payload. */ + public PayloadFormat getPayloadFormat() { + return payloadFormat; + } + + /** Returns the object name prefix for which this notification configuration applies. */ + public String getObjectNamePrefix() { + return objectNamePrefix; + } + + /** + * Returns HTTP 1.1 Entity tag for the notification. + * + * @see Entity Tags + */ + public String getEtag() { + return etag; + } + + /** + * Returns the list of event types that this notification will apply to. If empty, notifications + * will be sent on all event types. + * + * @see Cross-Origin Resource Sharing + * (CORS) + */ + public List getEventTypes() { + return eventTypes; + } + + /** + * Returns the list of additional attributes to attach to each Cloud PubSub message published for + * this notification subscription. + * + * @see + * About Access Control Lists + */ + public Map getCustomAttributes() { + return customAttributes; + } + + /** Returns a builder for the current notification. */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(getTopic()); + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj != null + && obj.getClass().equals(NotificationInfo.class) + && Objects.equals(toPb(), ((NotificationInfo) obj).toPb()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("topic", getTopic()).toString(); + } + + Notification toPb() { + Notification notificationPb = new Notification(); + notificationPb.setId(generatedId); + notificationPb.setEtag(etag); + if (customAttributes != null) { + notificationPb.setCustomAttributes(customAttributes); + } + if (eventTypes != null) { + notificationPb.setEventTypes(eventTypes); + } + if (objectNamePrefix != null) { + notificationPb.setObjectNamePrefix(objectNamePrefix); + } + if (payloadFormat != null) { + notificationPb.setPayloadFormat(payloadFormat.toString()); + } else { + notificationPb.setPayloadFormat(PayloadFormat.NONE.toString()); + } + notificationPb.setSelfLink(selfLink); + notificationPb.setTopic(topic); + + return notificationPb; + } + + /** + * Creates a {@code NotificationInfo} object for the provided topic name. + * + * @param topic The name of the topic. It must have the format + * "projects/{project}/topics/{topic}". + */ + public static NotificationInfo of(String topic) { + PATH_TEMPLATE.validatedMatch(topic, "topic name must be in valid format"); + return newBuilder(topic).build(); + } + + /** + * Returns a {@code NotificationInfo} builder where the topic's name is set to the provided name. + * + * @param topic The name of the topic. It must have the format + * "projects/{project}/topics/{topic}". + */ + public static Builder newBuilder(String topic) { + PATH_TEMPLATE.validatedMatch(topic, "topic name must be in valid format"); + return new Builder(topic); + } + + static NotificationInfo fromPb(Notification notificationPb) { + Builder builder = newBuilder(notificationPb.getTopic()); + if (notificationPb.getId() != null) { + builder.setGeneratedId(notificationPb.getId()); + } + if (notificationPb.getEtag() != null) { + builder.setEtag(notificationPb.getEtag()); + } + if (notificationPb.getCustomAttributes() != null) { + builder.setCustomAttributes(notificationPb.getCustomAttributes()); + } + if (notificationPb.getSelfLink() != null) { + builder.setSelfLink(notificationPb.getSelfLink()); + } + if (notificationPb.getObjectNamePrefix() != null) { + builder.setObjectNamePrefix(notificationPb.getObjectNamePrefix()); + } + if (notificationPb.getTopic() != null) { + builder.setTopic(notificationPb.getTopic()); + } + if (notificationPb.getEventTypes() != null) { + builder.setEventTypes(notificationPb.getEventTypes()); + } + if (notificationPb.getPayloadFormat() != null) { + builder.setPayloadFormat(PayloadFormat.valueOf(notificationPb.getPayloadFormat())); + } + return builder.build(); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 988f87d39..fa196c90d 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -21,6 +21,7 @@ import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.paging.Page; +import com.google.api.services.storage.model.Notification; import com.google.auth.ServiceAccountSigner; import com.google.auth.ServiceAccountSigner.SigningException; import com.google.cloud.FieldSelector; @@ -3433,4 +3434,36 @@ List testIamPermissions( * @throws StorageException upon failure */ ServiceAccount getServiceAccount(String projectId); + + /** + * Creates a notification with the specified entity on the specified bucket. + * + * @return the notification that was created. + * @throws StorageException upon failure + */ + Notification createNotification(String bucket, NotificationInfo notification); + + /** + * Get the notification with the specified name on the specified object. + * + * @return the notification object that exist on the bucket. + * @throws StorageException upon failure + */ + Notification getNotification(String bucket, String notification); + + /** + * List the notifications for the provided bucket. + * + * @return a list of {@link Notification} objects that exist on the bucket. + * @throws StorageException upon failure + */ + List listNotifications(String bucket); + + /** + * Deletes the notification with the specified name on the specified object. + * + * @return {@code true} if the notification was deleted, {@code false} if it was not found + * @throws StorageException upon failure + */ + boolean deleteNotification(String bucket, String notification); } 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 0e24521eb..ef46249d0 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 @@ -36,6 +36,7 @@ import com.google.api.gax.paging.Page; import com.google.api.services.storage.model.BucketAccessControl; +import com.google.api.services.storage.model.Notification; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.StorageObject; import com.google.api.services.storage.model.TestIamPermissionsResponse; @@ -1698,6 +1699,79 @@ public com.google.api.services.storage.model.ServiceAccount call() { } } + @Override + public Notification createNotification(final String bucket, final NotificationInfo notification) { + final com.google.api.services.storage.model.Notification notificationPb = notification.toPb(); + try { + return 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 notification) { + try { + return runWithRetries( + new Callable() { + @Override + public Notification call() { + return storageRpc.getNotification(bucket, notification); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock()); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + @Override + public List listNotifications(final String bucket) { + try { + return runWithRetries( + new Callable>() { + @Override + public List call() { + return storageRpc.listNotifications(bucket); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock()); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + @Override + public boolean deleteNotification(final String bucket, final String notification) { + try { + return runWithRetries( + new Callable() { + @Override + public Boolean call() { + return storageRpc.deleteNotification(bucket, notification); + } + }, + 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 e211c0277..97ceafa32 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 @@ -1489,6 +1489,21 @@ 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())); + throw translate(ex); + } 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 a5e4daf7d..11a33d8d0 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 36c7a5ff1..0291d920b 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 @@ -532,6 +532,14 @@ TestIamPermissionsResponse testIamPermissions( */ Notification createNotification(String bucket, Notification notification); + /** + * Get the notification with the specified name on the specified object. + * + * @return the notification object that exist on the bucket. + * @throws StorageException upon failure + */ + Notification getNotification(String bucket, String notification); + /** * 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 6bd2d487e..0f8e651a6 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 @@ -280,6 +280,11 @@ public Notification createNotification(String bucket, Notification notification) throw new UnsupportedOperationException("Not implemented yet"); } + @Override + public Notification getNotification(String bucket, String notification) { + 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..40b8a5ccf --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java @@ -0,0 +1,102 @@ +/* + * 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 com.google.cloud.storage.NotificationInfo.PayloadFormat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +public class NotificationInfoTest { + + 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 List EVENT_TYPES = + ImmutableList.of("OBJECT_FINALIZE", "OBJECT_METADATA_UPDATE"); + private static final String OBJECT_NAME_PREFIX = "index.html"; + private static final PayloadFormat PAYLOAD_FORMAT = 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 = + NotificationInfo.newBuilder(TOPIC) + .setEtag(ETAG) + .setCustomAttributes(CUSTOM_ATTRIBUTES) + .setSelfLink(SELF_LINK) + .setEventTypes(EVENT_TYPES) + .setObjectNamePrefix(OBJECT_NAME_PREFIX) + .setPayloadFormat(PAYLOAD_FORMAT) + .setGeneratedId(GENERATED_ID) + .build(); + + @Test + public void testToBuilder() { + compareBuckets(NOTIFICATION_INFO, NOTIFICATION_INFO.toBuilder().build()); + NotificationInfo bucketInfo = NOTIFICATION_INFO.toBuilder().setGeneratedId("id").build(); + assertEquals("id", bucketInfo.getGeneratedId()); + bucketInfo = bucketInfo.toBuilder().setGeneratedId(GENERATED_ID).build(); + compareBuckets(NOTIFICATION_INFO, bucketInfo); + } + + @Test + public void testToBuilderIncomplete() { + NotificationInfo incompleteBucketInfo = NotificationInfo.newBuilder(TOPIC).build(); + compareBuckets(incompleteBucketInfo, incompleteBucketInfo.toBuilder().build()); + } + + @Test + public void testOf() { + NotificationInfo bucketInfo = NotificationInfo.of(TOPIC); + assertEquals(TOPIC, bucketInfo.getTopic()); + } + + @Test + public void testBuilder() { + assertEquals(ETAG, NOTIFICATION_INFO.getEtag()); + assertEquals(GENERATED_ID, NOTIFICATION_INFO.getGeneratedId()); + assertEquals(SELF_LINK, NOTIFICATION_INFO.getSelfLink()); + assertEquals(EVENT_TYPES, NOTIFICATION_INFO.getEventTypes()); + assertEquals(OBJECT_NAME_PREFIX, NOTIFICATION_INFO.getObjectNamePrefix()); + assertEquals(PAYLOAD_FORMAT, NOTIFICATION_INFO.getPayloadFormat()); + assertEquals(TOPIC, NOTIFICATION_INFO.getTopic()); + assertEquals(CUSTOM_ATTRIBUTES, NOTIFICATION_INFO.getCustomAttributes()); + } + + @Test + public void testToPbAndFromPb() { + compareBuckets(NOTIFICATION_INFO, NotificationInfo.fromPb(NOTIFICATION_INFO.toPb())); + NotificationInfo bucketInfo = + NotificationInfo.of(TOPIC).toBuilder().setPayloadFormat(PayloadFormat.NONE).build(); + compareBuckets(bucketInfo, NotificationInfo.fromPb(bucketInfo.toPb())); + } + + private void compareBuckets(NotificationInfo expected, NotificationInfo value) { + assertEquals(expected, value); + assertEquals(expected.getGeneratedId(), value.getGeneratedId()); + assertEquals(expected.getCustomAttributes(), value.getCustomAttributes()); + assertEquals(expected.getEtag(), value.getEtag()); + assertEquals(expected.getSelfLink(), value.getSelfLink()); + assertEquals(expected.getEventTypes(), value.getEventTypes()); + assertEquals(expected.getObjectNamePrefix(), value.getObjectNamePrefix()); + assertEquals(expected.getPayloadFormat(), value.getPayloadFormat()); + assertEquals(expected.getTopic(), value.getTopic()); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java index 86bbdef79..ec06f2648 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java @@ -31,6 +31,7 @@ import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.api.core.ApiClock; import com.google.api.gax.paging.Page; +import com.google.api.services.storage.model.Notification; import com.google.api.services.storage.model.Policy.Bindings; import com.google.api.services.storage.model.StorageObject; import com.google.api.services.storage.model.TestIamPermissionsResponse; @@ -56,6 +57,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.io.BaseEncoding; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; @@ -72,6 +74,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -369,6 +372,38 @@ 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 List EVENT_TYPES = + ImmutableList.of("OBJECT_FINALIZE", "OBJECT_METADATA_UPDATE"); + private static final String OBJECT_NAME_PREFIX = "index.html"; + private static final NotificationInfo.PayloadFormat PAYLOAD_FORMAT = + NotificationInfo.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) + .setGeneratedId(GENERATED_ID) + .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) + .setGeneratedId(GENERATED_ID) + .build(); + private static final String ACCOUNT = "account"; private static PrivateKey privateKey; private static PublicKey publicKey; @@ -382,7 +417,8 @@ public long millisTime() { private Bucket expectedBucket1, expectedBucket2, expectedBucket3; @BeforeClass - public static void beforeClass() throws NoSuchAlgorithmException, InvalidKeySpecException { + public static void beforeClass() + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(BaseEncoding.base64().decode(PRIVATE_KEY_STRING)); @@ -2471,4 +2507,56 @@ public void testV4PostPolicy() { assertEquals(outputFields.get("key"), "my-object"); assertEquals("https://storage.googleapis.com/my-bucket/", policy.getUrl()); } + + @Test + public void testCreateNotification() { + Notification notification = NOTIFICATION_INFO_01.toPb(); + EasyMock.expect(storageRpcMock.createNotification(BUCKET_NAME1, notification)) + .andReturn(notification); + EasyMock.replay(storageRpcMock); + initializeService(); + Notification remoteNotification = + storage.createNotification(BUCKET_NAME1, NOTIFICATION_INFO_01); + compareBucketsNotification(remoteNotification); + } + + @Test + public void testGetNotification() { + EasyMock.expect(storageRpcMock.getNotification(BUCKET_NAME1, GENERATED_ID)) + .andReturn(NOTIFICATION_INFO_01.toPb()); + EasyMock.replay(storageRpcMock); + initializeService(); + Notification notification = storage.getNotification(BUCKET_NAME1, GENERATED_ID); + compareBucketsNotification(notification); + } + + @Test + public void testListNotification() { + EasyMock.expect(storageRpcMock.listNotifications(BUCKET_NAME1)) + .andReturn(Arrays.asList(NOTIFICATION_INFO_01.toPb(), NOTIFICATION_INFO_02.toPb())); + EasyMock.replay(storageRpcMock); + initializeService(); + List notifications = storage.listNotifications(BUCKET_NAME1); + assertEquals(2, notifications.size()); + } + + @Test + public void testDeleteNotification() { + EasyMock.expect(storageRpcMock.deleteNotification(BUCKET_NAME1, GENERATED_ID)).andReturn(true); + EasyMock.replay(storageRpcMock); + initializeService(); + Boolean isDeleted = storage.deleteNotification(BUCKET_NAME1, GENERATED_ID); + assertEquals(isDeleted, Boolean.TRUE); + } + + private void compareBucketsNotification(Notification value) { + assertEquals(GENERATED_ID, value.getId()); + assertEquals(CUSTOM_ATTRIBUTES, value.getCustomAttributes()); + assertEquals(ETAG, value.getEtag()); + assertEquals(SELF_LINK, value.getSelfLink()); + assertEquals(EVENT_TYPES, value.getEventTypes()); + assertEquals(OBJECT_NAME_PREFIX, value.getObjectNamePrefix()); + assertEquals(PAYLOAD_FORMAT.name(), value.getPayloadFormat()); + assertEquals(TOPIC, value.getTopic()); + } } 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 7966c9281..38fff667c 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 @@ -33,6 +33,7 @@ import com.google.api.client.http.apache.ApacheHttpTransport; import com.google.api.client.util.DateTime; import com.google.api.gax.paging.Page; +import com.google.api.services.storage.model.Notification; import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.GoogleCredentials; @@ -41,6 +42,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 +56,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; @@ -68,6 +71,7 @@ import com.google.cloud.storage.CopyWriter; import com.google.cloud.storage.HmacKey; import com.google.cloud.storage.HttpMethod; +import com.google.cloud.storage.NotificationInfo; import com.google.cloud.storage.PostPolicyV4; import com.google.cloud.storage.PostPolicyV4.PostFieldsV4; import com.google.cloud.storage.ServiceAccount; @@ -120,6 +124,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.logging.Level; @@ -142,6 +147,8 @@ public class ITStorageTest { private static RemoteStorageHelper remoteStorageHelper; private static Storage storage; + private static TopicAdminClient topicAdminClient; + private static Notification notification; private static String kmsKeyOneResourcePath; private static String kmsKeyTwoResourcePath; private static Metadata requestParamsHeader = new Metadata(); @@ -196,6 +203,15 @@ public class ITStorageTest { private static final ImmutableList LIFECYCLE_RULES = ImmutableList.of(LIFECYCLE_RULE_1, LIFECYCLE_RULE_2); + private static final String PROJECT = ServiceOptions.getDefaultProjectId(); + private static final String ID = UUID.randomUUID().toString().substring(0, 8); + private static final NotificationInfo.PayloadFormat PAYLOAD_FORMAT = + NotificationInfo.PayloadFormat.JSON_API_V1.JSON_API_V1; + + private static final String TOPIC = + String.format("projects/%s/topics/test_topic_foo_%s", PROJECT, ID); + private static final Map CUSTOM_ATTRIBUTES = ImmutableMap.of("label1", "value1"); + @BeforeClass public static void beforeClass() throws IOException { remoteStorageHelper = RemoteStorageHelper.create(); @@ -213,6 +229,22 @@ public static void beforeClass() throws IOException { // Prepare KMS KeyRing for CMEK tests prepareKmsKeys(); + + // Create pubsub topic for notification. + topicAdminClient = TopicAdminClient.create(); + topicAdminClient.createTopic(TOPIC); + com.google.iam.v1.Policy policy = topicAdminClient.getIamPolicy(TOPIC); + Binding binding = + Binding.newBuilder().setRole("roles/owner").addMembers("allAuthenticatedUsers").build(); + topicAdminClient.setIamPolicy(TOPIC, policy.toBuilder().addBindings(binding).build()); + + // Create a notification on a bucket. + NotificationInfo notificationInfo = + NotificationInfo.newBuilder(TOPIC) + .setCustomAttributes(CUSTOM_ATTRIBUTES) + .setPayloadFormat(PAYLOAD_FORMAT) + .build(); + notification = storage.createNotification(BUCKET, notificationInfo); } @Before @@ -235,6 +267,13 @@ public void beforeEach() { @AfterClass public static void afterClass() throws ExecutionException, InterruptedException { if (storage != null) { + + /* Delete the specified notification on the specified bucket as well as delete the pubsub topic */ + if (topicAdminClient != null) { + storage.deleteNotification(BUCKET, notification.getId()); + 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); @@ -3322,4 +3361,24 @@ public void testDeleteLifecycleRules() throws ExecutionException, InterruptedExc RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); } } + + @Test + public void testGetNotification() { + Notification actualNotification = storage.getNotification(BUCKET, notification.getId()); + assertEquals(CUSTOM_ATTRIBUTES, actualNotification.getCustomAttributes()); + assertEquals(PAYLOAD_FORMAT.name(), actualNotification.getPayloadFormat()); + assertTrue(actualNotification.getTopic().contains(TOPIC)); + } + + @Test + public void testListNotification() { + List notifications = storage.listNotifications(BUCKET); + for (Notification actualNotification : notifications) { + if (actualNotification.getId().equals(notification.getId())) { + assertEquals(CUSTOM_ATTRIBUTES, actualNotification.getCustomAttributes()); + assertEquals(PAYLOAD_FORMAT.name(), actualNotification.getPayloadFormat()); + assertTrue(actualNotification.getTopic().contains(TOPIC)); + } + } + } } 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 02c4dd394..aa0146a4c 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,12 @@ google-api-services-storage v1-rev20200430-1.30.9 + + com.google.cloud + google-cloud-pubsub + 1.106.0 + test + org.easymock easymock