diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml index c45b439cc..8374be3fc 100644 --- a/google-cloud-storage/clirr-ignored-differences.xml +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -11,4 +11,29 @@ BucketInfo$LifecycleRule$LifecycleAction() 7004 + + com/google/cloud/storage/Storage + *.Notification createNotification(*.String, *.NotificationInfo) + 7012 + + + com/google/cloud/storage/Storage + *.Notification getNotification(*.String, *.String) + 7012 + + + com/google/cloud/storage/Storage + java.util.List listNotifications(*.String) + 7012 + + + com/google/cloud/storage/Storage + boolean deleteNotification(*.String, *.String) + 7012 + + + com/google/cloud/storage/spi/v1/StorageRpc + *.model.Notification getNotification(*.String, *.String) + 7012 + diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index bddc08608..976f60f6c 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -147,6 +147,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/Notification.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java new file mode 100644 index 000000000..7f9107a58 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java @@ -0,0 +1,139 @@ +/* + * 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 java.util.Map; +import java.util.Objects; + +/** + * The class representing Pub/Sub notifications for the Storage. See pubsub-notifications for + * details. + */ +public class Notification extends NotificationInfo { + + private final StorageOptions options; + private transient Storage storage; + + /** Builder for {@code Notification}. */ + public static class Builder extends NotificationInfo.Builder { + private final Storage storage; + private final NotificationInfo.BuilderImpl infoBuilder; + + Builder(Notification notification) { + this.storage = notification.storage; + this.infoBuilder = new NotificationInfo.BuilderImpl(notification); + } + + @Override + Builder setNotificationId(String notificationId) { + infoBuilder.setNotificationId(notificationId); + return this; + } + + @Override + public Builder setSelfLink(String selfLink) { + infoBuilder.setSelfLink(selfLink); + return this; + } + + @Override + public Builder setTopic(String topic) { + infoBuilder.setTopic(topic); + return this; + } + + @Override + public Builder setPayloadFormat(PayloadFormat payloadFormat) { + infoBuilder.setPayloadFormat(payloadFormat); + return this; + } + + @Override + public Builder setObjectNamePrefix(String objectNamePrefix) { + infoBuilder.setObjectNamePrefix(objectNamePrefix); + return this; + } + + @Override + public Builder setEventTypes(EventType... eventTypes) { + infoBuilder.setEventTypes(eventTypes); + return this; + } + + @Override + public Builder setEtag(String etag) { + infoBuilder.setEtag(etag); + return this; + } + + @Override + public Builder setCustomAttributes(Map customAttributes) { + infoBuilder.setCustomAttributes(customAttributes); + return this; + } + + @Override + public Notification build() { + return new Notification(storage, infoBuilder); + } + } + + Notification(Storage storage, NotificationInfo.BuilderImpl infoBuilder) { + super(infoBuilder); + this.storage = checkNotNull(storage); + this.options = storage.getOptions(); + } + + /** Returns the notification's {@code Storage} object used to issue requests. */ + public Storage getStorage() { + return storage; + } + + @Override + public Builder toBuilder() { + return new Notification.Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Notification notification = (Notification) o; + return Objects.equals(toPb(), notification.toPb()) + && Objects.equals(options, notification.options); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), options, storage); + } + + static Notification fromPb( + Storage storage, com.google.api.services.storage.model.Notification notificationPb) { + return new Notification( + storage, new NotificationInfo.BuilderImpl(NotificationInfo.fromPb(notificationPb))); + } +} 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..df986ce2e --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java @@ -0,0 +1,362 @@ +/* + * 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.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** The class representing Pub/Sub Notification metadata for the Storage. */ +public class NotificationInfo implements Serializable { + + private static final long serialVersionUID = 5725883368559753810L; + private static final PathTemplate PATH_TEMPLATE = + PathTemplate.createWithoutUrlEncoding("projects/{project}/topics/{topic}"); + static final Function + FROM_PB_FUNCTION = + new Function() { + @Override + public NotificationInfo apply(com.google.api.services.storage.model.Notification pb) { + return NotificationInfo.fromPb(pb); + } + }; + static final Function + TO_PB_FUNCTION = + new Function() { + @Override + public com.google.api.services.storage.model.Notification apply( + NotificationInfo NotificationInfo) { + return NotificationInfo.toPb(); + } + }; + + public enum PayloadFormat { + JSON_API_V1, + NONE + } + + public enum EventType { + OBJECT_FINALIZE, + OBJECT_METADATA_UPDATE, + OBJECT_DELETE, + OBJECT_ARCHIVE + } + + private final String notificationId; + 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; + + /** Builder for {@code NotificationInfo}. */ + public abstract static class Builder { + Builder() {} + + abstract Builder setNotificationId(String notificationId); + + public abstract Builder setSelfLink(String selfLink); + + public abstract Builder setTopic(String topic); + + public abstract Builder setPayloadFormat(PayloadFormat payloadFormat); + + public abstract Builder setObjectNamePrefix(String objectNamePrefix); + + public abstract Builder setEventTypes(EventType... eventTypes); + + public abstract Builder setEtag(String etag); + + public abstract Builder setCustomAttributes(Map customAttributes); + + /** Creates a {@code NotificationInfo} object. */ + public abstract NotificationInfo build(); + } + + /** Builder for {@code NotificationInfo}. */ + public static class BuilderImpl extends Builder { + + private String notificationId; + private String topic; + private List eventTypes; + private Map customAttributes; + private PayloadFormat payloadFormat; + private String objectNamePrefix; + private String etag; + private String selfLink; + + BuilderImpl(String topic) { + this.topic = topic; + } + + BuilderImpl(NotificationInfo notificationInfo) { + notificationId = notificationInfo.notificationId; + etag = notificationInfo.etag; + selfLink = notificationInfo.selfLink; + topic = notificationInfo.topic; + eventTypes = notificationInfo.eventTypes; + customAttributes = notificationInfo.customAttributes; + payloadFormat = notificationInfo.payloadFormat; + objectNamePrefix = notificationInfo.objectNamePrefix; + } + + @Override + Builder setNotificationId(String notificationId) { + this.notificationId = notificationId; + return this; + } + + @Override + public Builder setSelfLink(String selfLink) { + this.selfLink = selfLink; + return this; + } + + /** Sets a topic in the format of "projects/{project}/topics/{topic}". */ + @Override + public Builder setTopic(String topic) { + this.topic = topic; + return this; + } + + @Override + public Builder setPayloadFormat(PayloadFormat payloadFormat) { + this.payloadFormat = payloadFormat; + return this; + } + + @Override + public Builder setObjectNamePrefix(String objectNamePrefix) { + this.objectNamePrefix = objectNamePrefix; + return this; + } + + @Override + public Builder setEventTypes(EventType... eventTypes) { + this.eventTypes = eventTypes != null ? Arrays.asList(eventTypes) : null; + return this; + } + + @Override + public Builder setEtag(String etag) { + this.etag = etag; + return this; + } + + @Override + public Builder setCustomAttributes(Map customAttributes) { + this.customAttributes = + customAttributes != null ? ImmutableMap.copyOf(customAttributes) : null; + return this; + } + + public NotificationInfo build() { + checkNotNull(topic); + checkTopicFormat(topic); + return new NotificationInfo(this); + } + } + + NotificationInfo(BuilderImpl builder) { + notificationId = builder.notificationId; + 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 getNotificationId() { + return notificationId; + } + + /** Returns the topic in Pub/Sub that receives notifications. */ + 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 NotificationInfo.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 events that trigger a notification to be sent. If empty, notifications are + * triggered by any event. See Event types to get + * list of available events. + */ + public List getEventTypes() { + return eventTypes; + } + + /** + * Returns the list of additional attributes to attach to each Cloud PubSub message published for + * this notification subscription. + */ + public Map getCustomAttributes() { + return customAttributes; + } + + @Override + public int hashCode() { + return toPb().hashCode(); + } + + @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", topic).toString(); + } + + com.google.api.services.storage.model.Notification toPb() { + com.google.api.services.storage.model.Notification notificationPb = + new com.google.api.services.storage.model.Notification(); + if (notificationId != null) { + notificationPb.setId(notificationId); + } + notificationPb.setEtag(etag); + if (customAttributes != null) { + notificationPb.setCustomAttributes(customAttributes); + } + if (eventTypes != null && eventTypes.size() > 0) { + List eventTypesPb = new ArrayList<>(); + for (EventType eventType : eventTypes) { + eventTypesPb.add(eventType.toString()); + } + notificationPb.setEventTypes(eventTypesPb); + } + 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. + * + *

Example of creating the NotificationInfo object: + * + *

{@code
+   * String topic = "projects/myProject/topics/myTopic"
+   * NotificationInfo notificationInfo = NotificationInfo.of(topic)
+   * }
+ * + * @param topic a string in the format "projects/{project}/topics/{topic}" + */ + public static NotificationInfo of(String topic) { + checkTopicFormat(topic); + return newBuilder(topic).build(); + } + /** + * Creates a {@code NotificationInfo} object for the provided topic. + * + * @param topic a string in the format "projects/{project}/topics/{topic}" + */ + public static Builder newBuilder(String topic) { + checkTopicFormat(topic); + return new BuilderImpl(topic); + } + + /** Returns a builder for the current notification. */ + public Builder toBuilder() { + return new BuilderImpl(this); + } + + static NotificationInfo fromPb( + com.google.api.services.storage.model.Notification notificationPb) { + NotificationInfo.Builder builder = new NotificationInfo.BuilderImpl(notificationPb.getTopic()); + if (notificationPb.getId() != null) { + builder.setNotificationId(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.getEventTypes() != null) { + List eventTypesPb = notificationPb.getEventTypes(); + EventType[] eventTypes = new EventType[eventTypesPb.size()]; + for (int index = 0; index < eventTypesPb.size(); index++) { + eventTypes[index] = EventType.valueOf(eventTypesPb.get(index)); + } + builder.setEventTypes(eventTypes); + } + if (notificationPb.getPayloadFormat() != null) { + builder.setPayloadFormat(PayloadFormat.valueOf(notificationPb.getPayloadFormat())); + } + return builder.build(); + } + + private static void checkTopicFormat(String topic) { + PATH_TEMPLATE.validatedMatch(topic, "topic name must be in valid format"); + } +} 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 211859945..634155f24 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 @@ -3625,4 +3625,84 @@ List 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
+ + com.google.cloud + google-cloud-pubsub + 1.106.0 + test + org.easymock easymock