Skip to content

Commit 830052b

Browse files
authored
feat: implement GrpcStorageImpl#{get,list,create,delete}Notification (#1958)
Rewrite ITNotificationTest to leverage testbench for grpc and to separate different cases into their own individual tests rather than having a single large test. For now the backend hasn't yet implemented the rpcs for grpc, so we rely on testbench. Once the backend does provide we can remove the CrossRun.Ignore annotation on each of the methods.
1 parent 5a274d7 commit 830052b

File tree

11 files changed

+425
-88
lines changed

11 files changed

+425
-88
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ final class ApiaryConversions {
8989
// when converting from gRPC to apiary or vice-versa we want to preserve this property. Until
9090
// such a time as the apiary model has a project field, we manually apply it with this name.
9191
private static final String PROJECT_ID_FIELD_NAME = "x_project";
92+
// gRPC has a NotificationConfig.name property which contains the bucket the config is associated
93+
// with which that apiary doesn't have yet.
94+
// when converting from gRPC to apiary or vice-versa we want to preserve this property. Until
95+
// such a time as the apiary model has a bucket field, we manually apply it with this name.
96+
private static final String NOTIFICATION_BUCKET_FIELD_NAME = "x_bucket";
9297

9398
private final Codec<Entity, String> entityCodec =
9499
Codec.of(this::entityEncode, this::entityDecode);
@@ -774,6 +779,7 @@ private com.google.api.services.storage.model.Notification notificationEncode(
774779
to.setEtag(from.getEtag());
775780
to.setSelfLink(from.getSelfLink());
776781
to.setTopic(from.getTopic());
782+
ifNonNull(from.getBucket(), b -> to.set(NOTIFICATION_BUCKET_FIELD_NAME, b));
777783
ifNonNull(from.getNotificationId(), to::setId);
778784
ifNonNull(from.getCustomAttributes(), to::setCustomAttributes);
779785
ifNonNull(from.getObjectNamePrefix(), to::setObjectNamePrefix);
@@ -799,6 +805,7 @@ private com.google.api.services.storage.model.Notification notificationEncode(
799805
private NotificationInfo notificationDecode(
800806
com.google.api.services.storage.model.Notification from) {
801807
NotificationInfo.Builder builder = new NotificationInfo.BuilderImpl(from.getTopic());
808+
ifNonNull(from.get(NOTIFICATION_BUCKET_FIELD_NAME), String.class::cast, builder::setBucket);
802809
ifNonNull(from.getId(), builder::setNotificationId);
803810
ifNonNull(from.getEtag(), builder::setEtag);
804811
ifNonNull(from.getCustomAttributes(), builder::setCustomAttributes);

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.google.cloud.storage.Utils.ifNonNull;
2121
import static com.google.cloud.storage.Utils.lift;
2222
import static com.google.cloud.storage.Utils.projectNameCodec;
23+
import static com.google.cloud.storage.Utils.topicNameCodec;
2324

2425
import com.google.api.pathtemplate.PathTemplate;
2526
import com.google.cloud.Binding;
@@ -35,6 +36,8 @@
3536
import com.google.cloud.storage.BucketInfo.PublicAccessPrevention;
3637
import com.google.cloud.storage.Conversions.Codec;
3738
import com.google.cloud.storage.HmacKey.HmacKeyState;
39+
import com.google.cloud.storage.NotificationInfo.EventType;
40+
import com.google.cloud.storage.NotificationInfo.PayloadFormat;
3841
import com.google.common.annotations.VisibleForTesting;
3942
import com.google.common.collect.ImmutableList;
4043
import com.google.common.collect.ImmutableSet;
@@ -48,6 +51,8 @@
4851
import com.google.storage.v2.BucketAccessControl;
4952
import com.google.storage.v2.CryptoKeyName;
5053
import com.google.storage.v2.HmacKeyMetadata;
54+
import com.google.storage.v2.NotificationConfig;
55+
import com.google.storage.v2.NotificationConfigName;
5156
import com.google.storage.v2.Object;
5257
import com.google.storage.v2.ObjectAccessControl;
5358
import com.google.storage.v2.ObjectChecksums;
@@ -918,12 +923,54 @@ private BlobInfo blobInfoDecode(Object from) {
918923
return toBuilder.build();
919924
}
920925

921-
private com.google.storage.v2.NotificationConfig notificationEncode(NotificationInfo from) {
922-
return todo();
926+
private NotificationConfig notificationEncode(NotificationInfo from) {
927+
NotificationConfig.Builder to = NotificationConfig.newBuilder();
928+
String id = from.getNotificationId();
929+
if (id != null) {
930+
if (NotificationConfigName.isParsableFrom(id)) {
931+
ifNonNull(id, to::setName);
932+
} else {
933+
NotificationConfigName name = NotificationConfigName.of("_", from.getBucket(), id);
934+
to.setName(name.toString());
935+
}
936+
}
937+
ifNonNull(from.getTopic(), topicNameCodec::encode, to::setTopic);
938+
ifNonNull(from.getEtag(), to::setEtag);
939+
ifNonNull(from.getEventTypes(), toImmutableListOf(EventType::name), to::addAllEventTypes);
940+
ifNonNull(from.getCustomAttributes(), to::putAllCustomAttributes);
941+
ifNonNull(from.getObjectNamePrefix(), to::setObjectNamePrefix);
942+
ifNonNull(from.getPayloadFormat(), PayloadFormat::name, to::setPayloadFormat);
943+
return to.build();
923944
}
924945

925-
private NotificationInfo notificationDecode(com.google.storage.v2.NotificationConfig from) {
926-
return todo();
946+
private NotificationInfo notificationDecode(NotificationConfig from) {
947+
NotificationInfo.Builder to =
948+
NotificationInfo.newBuilder(topicNameCodec.decode(from.getTopic()));
949+
if (!from.getName().isEmpty()) {
950+
NotificationConfigName parse = NotificationConfigName.parse(from.getName());
951+
// the case where parse could return null is already guarded by the preceding isEmpty check
952+
//noinspection DataFlowIssue
953+
to.setNotificationId(parse.getNotificationConfig());
954+
to.setBucket(parse.getBucket());
955+
}
956+
if (!from.getEtag().isEmpty()) {
957+
to.setEtag(from.getEtag());
958+
}
959+
if (!from.getEventTypesList().isEmpty()) {
960+
EventType[] eventTypes =
961+
from.getEventTypesList().stream().map(EventType::valueOf).toArray(EventType[]::new);
962+
to.setEventTypes(eventTypes);
963+
}
964+
if (!from.getCustomAttributesMap().isEmpty()) {
965+
to.setCustomAttributes(from.getCustomAttributesMap());
966+
}
967+
if (!from.getObjectNamePrefix().isEmpty()) {
968+
to.setObjectNamePrefix(from.getObjectNamePrefix());
969+
}
970+
if (!from.getPayloadFormat().isEmpty()) {
971+
to.setPayloadFormat(PayloadFormat.valueOf(from.getPayloadFormat()));
972+
}
973+
return to.build();
927974
}
928975

929976
private com.google.iam.v1.Policy policyEncode(Policy from) {

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import com.google.api.core.ApiFuture;
3131
import com.google.api.core.BetaApi;
3232
import com.google.api.gax.grpc.GrpcCallContext;
33-
import com.google.api.gax.grpc.GrpcStatusCode;
3433
import com.google.api.gax.paging.AbstractPage;
3534
import com.google.api.gax.paging.Page;
3635
import com.google.api.gax.retrying.ResultRetryAlgorithm;
@@ -39,7 +38,6 @@
3938
import com.google.api.gax.rpc.NotFoundException;
4039
import com.google.api.gax.rpc.StatusCode;
4140
import com.google.api.gax.rpc.UnaryCallable;
42-
import com.google.api.gax.rpc.UnimplementedException;
4341
import com.google.cloud.BaseService;
4442
import com.google.cloud.Policy;
4543
import com.google.cloud.WriteChannel;
@@ -84,32 +82,39 @@
8482
import com.google.storage.v2.ComposeObjectRequest.SourceObject;
8583
import com.google.storage.v2.CreateBucketRequest;
8684
import com.google.storage.v2.CreateHmacKeyRequest;
85+
import com.google.storage.v2.CreateNotificationConfigRequest;
8786
import com.google.storage.v2.DeleteBucketRequest;
8887
import com.google.storage.v2.DeleteHmacKeyRequest;
88+
import com.google.storage.v2.DeleteNotificationConfigRequest;
8989
import com.google.storage.v2.DeleteObjectRequest;
9090
import com.google.storage.v2.GetBucketRequest;
9191
import com.google.storage.v2.GetHmacKeyRequest;
92+
import com.google.storage.v2.GetNotificationConfigRequest;
9293
import com.google.storage.v2.GetObjectRequest;
9394
import com.google.storage.v2.GetServiceAccountRequest;
9495
import com.google.storage.v2.ListBucketsRequest;
9596
import com.google.storage.v2.ListHmacKeysRequest;
97+
import com.google.storage.v2.ListNotificationConfigsRequest;
98+
import com.google.storage.v2.ListNotificationConfigsResponse;
9699
import com.google.storage.v2.ListObjectsRequest;
97100
import com.google.storage.v2.ListObjectsResponse;
98101
import com.google.storage.v2.LockBucketRetentionPolicyRequest;
102+
import com.google.storage.v2.NotificationConfig;
103+
import com.google.storage.v2.NotificationConfigName;
99104
import com.google.storage.v2.Object;
100105
import com.google.storage.v2.ObjectAccessControl;
101106
import com.google.storage.v2.ObjectChecksums;
102107
import com.google.storage.v2.ReadObjectRequest;
103108
import com.google.storage.v2.RewriteObjectRequest;
104109
import com.google.storage.v2.RewriteResponse;
105110
import com.google.storage.v2.StorageClient;
111+
import com.google.storage.v2.StorageClient.ListNotificationConfigsPage;
106112
import com.google.storage.v2.UpdateBucketRequest;
107113
import com.google.storage.v2.UpdateHmacKeyRequest;
108114
import com.google.storage.v2.UpdateObjectRequest;
109115
import com.google.storage.v2.WriteObjectRequest;
110116
import com.google.storage.v2.WriteObjectResponse;
111117
import com.google.storage.v2.WriteObjectSpec;
112-
import io.grpc.Status.Code;
113118
import java.io.ByteArrayInputStream;
114119
import java.io.ByteArrayOutputStream;
115120
import java.io.IOException;
@@ -1404,23 +1409,92 @@ public ServiceAccount getServiceAccount(String projectId) {
14041409

14051410
@Override
14061411
public Notification createNotification(String bucket, NotificationInfo notificationInfo) {
1407-
return throwNotYetImplemented(
1408-
fmtMethodName("createNotification", String.class, NotificationInfo.class));
1412+
NotificationConfig encode = codecs.notificationInfo().encode(notificationInfo);
1413+
CreateNotificationConfigRequest req =
1414+
CreateNotificationConfigRequest.newBuilder()
1415+
.setParent(bucketNameCodec.encode(bucket))
1416+
.setNotificationConfig(encode)
1417+
.build();
1418+
return Retrying.run(
1419+
getOptions(),
1420+
retryAlgorithmManager.getFor(req),
1421+
() -> storageClient.createNotificationConfigCallable().call(req),
1422+
syntaxDecoders.notificationConfig);
14091423
}
14101424

14111425
@Override
14121426
public Notification getNotification(String bucket, String notificationId) {
1413-
return throwNotYetImplemented(fmtMethodName("getNotification", String.class, String.class));
1427+
String name;
1428+
if (NotificationConfigName.isParsableFrom(notificationId)) {
1429+
name = notificationId;
1430+
} else {
1431+
NotificationConfigName configName = NotificationConfigName.of("_", bucket, notificationId);
1432+
name = configName.toString();
1433+
}
1434+
GetNotificationConfigRequest req =
1435+
GetNotificationConfigRequest.newBuilder().setName(name).build();
1436+
return Retrying.run(
1437+
getOptions(),
1438+
retryAlgorithmManager.getFor(req),
1439+
() -> {
1440+
try {
1441+
return storageClient.getNotificationConfigCallable().call(req);
1442+
} catch (NotFoundException e) {
1443+
return null;
1444+
}
1445+
},
1446+
syntaxDecoders.notificationConfig);
14141447
}
14151448

14161449
@Override
14171450
public List<Notification> listNotifications(String bucket) {
1418-
return throwNotYetImplemented(fmtMethodName("listNotifications", String.class));
1451+
ListNotificationConfigsRequest req =
1452+
ListNotificationConfigsRequest.newBuilder()
1453+
.setParent(bucketNameCodec.encode(bucket))
1454+
.build();
1455+
ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getFor(req);
1456+
return Retrying.run(
1457+
getOptions(),
1458+
algorithm,
1459+
() -> storageClient.listNotificationConfigsPagedCallable().call(req),
1460+
resp -> {
1461+
TransformingPageDecorator<
1462+
ListNotificationConfigsRequest,
1463+
ListNotificationConfigsResponse,
1464+
NotificationConfig,
1465+
ListNotificationConfigsPage,
1466+
Notification>
1467+
page =
1468+
new TransformingPageDecorator<>(
1469+
resp.getPage(), syntaxDecoders.notificationConfig, getOptions(), algorithm);
1470+
return ImmutableList.copyOf(page.iterateAll());
1471+
});
14191472
}
14201473

14211474
@Override
14221475
public boolean deleteNotification(String bucket, String notificationId) {
1423-
return throwNotYetImplemented(fmtMethodName("deleteNotification", String.class, String.class));
1476+
String name;
1477+
if (NotificationConfigName.isParsableFrom(notificationId)) {
1478+
name = notificationId;
1479+
} else {
1480+
NotificationConfigName configName = NotificationConfigName.of("_", bucket, notificationId);
1481+
name = configName.toString();
1482+
}
1483+
DeleteNotificationConfigRequest req =
1484+
DeleteNotificationConfigRequest.newBuilder().setName(name).build();
1485+
return Boolean.TRUE.equals(
1486+
Retrying.run(
1487+
getOptions(),
1488+
retryAlgorithmManager.getFor(req),
1489+
() -> {
1490+
try {
1491+
storageClient.deleteNotificationConfigCallable().call(req);
1492+
return true;
1493+
} catch (NotFoundException e) {
1494+
return false;
1495+
}
1496+
},
1497+
Decoder.identity()));
14241498
}
14251499

14261500
@Override
@@ -1448,6 +1522,8 @@ private final class SyntaxDecoders {
14481522
o -> codecs.blobInfo().decode(o).asBlob(GrpcStorageImpl.this);
14491523
final Decoder<com.google.storage.v2.Bucket, Bucket> bucket =
14501524
b -> codecs.bucketInfo().decode(b).asBucket(GrpcStorageImpl.this);
1525+
final Decoder<NotificationConfig, Notification> notificationConfig =
1526+
n -> codecs.notificationInfo().decode(n).asNotification(GrpcStorageImpl.this);
14511527
}
14521528

14531529
/**
@@ -1668,14 +1744,6 @@ static <T> T throwHttpJsonOnly(Class<?> clazz, String methodName) {
16681744
throw new UnsupportedOperationException(message);
16691745
}
16701746

1671-
static <T> T throwNotYetImplemented(String methodName) {
1672-
String message =
1673-
String.format(
1674-
"%s#%s is not yet implemented for GRPC transport. Please use StorageOptions.http() to construct a compatible instance in the interim.",
1675-
Storage.class.getName(), methodName);
1676-
throw new UnimplementedException(message, null, GrpcStatusCode.of(Code.UNIMPLEMENTED), false);
1677-
}
1678-
16791747
private static String fmtMethodName(String name, Class<?>... args) {
16801748
return name
16811749
+ "("

google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ public Builder setCustomAttributes(Map<String, String> customAttributes) {
8888
return this;
8989
}
9090

91+
@Override
92+
Builder setBucket(String bucket) {
93+
infoBuilder.setBucket(bucket);
94+
return this;
95+
}
96+
9197
@Override
9298
public Notification build() {
9399
return new Notification(storage, infoBuilder);

google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
2020

21+
import com.google.api.core.InternalApi;
2122
import com.google.api.pathtemplate.PathTemplate;
2223
import com.google.common.base.MoreObjects;
2324
import com.google.common.collect.ImmutableMap;
@@ -34,11 +35,13 @@ public class NotificationInfo implements Serializable {
3435
private static final PathTemplate PATH_TEMPLATE =
3536
PathTemplate.createWithoutUrlEncoding("projects/{project}/topics/{topic}");
3637

38+
// TODO: Change to StringEnum in next major version
3739
public enum PayloadFormat {
3840
JSON_API_V1,
3941
NONE
4042
}
4143

44+
// TODO: Change to StringEnum in next major version
4245
public enum EventType {
4346
OBJECT_FINALIZE,
4447
OBJECT_METADATA_UPDATE,
@@ -54,6 +57,7 @@ public enum EventType {
5457
private final String objectNamePrefix;
5558
private final String etag;
5659
private final String selfLink;
60+
private final String bucket;
5761

5862
/** Builder for {@code NotificationInfo}. */
5963
public abstract static class Builder {
@@ -75,6 +79,8 @@ public abstract static class Builder {
7579

7680
public abstract Builder setCustomAttributes(Map<String, String> customAttributes);
7781

82+
abstract Builder setBucket(String bucket);
83+
7884
/** Creates a {@code NotificationInfo} object. */
7985
public abstract NotificationInfo build();
8086
}
@@ -90,6 +96,7 @@ public static class BuilderImpl extends Builder {
9096
private String objectNamePrefix;
9197
private String etag;
9298
private String selfLink;
99+
private String bucket;
93100

94101
BuilderImpl(String topic) {
95102
this.topic = topic;
@@ -104,6 +111,7 @@ public static class BuilderImpl extends Builder {
104111
customAttributes = notificationInfo.customAttributes;
105112
payloadFormat = notificationInfo.payloadFormat;
106113
objectNamePrefix = notificationInfo.objectNamePrefix;
114+
bucket = notificationInfo.bucket;
107115
}
108116

109117
@Override
@@ -156,6 +164,12 @@ public Builder setCustomAttributes(Map<String, String> customAttributes) {
156164
return this;
157165
}
158166

167+
@Override
168+
Builder setBucket(String bucket) {
169+
this.bucket = bucket;
170+
return this;
171+
}
172+
159173
public NotificationInfo build() {
160174
checkNotNull(topic);
161175
checkTopicFormat(topic);
@@ -172,6 +186,7 @@ public NotificationInfo build() {
172186
customAttributes = builder.customAttributes;
173187
payloadFormat = builder.payloadFormat;
174188
objectNamePrefix = builder.objectNamePrefix;
189+
bucket = builder.bucket;
175190
}
176191

177192
/** Returns the service-generated id for the notification. */
@@ -225,6 +240,15 @@ public Map<String, String> getCustomAttributes() {
225240
return customAttributes;
226241
}
227242

243+
/**
244+
* gRPC has the bucket name encoded in the notification name, use this internal property to track
245+
* it.
246+
*/
247+
@InternalApi
248+
String getBucket() {
249+
return bucket;
250+
}
251+
228252
@Override
229253
public int hashCode() {
230254
return Objects.hash(

0 commit comments

Comments
 (0)