From 8ab1f442ca270f08a7bd2285b3e5aa69e77d1c6e Mon Sep 17 00:00:00 2001 From: vam-google Date: Tue, 21 Sep 2021 02:58:37 -0700 Subject: [PATCH 1/2] feat: Add REST AIP-151 LRO support --- .../gax/grpc/ProtoOperationTransformers.java | 11 +- .../grpc/ProtoOperationTransformersTest.java | 11 +- .../ApiMessageHttpResponseParser.java | 6 ++ .../api/gax/httpjson/HttpJsonCallOptions.java | 6 ++ .../gax/httpjson/HttpJsonCallSettings.java | 19 +++- .../gax/httpjson/HttpJsonCallableFactory.java | 2 +- .../gax/httpjson/HttpJsonDirectCallable.java | 9 ++ .../httpjson/HttpJsonOperationSnapshot.java | 39 ++++--- .../api/gax/httpjson/HttpJsonStatusCode.java | 52 +++++++++ .../api/gax/httpjson/HttpRequestRunnable.java | 7 +- .../api/gax/httpjson/HttpResponseParser.java | 11 ++ .../httpjson/ProtoMessageResponseParser.java | 26 ++++- .../httpjson/ProtoOperationTransformers.java | 91 ++++++++++++++-- .../api/gax/httpjson/ProtoRestSerializer.java | 18 +++- .../stub/HttpJsonOperationsStub.java | 63 ++++++++++- .../longrunning/stub/OperationsStub.java | 5 + .../httpjson/ApiMessageHttpRequestTest.java | 6 ++ .../httpjson/HttpJsonDirectCallableTest.java | 6 ++ .../HttpJsonOperationSnapshotTest.java | 25 +++-- .../gax/httpjson/HttpRequestRunnableTest.java | 12 +++ .../api/gax/httpjson/MockHttpServiceTest.java | 6 ++ .../ProtoMessageResponseParserTest.java | 10 ++ .../ProtoOperationTransformersTest.java | 100 ++++++++++++++++-- .../api/gax/tracing/OpencensusTracer.java | 11 ++ 24 files changed, 486 insertions(+), 66 deletions(-) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/ProtoOperationTransformers.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/ProtoOperationTransformers.java index db92324d6..330ecff5b 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/ProtoOperationTransformers.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/ProtoOperationTransformers.java @@ -65,6 +65,11 @@ public ResponseT apply(OperationSnapshot operationSnapshot) { operationSnapshot.getErrorCode(), false); } + + if (!(operationSnapshot.getResponse() instanceof Any)) { + return (ResponseT) operationSnapshot.getResponse(); + } + try { return transformer.apply((Any) operationSnapshot.getResponse()); } catch (RuntimeException e) { @@ -94,9 +99,11 @@ private MetadataTransformer(Class packedClass) { @Override public MetadataT apply(OperationSnapshot operationSnapshot) { + if (!(operationSnapshot.getMetadata() instanceof Any)) { + return (MetadataT) operationSnapshot.getMetadata(); + } try { - return transformer.apply( - operationSnapshot.getMetadata() != null ? (Any) operationSnapshot.getMetadata() : null); + return transformer.apply((Any) operationSnapshot.getMetadata()); } catch (RuntimeException e) { throw ApiExceptionFactory.createException( "Polling operation with name \"" diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/ProtoOperationTransformersTest.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/ProtoOperationTransformersTest.java index 809e8d7a8..d8eba05a2 100644 --- a/gax-grpc/src/test/java/com/google/api/gax/grpc/ProtoOperationTransformersTest.java +++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/ProtoOperationTransformersTest.java @@ -48,9 +48,8 @@ @RunWith(JUnit4.class) public class ProtoOperationTransformersTest { - @Test - public void testResponseTransformer() { + public void testAnyResponseTransformer() { ResponseTransformer transformer = ResponseTransformer.create(Money.class); Money inputMoney = Money.newBuilder().setCurrencyCode("USD").build(); OperationSnapshot operationSnapshot = @@ -60,7 +59,7 @@ public void testResponseTransformer() { } @Test - public void testResponseTransformer_exception() { + public void testAnyResponseTransformer_exception() { ResponseTransformer transformer = ResponseTransformer.create(Money.class); Money inputMoney = Money.newBuilder().setCurrencyCode("USD").build(); Status status = Status.newBuilder().setCode(Code.UNAVAILABLE.value()).build(); @@ -78,7 +77,7 @@ public void testResponseTransformer_exception() { } @Test - public void testResponseTransformer_mismatchedTypes() { + public void testAnyResponseTransformer_mismatchedTypes() { ResponseTransformer transformer = ResponseTransformer.create(Money.class); Status status = Status.newBuilder().setCode(Code.OK.value()).build(); OperationSnapshot operationSnapshot = @@ -96,7 +95,7 @@ public void testResponseTransformer_mismatchedTypes() { } @Test - public void testMetadataTransformer() { + public void testAnyMetadataTransformer() { MetadataTransformer transformer = MetadataTransformer.create(Money.class); Money inputMoney = Money.newBuilder().setCurrencyCode("USD").build(); OperationSnapshot operationSnapshot = @@ -106,7 +105,7 @@ public void testMetadataTransformer() { } @Test - public void testMetadataTransformer_mismatchedTypes() { + public void testAnyMetadataTransformer_mismatchedTypes() { MetadataTransformer transformer = MetadataTransformer.create(Money.class); Status status = Status.newBuilder().setCode(Code.OK.value()).build(); OperationSnapshot operationSnapshot = diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpResponseParser.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpResponseParser.java index e7c7848c1..8c5a5c806 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpResponseParser.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ApiMessageHttpResponseParser.java @@ -37,6 +37,7 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import com.google.protobuf.TypeRegistry; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; @@ -104,6 +105,11 @@ public ResponseT parse(InputStream httpResponseBody) { } } + @Override + public ResponseT parse(InputStream httpResponseBody, TypeRegistry registry) { + return parse(httpResponseBody); + } + @Override public String serialize(ResponseT response) { return getResponseMarshaller().toJson(response); diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallOptions.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallOptions.java index ca826bf0e..beb5ff98b 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallOptions.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallOptions.java @@ -32,6 +32,7 @@ import com.google.api.core.BetaApi; import com.google.auth.Credentials; import com.google.auto.value.AutoValue; +import com.google.protobuf.TypeRegistry; import javax.annotation.Nullable; import org.threeten.bp.Instant; @@ -45,6 +46,9 @@ public abstract class HttpJsonCallOptions { @Nullable public abstract Credentials getCredentials(); + @Nullable + public abstract TypeRegistry getTypeRegistry(); + public static Builder newBuilder() { return new AutoValue_HttpJsonCallOptions.Builder(); } @@ -55,6 +59,8 @@ public abstract static class Builder { public abstract Builder setCredentials(Credentials value); + public abstract Builder setTypeRegistry(TypeRegistry value); + public abstract HttpJsonCallOptions build(); } } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java index 5f0af5486..ac013ed74 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java @@ -29,18 +29,27 @@ */ package com.google.api.gax.httpjson; +import com.google.protobuf.TypeRegistry; + /** HTTP-specific settings for creating callables. */ public class HttpJsonCallSettings { private final ApiMethodDescriptor methodDescriptor; + private final TypeRegistry typeRegistry; - private HttpJsonCallSettings(ApiMethodDescriptor methodDescriptor) { + private HttpJsonCallSettings( + ApiMethodDescriptor methodDescriptor, TypeRegistry typeRegistry) { this.methodDescriptor = methodDescriptor; + this.typeRegistry = typeRegistry; } public ApiMethodDescriptor getMethodDescriptor() { return methodDescriptor; } + public TypeRegistry getTypeRegistry() { + return typeRegistry; + } + public static Builder newBuilder() { return new Builder<>(); } @@ -58,6 +67,7 @@ public Builder toBuilder() { public static class Builder { private ApiMethodDescriptor methodDescriptor; + private TypeRegistry typeRegistry; private Builder() {} @@ -71,8 +81,13 @@ public Builder setMethodDescriptor( return this; } + public Builder setTypeRegistry(TypeRegistry typeRegistry) { + this.typeRegistry = typeRegistry; + return this; + } + public HttpJsonCallSettings build() { - return new HttpJsonCallSettings<>(methodDescriptor); + return new HttpJsonCallSettings<>(methodDescriptor, typeRegistry); } } } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java index 66ff47cab..c6b4c5763 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java @@ -61,7 +61,7 @@ private HttpJsonCallableFactory() {} private static UnaryCallable createDirectUnaryCallable( HttpJsonCallSettings httpJsonCallSettings) { return new HttpJsonDirectCallable( - httpJsonCallSettings.getMethodDescriptor()); + httpJsonCallSettings.getMethodDescriptor(), httpJsonCallSettings.getTypeRegistry()); } static UnaryCallable createUnaryCallable( diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java index c959644b3..55278c22f 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java @@ -33,6 +33,7 @@ import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.UnaryCallable; import com.google.common.base.Preconditions; +import com.google.protobuf.TypeRegistry; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Instant; @@ -44,9 +45,16 @@ */ class HttpJsonDirectCallable extends UnaryCallable { private final ApiMethodDescriptor descriptor; + private final TypeRegistry typeRegistry; HttpJsonDirectCallable(ApiMethodDescriptor descriptor) { + this(descriptor, null); + } + + HttpJsonDirectCallable( + ApiMethodDescriptor descriptor, TypeRegistry typeRegistry) { this.descriptor = descriptor; + this.typeRegistry = typeRegistry; } @Override @@ -68,6 +76,7 @@ public ApiFuture futureCall(RequestT request, ApiCallContext inputCon HttpJsonCallOptions.newBuilder() .setDeadline(deadline) .setCredentials(context.getCredentials()) + .setTypeRegistry(typeRegistry) .build(); return context.getChannel().issueFutureUnaryCall(callOptions, request, descriptor); } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshot.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshot.java index ff49b45eb..97cb55dfb 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshot.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshot.java @@ -33,6 +33,8 @@ import com.google.api.core.InternalApi; import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.StatusCode.Code; +import com.google.longrunning.Operation; /** * Implementation of OperationSnapshot based on REST transport. @@ -42,7 +44,6 @@ @BetaApi("The surface for long-running operations is not stable yet and may change in the future.") @InternalApi public class HttpJsonOperationSnapshot implements OperationSnapshot { - private final String name; private final Object metadata; private final boolean done; @@ -50,24 +51,19 @@ public class HttpJsonOperationSnapshot implements OperationSnapshot { private final StatusCode errorCode; private final String errorMessage; - public HttpJsonOperationSnapshot( + private HttpJsonOperationSnapshot( String name, Object metadata, boolean done, Object response, - int errorCode, + StatusCode errorCode, String errorMessage) { this.name = name; this.metadata = metadata; this.done = done; - this.response = done ? response : null; - if (done && errorCode != 0) { - this.errorCode = HttpJsonStatusCode.of(errorCode, errorMessage); - this.errorMessage = errorMessage; - } else { - this.errorCode = null; - this.errorMessage = null; - } + this.response = response; + this.errorCode = errorCode; + this.errorMessage = errorMessage; } /** {@inheritDoc} */ @@ -106,6 +102,10 @@ public String getErrorMessage() { return this.errorMessage; } + public static HttpJsonOperationSnapshot create(Operation operation) { + return newBuilder().setOperation(operation).build(); + } + public static Builder newBuilder() { return new HttpJsonOperationSnapshot.Builder(); } @@ -115,7 +115,7 @@ public static class Builder { private Object metadata; private boolean done; private Object response; - private int errorCode; + private StatusCode errorCode; private String errorMessage; public Builder setName(String name) { @@ -139,11 +139,24 @@ public Builder setResponse(Object response) { } public Builder setError(int errorCode, String errorMessage) { - this.errorCode = errorCode; + this.errorCode = + HttpJsonStatusCode.of( + errorCode == 0 ? Code.OK.getHttpStatusCode() : errorCode, errorMessage); this.errorMessage = errorMessage; return this; } + private Builder setOperation(Operation operation) { + this.name = operation.getName(); + this.done = operation.getDone(); + this.response = operation.getResponse(); + this.metadata = operation.getMetadata(); + this.errorCode = + HttpJsonStatusCode.of(com.google.rpc.Code.forNumber(operation.getError().getCode())); + this.errorMessage = operation.getError().getMessage(); + return this; + } + public HttpJsonOperationSnapshot build() { return new HttpJsonOperationSnapshot(name, metadata, done, response, errorCode, errorMessage); } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonStatusCode.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonStatusCode.java index 7e3f66a09..a61ff3f8f 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonStatusCode.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonStatusCode.java @@ -57,6 +57,51 @@ public static HttpJsonStatusCode of(StatusCode.Code statusCode) { return new HttpJsonStatusCode(statusCode.getHttpStatusCode(), statusCode); } + public static HttpJsonStatusCode of(com.google.rpc.Code rpcCode) { + return new HttpJsonStatusCode(rpcCode.getNumber(), rpcCodeToStatusCode(rpcCode)); + } + + static StatusCode.Code rpcCodeToStatusCode(com.google.rpc.Code rpcCode) { + switch (rpcCode) { + case OK: + return Code.OK; + case CANCELLED: + return Code.CANCELLED; + case UNKNOWN: + return Code.UNKNOWN; + case INVALID_ARGUMENT: + return Code.INVALID_ARGUMENT; + case DEADLINE_EXCEEDED: + return Code.DEADLINE_EXCEEDED; + case NOT_FOUND: + return Code.DEADLINE_EXCEEDED; + case ALREADY_EXISTS: + return Code.ALREADY_EXISTS; + case PERMISSION_DENIED: + return Code.PERMISSION_DENIED; + case RESOURCE_EXHAUSTED: + return Code.RESOURCE_EXHAUSTED; + case FAILED_PRECONDITION: + return Code.FAILED_PRECONDITION; + case ABORTED: + return Code.ABORTED; + case OUT_OF_RANGE: + return Code.OUT_OF_RANGE; + case UNIMPLEMENTED: + return Code.UNIMPLEMENTED; + case INTERNAL: + return Code.INTERNAL; + case UNAVAILABLE: + return Code.UNAVAILABLE; + case DATA_LOSS: + return Code.DATA_LOSS; + case UNAUTHENTICATED: + return Code.UNAUTHENTICATED; + default: + throw new IllegalArgumentException("Unrecognized rpc code: " + rpcCode); + } + } + static StatusCode.Code httpStatusToStatusCode(int httpStatus, String errorMessage) { String causeMessage = Strings.nullToEmpty(errorMessage).toUpperCase(); switch (httpStatus) { @@ -143,4 +188,11 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(statusCode); } + + @Override + public String toString() { + return "HttpJsonStatusCode{" + + "statusCode=" + statusCode + + "}"; + } } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestRunnable.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestRunnable.java index d0afc08e8..2482a31aa 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestRunnable.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestRunnable.java @@ -49,6 +49,7 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -195,9 +196,13 @@ public void run() { HttpJsonStatusCode.of(httpResponse.getStatusCode(), httpResponse.getStatusMessage()), false); } + if (getApiMethodDescriptor().getResponseParser() != null) { ResponseT response = - getApiMethodDescriptor().getResponseParser().parse(httpResponse.getContent()); + getApiMethodDescriptor() + .getResponseParser() + .parse(httpResponse.getContent(), getHttpJsonCallOptions().getTypeRegistry()); + getResponseFuture().set(response); } else { getResponseFuture().set(null); diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpResponseParser.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpResponseParser.java index 0bb70568e..78aacf2dd 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpResponseParser.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpResponseParser.java @@ -31,6 +31,7 @@ import com.google.api.core.InternalApi; import com.google.api.core.InternalExtensionOnly; +import com.google.protobuf.TypeRegistry; import java.io.InputStream; /** Interface for classes that parse parts of HTTP responses into the parameterized message type. */ @@ -46,6 +47,16 @@ public interface HttpResponseParser { */ MessageFormatT parse(InputStream httpContent); + /** + * Parse the http body content JSON stream into the MessageFormatT. + * + * @param httpContent the body of an HTTP response + * @param registry type registry with Any fields descriptors + * @throws RestSerializationException if failed to parse the {@code httpContent} to a valid {@code + * MessageFormatT} + */ + MessageFormatT parse(InputStream httpContent, TypeRegistry registry); + /** * Serialize an object into an HTTP body, which is written out to output. * diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageResponseParser.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageResponseParser.java index 084b725e1..fabf77ce7 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageResponseParser.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoMessageResponseParser.java @@ -31,6 +31,7 @@ import com.google.api.core.BetaApi; import com.google.protobuf.Message; +import com.google.protobuf.TypeRegistry; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -40,40 +41,55 @@ public class ProtoMessageResponseParser implements HttpResponseParser { private final ResponseT defaultInstance; + private final TypeRegistry defaultRegistry; - private ProtoMessageResponseParser(ResponseT defaultInstance) { + private ProtoMessageResponseParser(ResponseT defaultInstance, TypeRegistry defaultRegistry) { this.defaultInstance = defaultInstance; + this.defaultRegistry = defaultRegistry; } public static ProtoMessageResponseParser.Builder newBuilder() { - return new ProtoMessageResponseParser.Builder<>(); + return new ProtoMessageResponseParser.Builder() + .setDefaultTypeRegistry(TypeRegistry.getEmptyTypeRegistry()); } /* {@inheritDoc} */ @Override public ResponseT parse(InputStream httpContent) { - return ProtoRestSerializer.create() + return ProtoRestSerializer.create(defaultRegistry) + .fromJson(httpContent, StandardCharsets.UTF_8, defaultInstance.newBuilderForType()); + } + + @Override + public ResponseT parse(InputStream httpContent, TypeRegistry registry) { + return ProtoRestSerializer.create(registry) .fromJson(httpContent, StandardCharsets.UTF_8, defaultInstance.newBuilderForType()); } /* {@inheritDoc} */ @Override public String serialize(ResponseT response) { - return ProtoRestSerializer.create().toJson(response); + return ProtoRestSerializer.create(defaultRegistry).toJson(response); } // Convert to @AutoValue if this class gets more complicated public static class Builder { private ResponseT defaultInstance; + private TypeRegistry defaultRegistry; public Builder setDefaultInstance(ResponseT defaultInstance) { this.defaultInstance = defaultInstance; return this; } + public Builder setDefaultTypeRegistry(TypeRegistry defaultRegistry) { + this.defaultRegistry = defaultRegistry; + return this; + } + public ProtoMessageResponseParser build() { - return new ProtoMessageResponseParser<>(defaultInstance); + return new ProtoMessageResponseParser<>(defaultInstance, defaultRegistry); } } } diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoOperationTransformers.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoOperationTransformers.java index d2b0cfcc4..bb31a88c1 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoOperationTransformers.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoOperationTransformers.java @@ -32,6 +32,10 @@ import com.google.api.core.ApiFunction; import com.google.api.core.BetaApi; import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.rpc.ApiExceptionFactory; +import com.google.api.gax.rpc.StatusCode.Code; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; /** Public for technical reasons; intended for use by generated code. */ @@ -41,33 +45,100 @@ private ProtoOperationTransformers() {} public static class ResponseTransformer implements ApiFunction { + private final AnyTransformer transformer; - private ResponseTransformer() {} + private ResponseTransformer(Class packedClass) { + this.transformer = new AnyTransformer<>(packedClass); + } @Override public ResponseT apply(OperationSnapshot operationSnapshot) { - return (ResponseT) operationSnapshot.getResponse(); + if (!operationSnapshot.getErrorCode().getCode().equals(Code.OK)) { + throw ApiExceptionFactory.createException( + "Operation with name \"" + + operationSnapshot.getName() + + "\" failed with status = " + + operationSnapshot.getErrorCode() + + " and message = " + + operationSnapshot.getErrorMessage(), + null, + operationSnapshot.getErrorCode(), + false); + } + + if (!(operationSnapshot.getResponse() instanceof Any)) { + return (ResponseT) operationSnapshot.getResponse(); + } + + try { + return transformer.apply((Any) operationSnapshot.getResponse()); + } catch (RuntimeException e) { + throw ApiExceptionFactory.createException( + "Operation with name \"" + + operationSnapshot.getName() + + "\" succeeded, but encountered a problem unpacking it.", + e, + operationSnapshot.getErrorCode(), + false); + } } public static ResponseTransformer create( Class packedClass) { - return new ResponseTransformer<>(); + return new ResponseTransformer<>(packedClass); } } - public static class MetadataTransformer - implements ApiFunction { + public static class MetadataTransformer + implements ApiFunction { + private final AnyTransformer transformer; - private MetadataTransformer() {} + private MetadataTransformer(Class packedClass) { + this.transformer = new AnyTransformer<>(packedClass); + } @Override - public ResponseT apply(OperationSnapshot operationSnapshot) { - return (ResponseT) operationSnapshot.getMetadata(); + public MetadataT apply(OperationSnapshot operationSnapshot) { + if (!(operationSnapshot.getMetadata() instanceof Any)) { + return (MetadataT) operationSnapshot.getMetadata(); + } + try { + return transformer.apply((Any) operationSnapshot.getMetadata()); + } catch (RuntimeException e) { + throw ApiExceptionFactory.createException( + "Polling operation with name \"" + + operationSnapshot.getName() + + "\" succeeded, but encountered a problem unpacking it.", + e, + operationSnapshot.getErrorCode(), + false); + } } public static MetadataTransformer create( Class packedClass) { - return new MetadataTransformer<>(); + return new MetadataTransformer<>(packedClass); + } + } + + static class AnyTransformer implements ApiFunction { + private final Class packedClass; + + public AnyTransformer(Class packedClass) { + this.packedClass = packedClass; + } + + @Override + public PackedT apply(Any input) { + try { + return input == null || packedClass == null ? null : input.unpack(packedClass); + } catch (InvalidProtocolBufferException | ClassCastException e) { + throw new IllegalStateException( + "Failed to unpack object from 'any' field. Expected " + + packedClass.getName() + + ", found " + + input.getTypeUrl()); + } } } -} +} \ No newline at end of file diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java index bf278e907..39f352910 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ProtoRestSerializer.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; +import com.google.protobuf.TypeRegistry; import com.google.protobuf.util.JsonFormat; import java.io.IOException; import java.io.InputStream; @@ -50,11 +51,20 @@ */ @BetaApi public class ProtoRestSerializer { - private ProtoRestSerializer() {} + private final TypeRegistry registry; + + private ProtoRestSerializer(TypeRegistry registry) { + this.registry = registry; + } /** Creates a new instance of ProtoRestSerializer. */ public static ProtoRestSerializer create() { - return new ProtoRestSerializer<>(); + return create(TypeRegistry.getEmptyTypeRegistry()); + } + + /** Creates a new instance of ProtoRestSerializer. */ + static ProtoRestSerializer create(TypeRegistry registry) { + return new ProtoRestSerializer<>(registry); } /** @@ -67,7 +77,7 @@ public static ProtoRestSerializer create() */ String toJson(RequestT message) { try { - return JsonFormat.printer().print(message); + return JsonFormat.printer().usingTypeRegistry(registry).print(message); } catch (InvalidProtocolBufferException e) { throw new RestSerializationException("Failed to serialize message to JSON", e); } @@ -85,7 +95,7 @@ String toJson(RequestT message) { @SuppressWarnings("unchecked") RequestT fromJson(InputStream message, Charset messageCharset, Message.Builder builder) { try (Reader json = new InputStreamReader(message, messageCharset)) { - JsonFormat.parser().ignoringUnknownFields().merge(json, builder); + JsonFormat.parser().usingTypeRegistry(registry).ignoringUnknownFields().merge(json, builder); return (RequestT) builder.build(); } catch (IOException e) { throw new RestSerializationException("Failed to parse response message", e); diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java index 9b57dd899..24516b171 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java @@ -39,11 +39,17 @@ import com.google.api.gax.httpjson.ApiMethodDescriptor; import com.google.api.gax.httpjson.FieldsExtractor; import com.google.api.gax.httpjson.HttpJsonCallSettings; +import com.google.api.gax.httpjson.HttpJsonLongRunningClient; +import com.google.api.gax.httpjson.HttpJsonOperationSnapshot; import com.google.api.gax.httpjson.HttpJsonStubCallableFactory; +import com.google.api.gax.httpjson.OperationSnapshotFactory; +import com.google.api.gax.httpjson.PollingRequestFactory; import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; import com.google.api.gax.httpjson.ProtoMessageResponseParser; import com.google.api.gax.httpjson.ProtoRestSerializer; +import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.UnaryCallable; import com.google.longrunning.CancelOperationRequest; import com.google.longrunning.DeleteOperationRequest; @@ -52,6 +58,7 @@ import com.google.longrunning.ListOperationsResponse; import com.google.longrunning.Operation; import com.google.protobuf.Empty; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -157,6 +164,23 @@ public String extract(GetOperationRequest request) { ProtoMessageResponseParser.newBuilder() .setDefaultInstance(Operation.getDefaultInstance()) .build()) + .setOperationSnapshotFactory( + new OperationSnapshotFactory() { + @Override + public OperationSnapshot create( + GetOperationRequest request, Operation response) { + return HttpJsonOperationSnapshot.create(response); + } + }) + .setPollingRequestFactory( + new PollingRequestFactory() { + @Override + public GetOperationRequest create(String compoundOperationId) { + return GetOperationRequest.newBuilder() + .setName(compoundOperationId) + .build(); + } + }) .build(); private static final ApiMethodDescriptor @@ -254,6 +278,7 @@ public String extract(CancelOperationRequest request) { private final UnaryCallable deleteOperationCallable; private final UnaryCallable cancelOperationCallable; + private final LongRunningClient longRunningClient; private final BackgroundResource backgroundResources; private final HttpJsonStubCallableFactory callableFactory; @@ -270,7 +295,19 @@ public static final HttpJsonOperationsStub create(ClientContext clientContext) public static final HttpJsonOperationsStub create( ClientContext clientContext, HttpJsonStubCallableFactory callableFactory) throws IOException { return new HttpJsonOperationsStub( - OperationsStubSettings.newBuilder().build(), clientContext, callableFactory); + OperationsStubSettings.newBuilder().build(), + clientContext, + callableFactory, + TypeRegistry.getEmptyTypeRegistry()); + } + + public static final HttpJsonOperationsStub create( + ClientContext clientContext, + HttpJsonStubCallableFactory callableFactory, + TypeRegistry typeRegistry) + throws IOException { + return new HttpJsonOperationsStub( + OperationsStubSettings.newBuilder().build(), clientContext, callableFactory, typeRegistry); } /** @@ -280,7 +317,11 @@ public static final HttpJsonOperationsStub create( */ protected HttpJsonOperationsStub(OperationsStubSettings settings, ClientContext clientContext) throws IOException { - this(settings, clientContext, new HttpJsonOperationsCallableFactory()); + this( + settings, + clientContext, + new HttpJsonOperationsCallableFactory(), + TypeRegistry.getEmptyTypeRegistry()); } /** @@ -291,7 +332,8 @@ protected HttpJsonOperationsStub(OperationsStubSettings settings, ClientContext protected HttpJsonOperationsStub( OperationsStubSettings settings, ClientContext clientContext, - HttpJsonStubCallableFactory callableFactory) + HttpJsonStubCallableFactory callableFactory, + TypeRegistry typeRegistry) throws IOException { this.callableFactory = callableFactory; @@ -299,18 +341,22 @@ protected HttpJsonOperationsStub( listOperationsTransportSettings = HttpJsonCallSettings.newBuilder() .setMethodDescriptor(listOperationsMethodDescriptor) + .setTypeRegistry(typeRegistry) .build(); HttpJsonCallSettings getOperationTransportSettings = HttpJsonCallSettings.newBuilder() .setMethodDescriptor(getOperationMethodDescriptor) + .setTypeRegistry(typeRegistry) .build(); HttpJsonCallSettings deleteOperationTransportSettings = HttpJsonCallSettings.newBuilder() .setMethodDescriptor(deleteOperationMethodDescriptor) + .setTypeRegistry(typeRegistry) .build(); HttpJsonCallSettings cancelOperationTransportSettings = HttpJsonCallSettings.newBuilder() .setMethodDescriptor(cancelOperationMethodDescriptor) + .setTypeRegistry(typeRegistry) .build(); this.listOperationsCallable = @@ -329,6 +375,12 @@ protected HttpJsonOperationsStub( callableFactory.createUnaryCallable( cancelOperationTransportSettings, settings.cancelOperationSettings(), clientContext); + this.longRunningClient = + new HttpJsonLongRunningClient( + getOperationCallable, + getOperationMethodDescriptor.getOperationSnapshotFactory(), + getOperationMethodDescriptor.getPollingRequestFactory()); + this.backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); } @@ -369,6 +421,11 @@ public UnaryCallable cancelOperationCallable() { return cancelOperationCallable; } + @Override + public LongRunningClient longRunningClient() { + return longRunningClient; + } + @Override public final void close() { shutdown(); diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/OperationsStub.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/OperationsStub.java index 148303177..0c125e050 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/OperationsStub.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/OperationsStub.java @@ -32,6 +32,7 @@ import com.google.api.core.BetaApi; import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.httpjson.longrunning.OperationsClient.ListOperationsPagedResponse; +import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.UnaryCallable; import com.google.longrunning.CancelOperationRequest; import com.google.longrunning.DeleteOperationRequest; @@ -73,6 +74,10 @@ public UnaryCallable cancelOperationCallable() { throw new UnsupportedOperationException("Not implemented: cancelOperationCallable()"); } + public LongRunningClient longRunningClient() { + throw new UnsupportedOperationException("Not implemented: longRunningClient()"); + } + @Override public abstract void close(); } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java index 6778b977d..a5ac52638 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ApiMessageHttpRequestTest.java @@ -40,6 +40,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.truth.Truth; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; @@ -64,6 +65,11 @@ public Instant getDeadline() { public Credentials getCredentials() { return null; } + + @Override + public TypeRegistry getTypeRegistry() { + return null; + } }; @Test diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonDirectCallableTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonDirectCallableTest.java index 250605aa8..4a2d59136 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonDirectCallableTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonDirectCallableTest.java @@ -34,6 +34,7 @@ import com.google.api.core.SettableApiFuture; import com.google.api.pathtemplate.PathTemplate; import com.google.common.collect.ImmutableMap; +import com.google.protobuf.TypeRegistry; import java.io.InputStream; import java.util.List; import java.util.Map; @@ -193,6 +194,11 @@ public String parse(InputStream httpContent) { return "fake"; } + @Override + public String parse(InputStream httpContent, TypeRegistry registry) { + return parse(httpContent); + } + @Override public String serialize(String response) { return response; diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshotTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshotTest.java index ddef28645..20561fe17 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshotTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonOperationSnapshotTest.java @@ -30,8 +30,10 @@ package com.google.api.gax.httpjson; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import com.google.api.gax.rpc.StatusCode.Code; import java.util.ArrayList; import org.junit.Test; @@ -39,7 +41,7 @@ public class HttpJsonOperationSnapshotTest { @Test public void newBuilderTestNoError() { - HttpJsonOperationSnapshot testOperationSnapshot0 = + HttpJsonOperationSnapshot testOperationSnapshot = HttpJsonOperationSnapshot.newBuilder() .setName("snapshot0") .setMetadata("Los Angeles") @@ -48,13 +50,14 @@ public void newBuilderTestNoError() { .setError(0, "no error") .build(); - assertNull(testOperationSnapshot0.getErrorMessage()); - assertNull(testOperationSnapshot0.getErrorCode()); + assertEquals("no error", testOperationSnapshot.getErrorMessage()); + assertEquals(HttpJsonStatusCode.of(Code.OK), testOperationSnapshot.getErrorCode()); + assertTrue(testOperationSnapshot.isDone()); } @Test public void newBuilderTestWithError() { - HttpJsonOperationSnapshot testOperationSnapshot1 = + HttpJsonOperationSnapshot testOperationSnapshot = HttpJsonOperationSnapshot.newBuilder() .setName("snapshot1") .setMetadata("Austin") @@ -63,13 +66,14 @@ public void newBuilderTestWithError() { .setError(403, "Forbidden") .build(); - assertEquals(testOperationSnapshot1.getErrorMessage(), "Forbidden"); - assertEquals(testOperationSnapshot1.getErrorCode(), HttpJsonStatusCode.of(403, "Forbidden")); + assertEquals(testOperationSnapshot.getErrorMessage(), "Forbidden"); + assertEquals(testOperationSnapshot.getErrorCode(), HttpJsonStatusCode.of(403, "Forbidden")); + assertTrue(testOperationSnapshot.isDone()); } @Test public void newBuilderTestNotDone() { - HttpJsonOperationSnapshot testOperationSnapshot2 = + HttpJsonOperationSnapshot testOperationSnapshot = HttpJsonOperationSnapshot.newBuilder() .setName("snapshot2") .setMetadata("Chicago") @@ -78,7 +82,8 @@ public void newBuilderTestNotDone() { .setError(0, "no error") .build(); - assertNull(testOperationSnapshot2.getErrorMessage()); - assertNull(testOperationSnapshot2.getErrorCode()); + assertEquals("no error", testOperationSnapshot.getErrorMessage()); + assertEquals(HttpJsonStatusCode.of(Code.OK), testOperationSnapshot.getErrorCode()); + assertFalse(testOperationSnapshot.isDone()); } } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java index e0a1cb5e0..4771e570b 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java @@ -41,6 +41,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.truth.Truth; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -48,6 +49,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import javax.annotation.Nullable; import org.junit.BeforeClass; import org.junit.Test; import org.threeten.bp.Instant; @@ -76,6 +78,11 @@ public Instant getDeadline() { public Credentials getCredentials() { return null; } + + @Override + public TypeRegistry getTypeRegistry() { + return null; + } }; catMessage = @@ -132,6 +139,11 @@ public EmptyMessage parse(InputStream httpContent) { return null; } + @Override + public EmptyMessage parse(InputStream httpContent, TypeRegistry registry) { + return null; + } + @Override public String serialize(EmptyMessage response) { return null; diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java index a0f03b93a..3571275e9 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/MockHttpServiceTest.java @@ -51,6 +51,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.io.CharStreams; +import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -98,6 +99,11 @@ public PetMessage parse(InputStream httpContent) { return null; } + @Override + public PetMessage parse(InputStream httpContent, TypeRegistry registry) { + return parse(httpContent); + } + @Override public String serialize(PetMessage response) { return ((List) response.getFieldValue("type")).get(0); diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoMessageResponseParserTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoMessageResponseParserTest.java index 3794f66b1..86a01e676 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoMessageResponseParserTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoMessageResponseParserTest.java @@ -33,6 +33,7 @@ import com.google.protobuf.Field; import com.google.protobuf.Field.Cardinality; import com.google.protobuf.Option; +import com.google.protobuf.TypeRegistry; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -81,6 +82,15 @@ public void parse() { Truth.assertThat(actualField).isEqualTo(field); } + @Test + public void parseWithTypeRegistry() { + Field actualField = + parser.parse( + new ByteArrayInputStream(fieldJson.getBytes(StandardCharsets.UTF_8)), + TypeRegistry.newBuilder().build()); + Truth.assertThat(actualField).isEqualTo(field); + } + @Test public void parseInvalidJson() { try { diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoOperationTransformersTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoOperationTransformersTest.java index 61cff19e5..12b326c1c 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoOperationTransformersTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/ProtoOperationTransformersTest.java @@ -32,39 +32,121 @@ import com.google.api.gax.httpjson.ProtoOperationTransformers.MetadataTransformer; import com.google.api.gax.httpjson.ProtoOperationTransformers.ResponseTransformer; import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.UnavailableException; import com.google.common.truth.Truth; -import com.google.protobuf.Option; +import com.google.longrunning.Operation; +import com.google.protobuf.Any; +import com.google.rpc.Code; +import com.google.rpc.Status; +import com.google.type.Color; +import com.google.type.Money; +import org.junit.Assert; import org.junit.Test; public class ProtoOperationTransformersTest { @Test public void testResponseTransformer() { - ResponseTransformer