From ceaaddbd08651df694e7b11834ae71556aa366aa Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 7 Oct 2020 16:18:54 -0700 Subject: [PATCH 1/5] fix: refactor requestBuilder into separate method in ServiceClientClassComposer --- .../composer/ServiceClientClassComposer.java | 138 +++--- .../api/generator/gapic/composer/BUILD.bazel | 4 +- .../ServiceClientClassComposerTest.java | 39 +- .../composer/goldens/IdentityClient.golden | 407 ++++++++++++++++++ .../api/generator/gapic/testdata/BUILD.bazel | 1 + .../generator/gapic/testdata/identity.proto | 192 +++++++++ 6 files changed, 705 insertions(+), 76 deletions(-) create mode 100644 src/test/java/com/google/api/generator/gapic/composer/goldens/IdentityClient.golden create mode 100644 src/test/java/com/google/api/generator/gapic/testdata/identity.proto diff --git a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java index 177fffb162..eb9d4abc99 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java @@ -64,6 +64,7 @@ import com.google.api.generator.gapic.model.MethodArgument; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.util.concurrent.MoreExecutors; @@ -90,6 +91,9 @@ public class ServiceClientClassComposer implements ClassComposer { private static final String PAGED_CALLABLE_NAME_PATTERN = "%sPagedCallable"; private static final String OPERATION_CALLABLE_NAME_PATTERN = "%sOperationCallable"; + private static final Reference LIST_REFERENCE = ConcreteReference.withClazz(List.class); + private static final Reference MAP_REFERENCE = ConcreteReference.withClazz(Map.class); + private enum CallableMethodKind { REGULAR, LRO, @@ -499,8 +503,6 @@ private static List createMethodVariants( } String methodInputTypeName = methodInputType.reference().name(); - Reference listRef = ConcreteReference.withClazz(List.class); - Reference mapRef = ConcreteReference.withClazz(Map.class); // Make the method signature order deterministic, which helps with unit testing and per-version // diffs. @@ -544,71 +546,12 @@ private static List createMethodVariants( .setIsDecl(true) .build(); - MethodInvocationExpr newBuilderExpr = - MethodInvocationExpr.builder() - .setMethodName("newBuilder") - .setStaticReferenceType(methodInputType) - .build(); - // TODO(miraleung): Handle nested arguments and descending setters here. - for (MethodArgument argument : signature) { - String argumentName = JavaStyle.toLowerCamelCase(argument.name()); - TypeNode argumentType = argument.type(); - String setterMethodVariantPattern = "set%s"; - if (TypeNode.isReferenceType(argumentType)) { - if (listRef.isSupertypeOrEquals(argumentType.reference())) { - setterMethodVariantPattern = "addAll%s"; - } else if (mapRef.isSupertypeOrEquals(argumentType.reference())) { - setterMethodVariantPattern = "putAll%s"; - } - } - String setterMethodName = - String.format(setterMethodVariantPattern, JavaStyle.toUpperCamelCase(argumentName)); - - Expr argVarExpr = - VariableExpr.withVariable( - Variable.builder().setName(argumentName).setType(argumentType).build()); - - if (argument.isResourceNameHelper()) { - MethodInvocationExpr isNullCheckExpr = - MethodInvocationExpr.builder() - .setStaticReferenceType(types.get("Objects")) - .setMethodName("isNull") - .setArguments(Arrays.asList(argVarExpr)) - .setReturnType(TypeNode.BOOLEAN) - .build(); - Expr nullExpr = ValueExpr.withValue(NullObjectValue.create()); - MethodInvocationExpr toStringExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(argVarExpr) - .setMethodName("toString") - .setReturnType(TypeNode.STRING) - .build(); - argVarExpr = - TernaryExpr.builder() - .setConditionExpr(isNullCheckExpr) - .setThenExpr(nullExpr) - .setElseExpr(toStringExpr) - .build(); - } + Expr requestBuilderExpr = createRequestBuilderExpr(method, signature, types); - newBuilderExpr = - MethodInvocationExpr.builder() - .setMethodName(setterMethodName) - .setArguments(Arrays.asList(argVarExpr)) - .setExprReferenceExpr(newBuilderExpr) - .build(); - } - - MethodInvocationExpr builderExpr = - MethodInvocationExpr.builder() - .setMethodName("build") - .setExprReferenceExpr(newBuilderExpr) - .setReturnType(methodInputType) - .build(); AssignmentExpr requestAssignmentExpr = AssignmentExpr.builder() .setVariableExpr(requestVarExpr) - .setValueExpr(builderExpr) + .setValueExpr(requestBuilderExpr) .build(); List statements = Arrays.asList(ExprStatement.withExpr(requestAssignmentExpr)); @@ -1373,6 +1316,75 @@ private static ClassDefinition createNestedRpcFixedSizeCollectionClass( .build(); } + @VisibleForTesting + static Expr createRequestBuilderExpr( + Method method, List signature, Map types) { + TypeNode methodInputType = method.inputType(); + MethodInvocationExpr newBuilderExpr = + MethodInvocationExpr.builder() + .setMethodName("newBuilder") + .setStaticReferenceType(methodInputType) + .build(); + // TODO(miraleung): Handle nested arguments and descending setters here. + for (MethodArgument argument : signature) { + String argumentName = JavaStyle.toLowerCamelCase(argument.name()); + TypeNode argumentType = argument.type(); + String setterMethodVariantPattern = "set%s"; + if (TypeNode.isReferenceType(argumentType)) { + if (LIST_REFERENCE.isSupertypeOrEquals(argumentType.reference())) { + setterMethodVariantPattern = "addAll%s"; + } else if (MAP_REFERENCE.isSupertypeOrEquals(argumentType.reference())) { + setterMethodVariantPattern = "putAll%s"; + } + } + String setterMethodName = + String.format(setterMethodVariantPattern, JavaStyle.toUpperCamelCase(argumentName)); + + Expr argVarExpr = + VariableExpr.withVariable( + Variable.builder().setName(argumentName).setType(argumentType).build()); + + if (argument.isResourceNameHelper()) { + MethodInvocationExpr isNullCheckExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(types.get("Objects")) + .setMethodName("isNull") + .setArguments(Arrays.asList(argVarExpr)) + .setReturnType(TypeNode.BOOLEAN) + .build(); + Expr nullExpr = ValueExpr.withValue(NullObjectValue.create()); + MethodInvocationExpr toStringExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(argVarExpr) + .setMethodName("toString") + .setReturnType(TypeNode.STRING) + .build(); + argVarExpr = + TernaryExpr.builder() + .setConditionExpr(isNullCheckExpr) + .setThenExpr(nullExpr) + .setElseExpr(toStringExpr) + .build(); + } + + newBuilderExpr = + MethodInvocationExpr.builder() + .setMethodName(setterMethodName) + .setArguments(Arrays.asList(argVarExpr)) + .setExprReferenceExpr(newBuilderExpr) + .build(); + } + + MethodInvocationExpr builderExpr = + MethodInvocationExpr.builder() + .setMethodName("build") + .setExprReferenceExpr(newBuilderExpr) + .setReturnType(methodInputType) + .build(); + + return builderExpr; + } + private static Map createTypes( Service service, Map messageTypes) { Map types = new HashMap<>(); diff --git a/src/test/java/com/google/api/generator/gapic/composer/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/composer/BUILD.bazel index d04f29e1c9..3a689014d3 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/composer/BUILD.bazel @@ -11,6 +11,8 @@ UPDATE_GOLDENS_TESTS = [ "MockServiceClassComposerTest", "MockServiceImplClassComposerTest", "ResourceNameHelperClassComposerTest", + "ServiceClientClassComposerTest", + "ServiceClientTestClassComposerTest", "ServiceSettingsClassComposerTest", "ServiceStubSettingsClassComposerTest", "ServiceStubClassComposerTest", @@ -20,8 +22,6 @@ TESTS = UPDATE_GOLDENS_TESTS + [ "DefaultValueComposerTest", "ResourceNameTokenizerTest", "RetrySettingsComposerTest", - "ServiceClientClassComposerTest", - "ServiceClientTestClassComposerTest", ] TEST_DEPS = [ diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java index f0c0d70102..7e2f8d24d0 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java @@ -27,28 +27,22 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import com.google.showcase.v1beta1.IdentityOuterClass; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.junit.Before; import org.junit.Test; public class ServiceClientClassComposerTest { - private ServiceDescriptor echoService; - private FileDescriptor echoFileDescriptor; - - @Before - public void setUp() { - echoFileDescriptor = EchoOuterClass.getDescriptor(); - echoService = echoFileDescriptor.getServices().get(0); - assertEquals(echoService.getName(), "Echo"); - } - @Test public void generateServiceClasses() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + ServiceDescriptor echoService = echoFileDescriptor.getServices().get(0); + assertEquals(echoService.getName(), "Echo"); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); Set outputResourceNames = new HashSet<>(); @@ -65,4 +59,27 @@ public void generateServiceClasses() { Path goldenFilePath = Paths.get(ComposerConstants.GOLDENFILES_DIRECTORY, "EchoClient.golden"); assertCodeEquals(goldenFilePath, visitor.write()); } + + @Test + public void generateServiceClasses_methodSignatureHasNestedFields() { + FileDescriptor fileDescriptor = IdentityOuterClass.getDescriptor(); + ServiceDescriptor identityService = fileDescriptor.getServices().get(0); + assertEquals(identityService.getName(), "Identity"); + + Map messageTypes = Parser.parseMessages(fileDescriptor); + Map resourceNames = Parser.parseResourceNames(fileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService(fileDescriptor, messageTypes, resourceNames, outputResourceNames); + + Service protoService = services.get(0); + GapicClass clazz = ServiceClientClassComposer.instance().generate(protoService, messageTypes); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), "IdentityClient.golden", visitor.write()); + Path goldenFilePath = + Paths.get(ComposerConstants.GOLDENFILES_DIRECTORY, "IdentityClient.golden"); + assertCodeEquals(goldenFilePath, visitor.write()); + } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/goldens/IdentityClient.golden b/src/test/java/com/google/api/generator/gapic/composer/goldens/IdentityClient.golden new file mode 100644 index 0000000000..0f6f1a418d --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/composer/goldens/IdentityClient.golden @@ -0,0 +1,407 @@ +package com.google.showcase.v1beta1; + +import com.google.api.core.ApiFunction; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.core.BetaApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.paging.AbstractFixedSizeCollection; +import com.google.api.gax.paging.AbstractPage; +import com.google.api.gax.paging.AbstractPagedListResponse; +import com.google.api.gax.rpc.PageContext; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.Empty; +import com.google.showcase.v1beta1.stub.IdentityStub; +import com.google.showcase.v1beta1.stub.IdentityStubSettings; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * This class provides the ability to make remote calls to the backing service through method calls + * that map to API methods. Sample code to get started: + * + *

Note: close() needs to be called on the echoClient object to clean up resources such as + * threads. In the example above, try-with-resources is used, which automatically calls close(). + * + *

The surface of this class includes several types of Java methods for each of the API's + * methods: + * + *

    + *
  1. A "flattened" method. With this type of method, the fields of the request type have been + * converted into function parameters. It may be the case that not all fields are available as + * parameters, and not every API method will have a flattened method entry point. + *
  2. A "request object" method. This type of method only takes one parameter, a request object, + * which must be constructed before the call. Not every API method will have a request object + * method. + *
  3. A "callable" method. This type of method takes no parameters and returns an immutable API + * callable object, which can be used to initiate calls to the service. + *
+ * + *

See the individual methods for example code. + * + *

Many parameters require resource names to be formatted in a particular way. To assist with + * these names, this class includes a format method for each type of name, and additionally a parse + * method to extract the individual identifiers contained within names that are returned. + * + *

This class can be customized by passing in a custom instance of IdentitySettings to create(). + * For example: + * + *

To customize credentials: + * + *

To customize the endpoint: + */ +@BetaApi +@Generated("by gapic-generator") +public class IdentityClient implements BackgroundResource { + private final IdentitySettings settings; + private final IdentityStub stub; + + /** Constructs an instance of EchoClient with default settings. */ + public static final IdentityClient create() throws IOException { + return create(IdentitySettings.newBuilder().build()); + } + + /** + * Constructs an instance of EchoClient, using the given settings. The channels are created based + * on the settings passed in, or defaults for any settings that are not set. + */ + public static final IdentityClient create(IdentitySettings settings) throws IOException { + return new IdentityClient(settings); + } + + /** + * Constructs an instance of EchoClient, using the given stub for making calls. This is for + * advanced usage - prefer using create(IdentitySettings). + */ + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") + public static final IdentityClient create(IdentityStub stub) { + return new IdentityClient(stub); + } + + /** + * Constructs an instance of EchoClient, using the given settings. This is protected so that it is + * easy to make a subclass, but otherwise, the static factory methods should be preferred. + */ + protected IdentityClient(IdentitySettings settings) throws IOException { + this.settings = settings; + this.stub = ((IdentityStubSettings) settings.getStubSettings()).createStub(); + } + + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") + protected IdentityClient(IdentityStub stub) { + this.settings = null; + this.stub = stub; + } + + public final IdentitySettings getSettings() { + return settings; + } + + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") + public IdentityStub getStub() { + return stub; + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param parent + * @param display_name + * @param email + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User createUser(String parent, String displayName, String email) { + CreateUserRequest request = + CreateUserRequest.newBuilder() + .setParent(parent) + .setDisplayName(displayName) + .setEmail(email) + .build(); + return createUser(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param parent + * @param display_name + * @param email + * @param age + * @param nickname + * @param enable_notifications + * @param height_feet + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User createUser( + String parent, + String displayName, + String email, + int age, + String nickname, + boolean enableNotifications, + double heightFeet) { + CreateUserRequest request = + CreateUserRequest.newBuilder() + .setParent(parent) + .setDisplayName(displayName) + .setEmail(email) + .setAge(age) + .setNickname(nickname) + .setEnableNotifications(enableNotifications) + .setHeightFeet(heightFeet) + .build(); + return createUser(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User createUser(CreateUserRequest request) { + return createUserCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** Sample code: */ + public final UnaryCallable createUserCallable() { + return stub.createUserCallable(); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param name + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User getUser(UserName name) { + GetUserRequest request = + GetUserRequest.newBuilder().setName(Objects.isNull(name) ? null : name.toString()).build(); + return getUser(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param name + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User getUser(String name) { + GetUserRequest request = GetUserRequest.newBuilder().setName(name).build(); + return getUser(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User getUser(GetUserRequest request) { + return getUserCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** Sample code: */ + public final UnaryCallable getUserCallable() { + return stub.getUserCallable(); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final User updateUser(UpdateUserRequest request) { + return updateUserCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** Sample code: */ + public final UnaryCallable updateUserCallable() { + return stub.updateUserCallable(); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param name + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Empty deleteUser(UserName name) { + DeleteUserRequest request = + DeleteUserRequest.newBuilder() + .setName(Objects.isNull(name) ? null : name.toString()) + .build(); + return deleteUser(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param name + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Empty deleteUser(String name) { + DeleteUserRequest request = DeleteUserRequest.newBuilder().setName(name).build(); + return deleteUser(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Empty deleteUser(DeleteUserRequest request) { + return deleteUserCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** Sample code: */ + public final UnaryCallable deleteUserCallable() { + return stub.deleteUserCallable(); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** + * Sample code: + * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final ListUsersPagedResponse listUsers(ListUsersRequest request) { + return listUsersPagedCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** Sample code: */ + public final UnaryCallable listUsersPagedCallable() { + return stub.listUsersPagedCallable(); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD. + /** Sample code: */ + public final UnaryCallable listUsersCallable() { + return stub.listUsersCallable(); + } + + @Override + public final void close() { + stub.close(); + } + + @Override + public void shutdown() { + stub.shutdown(); + } + + @Override + public boolean isShutdown() { + return stub.isShutdown(); + } + + @Override + public boolean isTerminated() { + return stub.isTerminated(); + } + + @Override + public void shutdownNow() { + stub.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return stub.awaitTermination(duration, unit); + } + + public static class ListUsersPagedResponse + extends AbstractPagedListResponse< + ListUsersRequest, ListUsersResponse, User, ListUsersPage, ListUsersFixedSizeCollection> { + + public static ApiFuture createAsync( + PageContext context, + ApiFuture futureResponse) { + ApiFuture futurePage = + ListUsersPage.createEmptyPage().createPageAsync(context, futureResponse); + return ApiFutures.transform( + futurePage, + new ApiFunction() { + @Override + public ListUsersPagedResponse apply(ListUsersPage input) { + return new ListUsersPagedResponse(input); + } + }, + MoreExecutors.directExecutor()); + } + + private ListUsersPagedResponse(ListUsersPage page) { + super(page, ListUsersFixedSizeCollection.createEmptyCollection()); + } + } + + public static class ListUsersPage + extends AbstractPage { + + private ListUsersPage( + PageContext context, + ListUsersResponse response) { + super(context, response); + } + + private static ListUsersPage createEmptyPage() { + return new ListUsersPage(null, null); + } + + @Override + protected ListUsersPage createPage( + PageContext context, + ListUsersResponse response) { + return new ListUsersPage(context, response); + } + + @Override + public ApiFuture createPageAsync( + PageContext context, + ApiFuture futureResponse) { + return super.createPageAsync(context, futureResponse); + } + } + + public static class ListUsersFixedSizeCollection + extends AbstractFixedSizeCollection< + ListUsersRequest, ListUsersResponse, User, ListUsersPage, ListUsersFixedSizeCollection> { + + private ListUsersFixedSizeCollection(List pages, int collectionSize) { + super(pages, collectionSize); + } + + private static ListUsersFixedSizeCollection createEmptyCollection() { + return new ListUsersFixedSizeCollection(null, 0); + } + + @Override + protected ListUsersFixedSizeCollection createCollection( + List pages, int collectionSize) { + return new ListUsersFixedSizeCollection(pages, collectionSize); + } + } +} diff --git a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel index a846eaf638..9682b5c414 100644 --- a/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/testdata/BUILD.bazel @@ -32,6 +32,7 @@ proto_library( name = "showcase_proto", srcs = [ "echo.proto", + "identity.proto", "testing.proto", ], deps = [ diff --git a/src/test/java/com/google/api/generator/gapic/testdata/identity.proto b/src/test/java/com/google/api/generator/gapic/testdata/identity.proto new file mode 100644 index 0000000000..5ed47e8c59 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/testdata/identity.proto @@ -0,0 +1,192 @@ +// Copyright 2018 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 +// +// https://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. + +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/timestamp.proto"; + +package google.showcase.v1beta1; + +option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; +option java_package = "com.google.showcase.v1beta1"; +option java_multiple_files = true; + +// A simple identity service. +service Identity { + // This service is meant to only run locally on the port 7469 (keypad digits + // for "show"). + option (google.api.default_host) = "localhost:7469"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform"; + + // Creates a user. + rpc CreateUser(CreateUserRequest) returns (User) { + option (google.api.http) = { + post: "/v1beta1/{parent=users}" + body: "*" + }; + option (google.api.method_signature) = + "parent,user.display_name,user.email"; + option (google.api.method_signature) = + "parent,user.display_name,user.email,user.age,user.nickname,user.enable_notifications,user.height_feet"; + } + + // Retrieves the User with the given uri. + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/v1beta1/{name=users/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Updates a user. + rpc UpdateUser(UpdateUserRequest) returns (User) { + option (google.api.http) = { + patch: "/v1beta1/{user.name=users/*}" + body: "*" + }; + } + + // Deletes a user, their profile, and all of their authored messages. + rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1beta1/{name=users/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Lists all users. + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) { + option (google.api.http) = { + get: "/v1beta1/users" + }; + } +} + +// A user. +message User { + option (google.api.resource) = { + type: "showcase.googleapis.com/User" + pattern: "users/{user}" + }; + + // The resource name of the user. + string name = 1; + + // The display_name of the user. + string display_name = 2 [(google.api.field_behavior) = REQUIRED]; + + // The email address of the user. + string email = 3 [(google.api.field_behavior) = REQUIRED]; + + // The timestamp at which the user was created. + google.protobuf.Timestamp create_time = 4 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // The latest timestamp at which the user was updated. + google.protobuf.Timestamp update_time = 5 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // The age of the use in years. + optional int32 age = 6; + + // The height of the user in feet. + optional double height_feet = 7; + + // The nickname of the user. + // + // (-- aip.dev/not-precedent: An empty string is a valid nickname. + // Ordinarily, proto3_optional should not be used on a `string` field. --) + optional string nickname = 8; + + // Enables the receiving of notifications. The default is true if unset. + // + // (-- aip.dev/not-precedent: The default for the feature is true. + // Ordinarily, the default for a `bool` field should be false. --) + optional bool enable_notifications = 9; +} + +// The request message for the google.showcase.v1beta1.Identity\CreateUser +// method. +message CreateUserRequest { + string parent = 1 [ + (google.api.resource_reference).child_type = "showcase.googleapis.com/User", + (google.api.field_behavior) = REQUIRED + ]; + // The user to create. + User user = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// The request message for the google.showcase.v1beta1.Identity\GetUser +// method. +message GetUserRequest { + // The resource name of the requested user. + string name = 1 [ + (google.api.resource_reference).type = "showcase.googleapis.com/User", + (google.api.field_behavior) = REQUIRED + ]; +} + +// The request message for the google.showcase.v1beta1.Identity\UpdateUser +// method. +message UpdateUserRequest { + // The user to update. + User user = 1; + + // The field mask to determine wich fields are to be updated. If empty, the + // server will assume all fields are to be updated. + google.protobuf.FieldMask update_mask = 2; +} + +// The request message for the google.showcase.v1beta1.Identity\DeleteUser +// method. +message DeleteUserRequest { + // The resource name of the user to delete. + string name = 1 [ + (google.api.resource_reference).type = "showcase.googleapis.com/User", + (google.api.field_behavior) = REQUIRED + ]; +} + +// The request message for the google.showcase.v1beta1.Identity\ListUsers +// method. +message ListUsersRequest { + // The maximum number of users to return. Server may return fewer users + // than requested. If unspecified, server will pick an appropriate default. + int32 page_size = 1; + + // The value of google.showcase.v1beta1.ListUsersResponse.next_page_token + // returned from the previous call to + // `google.showcase.v1beta1.Identity\ListUsers` method. + string page_token = 2; +} + +// The response message for the google.showcase.v1beta1.Identity\ListUsers +// method. +message ListUsersResponse { + // The list of users. + repeated User users = 1; + + // A token to retrieve next page of results. + // Pass this value in ListUsersRequest.page_token field in the subsequent + // call to `google.showcase.v1beta1.Message\ListUsers` method to retrieve the + // next page of results. + string next_page_token = 2; +} From bdcb62ecd23604ae07d47501503866e8c77e0d0c Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 21 Oct 2020 13:19:44 -0700 Subject: [PATCH 2/5] feat: add varargs to AnonClass and ref setter methods --- .../api/generator/engine/ast/AnonymousClassExpr.java | 5 +++++ .../google/api/generator/engine/ast/ConcreteReference.java | 7 ++++++- .../google/api/generator/engine/ast/VaporReference.java | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/google/api/generator/engine/ast/AnonymousClassExpr.java b/src/main/java/com/google/api/generator/engine/ast/AnonymousClassExpr.java index 7cbce07ff0..e4ddc7e11d 100644 --- a/src/main/java/com/google/api/generator/engine/ast/AnonymousClassExpr.java +++ b/src/main/java/com/google/api/generator/engine/ast/AnonymousClassExpr.java @@ -17,6 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -45,6 +46,10 @@ public static Builder builder() { public abstract static class Builder { public abstract Builder setType(TypeNode type); + public Builder setMethods(MethodDefinition... methods) { + return setMethods(Arrays.asList(methods)); + } + public abstract Builder setMethods(List methods); public abstract Builder setStatements(List statements); diff --git a/src/main/java/com/google/api/generator/engine/ast/ConcreteReference.java b/src/main/java/com/google/api/generator/engine/ast/ConcreteReference.java index 7d37a02417..58494fe7a5 100644 --- a/src/main/java/com/google/api/generator/engine/ast/ConcreteReference.java +++ b/src/main/java/com/google/api/generator/engine/ast/ConcreteReference.java @@ -16,6 +16,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; @@ -210,7 +211,11 @@ public abstract static class Builder { public abstract Builder setWildcardUpperBound(Reference reference); - public abstract Builder setGenerics(List clazzes); + public Builder setGenerics(Reference... references) { + return setGenerics(Arrays.asList(references)); + } + + public abstract Builder setGenerics(List references); public abstract Builder setIsStaticImport(boolean isStaticImport); diff --git a/src/main/java/com/google/api/generator/engine/ast/VaporReference.java b/src/main/java/com/google/api/generator/engine/ast/VaporReference.java index c3b237bdf3..cd9361ebe3 100644 --- a/src/main/java/com/google/api/generator/engine/ast/VaporReference.java +++ b/src/main/java/com/google/api/generator/engine/ast/VaporReference.java @@ -17,6 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; @@ -146,6 +147,10 @@ public abstract static class Builder { public abstract Builder setUseFullName(boolean useFullName); + public Builder setGenerics(Reference... references) { + return setGenerics(Arrays.asList(references)); + } + public abstract Builder setGenerics(List clazzes); public abstract Builder setEnclosingClassName(String enclosingClassName); From e15e1157ac8e8d4f2273c83a919a5c064ff62084 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 21 Oct 2020 13:22:32 -0700 Subject: [PATCH 3/5] feat: add HTTP annotation parsing/validation --- .../api/generator/engine/ast/TypeNode.java | 4 + .../api/generator/gapic/model/Method.java | 11 ++ .../gapic/protoparser/HttpRuleParser.java | 129 ++++++++++++++++++ .../generator/gapic/protoparser/Parser.java | 8 ++ .../generator/gapic/protoparser/BUILD.bazel | 1 + .../gapic/protoparser/HttpRuleParserTest.java | 73 ++++++++++ 6 files changed, 226 insertions(+) create mode 100644 src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java create mode 100644 src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java diff --git a/src/main/java/com/google/api/generator/engine/ast/TypeNode.java b/src/main/java/com/google/api/generator/engine/ast/TypeNode.java index 9505cbb8cc..49fa35aaac 100644 --- a/src/main/java/com/google/api/generator/engine/ast/TypeNode.java +++ b/src/main/java/com/google/api/generator/engine/ast/TypeNode.java @@ -186,6 +186,10 @@ public boolean isPrimitiveType() { return isPrimitiveType(typeKind()); } + public boolean isProtoPrimitiveType() { + return isPrimitiveType() || this.equals(TypeNode.STRING); + } + public boolean isSupertypeOrEquals(TypeNode other) { boolean oneTypeIsNull = this.equals(TypeNode.NULL) ^ other.equals(TypeNode.NULL); return !isPrimitiveType() diff --git a/src/main/java/com/google/api/generator/gapic/model/Method.java b/src/main/java/com/google/api/generator/gapic/model/Method.java index 30e94c9ad5..2d78b5f941 100644 --- a/src/main/java/com/google/api/generator/gapic/model/Method.java +++ b/src/main/java/com/google/api/generator/gapic/model/Method.java @@ -45,6 +45,10 @@ public enum Stream { @Nullable public abstract String description(); + // TODO(miraleung): May need to change this to MethodArgument, Field, or some new struct + // HttpBinding pending dotted reference support. + public abstract List httpBindings(); + // Example from Expand in echo.proto: Thet TypeNodes that map to // [["content", "error"], ["content", "error", "info"]]. public abstract ImmutableList> methodSignatures(); @@ -57,10 +61,15 @@ public boolean hasDescription() { return description() != null; } + public boolean hasHttpBindings() { + return !httpBindings().isEmpty(); + } + public static Builder builder() { return new AutoValue_Method.Builder() .setStream(Stream.NONE) .setMethodSignatures(ImmutableList.of()) + .setHttpBindings(ImmutableList.of()) .setIsPaged(false); } @@ -91,6 +100,8 @@ public abstract static class Builder { public abstract Builder setDescription(String description); + public abstract Builder setHttpBindings(List httpBindings); + public abstract Builder setMethodSignatures(List> methodSignature); public abstract Builder setIsPaged(boolean isPaged); diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java b/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java new file mode 100644 index 0000000000..e48344afde --- /dev/null +++ b/src/main/java/com/google/api/generator/gapic/protoparser/HttpRuleParser.java @@ -0,0 +1,129 @@ +// 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.api.generator.gapic.protoparser; + +import com.google.api.AnnotationsProto; +import com.google.api.HttpRule; +import com.google.api.HttpRule.PatternCase; +import com.google.api.generator.gapic.model.Field; +import com.google.api.generator.gapic.model.Message; +import com.google.api.pathtemplate.PathTemplate; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.protobuf.DescriptorProtos.MethodOptions; +import com.google.protobuf.Descriptors.MethodDescriptor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class HttpRuleParser { + private static final String ASTERISK = "*"; + + public static Optional> parseHttpBindings( + MethodDescriptor protoMethod, Message inputMessage, Map messageTypes) { + MethodOptions methodOptions = protoMethod.getOptions(); + if (!methodOptions.hasExtension(AnnotationsProto.http)) { + return Optional.empty(); + } + + HttpRule httpRule = methodOptions.getExtension(AnnotationsProto.http); + + // Body validation. + if (!Strings.isNullOrEmpty(httpRule.getBody()) && !httpRule.getBody().equals(ASTERISK)) { + checkHttpFieldIsValid(httpRule.getBody(), inputMessage, true); + } + + // Get pattern. + List bindings = getPatternBindings(httpRule); + if (bindings.isEmpty()) { + return Optional.empty(); + } + + // Binding validation. + for (String binding : bindings) { + // Handle foo.bar cases by descending into the subfields. + String[] descendantBindings = binding.split("\\."); + Message containingMessage = inputMessage; + for (int i = 0; i < descendantBindings.length; i++) { + String subField = descendantBindings[i]; + if (i < descendantBindings.length - 1) { + Field field = containingMessage.fieldMap().get(subField); + containingMessage = messageTypes.get(field.type().reference().name()); + } else { + checkHttpFieldIsValid(subField, containingMessage, false); + } + } + } + + return Optional.of(bindings); + } + + private static List getPatternBindings(HttpRule httpRule) { + String pattern = null; + // Assign a temp variable to prevent the formatter from removing the import. + PatternCase patternCase = httpRule.getPatternCase(); + switch (patternCase) { + case GET: + pattern = httpRule.getGet(); + break; + case PUT: + pattern = httpRule.getPut(); + break; + case POST: + pattern = httpRule.getPost(); + break; + case DELETE: + pattern = httpRule.getDelete(); + break; + case PATCH: + pattern = httpRule.getPatch(); + break; + case CUSTOM: // Invalid pattern. + // Fall through. + default: + return Collections.emptyList(); + } + + PathTemplate template = PathTemplate.create(pattern); + List bindings = new ArrayList(template.vars()); + Collections.sort(bindings); + return bindings; + } + + private static void checkHttpFieldIsValid(String binding, Message inputMessage, boolean isBody) { + Preconditions.checkState( + !Strings.isNullOrEmpty(binding), + String.format("DEL: Null or empty binding for " + inputMessage.name())); + Preconditions.checkState( + inputMessage.fieldMap().containsKey(binding), + String.format( + "Expected message %s to contain field %s but none found", + inputMessage.name(), binding)); + Field field = inputMessage.fieldMap().get(binding); + boolean fieldCondition = !field.isRepeated(); + if (!isBody) { + fieldCondition &= field.type().isProtoPrimitiveType(); + } + String messageFormat = + "Expected a non-repeated " + + (isBody ? "" : "primitive ") + + "type for field %s in message %s but got type %s"; + Preconditions.checkState( + fieldCondition, + String.format(messageFormat, field.name(), inputMessage.name(), field.type())); + } +} diff --git a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java index 43e7e7c435..98e7fe5f5d 100644 --- a/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java +++ b/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java @@ -52,6 +52,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -276,6 +277,12 @@ static List parseMethods( } } + Optional> httpBindingsOpt = + HttpRuleParser.parseHttpBindings( + protoMethod, messageTypes.get(inputType.reference().name()), messageTypes); + List httpBindings = + httpBindingsOpt.isPresent() ? httpBindingsOpt.get() : Collections.emptyList(); + methods.add( methodBuilder .setName(protoMethod.getName()) @@ -292,6 +299,7 @@ static List parseMethods( messageTypes, resourceNames, outputArgResourceNames)) + .setHttpBindings(httpBindings) .setIsPaged(parseIsPaged(protoMethod, messageTypes)) .build()); diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel index cc3063d4db..a7df4ab20b 100644 --- a/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel +++ b/src/test/java/com/google/api/generator/gapic/protoparser/BUILD.bazel @@ -4,6 +4,7 @@ package(default_visibility = ["//visibility:public"]) TESTS = [ "BatchingSettingsConfigParserTest", + "HttpRuleParserTest", "MethodSignatureParserTest", "ParserTest", "PluginArgumentParserTest", diff --git a/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java b/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java new file mode 100644 index 0000000000..4f20e23a33 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/protoparser/HttpRuleParserTest.java @@ -0,0 +1,73 @@ +// 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.api.generator.gapic.protoparser; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertThrows; + +import com.google.api.generator.gapic.model.Message; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.MethodDescriptor; +import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.showcase.v1beta1.TestingOuterClass; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.Test; + +public class HttpRuleParserTest { + @Test + public void parseHttpAnnotation_basic() { + FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); + ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); + assertEquals(testingService.getName(), "Testing"); + + Map messages = Parser.parseMessages(testingFileDescriptor); + + // CreateSession method. + MethodDescriptor rpcMethod = testingService.getMethods().get(0); + Message inputMessage = messages.get("CreateSessionRequest"); + Optional> httpBindingsOpt = + HttpRuleParser.parseHttpBindings(rpcMethod, inputMessage, messages); + assertFalse(httpBindingsOpt.isPresent()); + + // VerityTest method. + rpcMethod = testingService.getMethods().get(testingService.getMethods().size() - 1); + inputMessage = messages.get("VerifyTestRequest"); + httpBindingsOpt = HttpRuleParser.parseHttpBindings(rpcMethod, inputMessage, messages); + assertTrue(httpBindingsOpt.isPresent()); + assertThat(httpBindingsOpt.get()).containsExactly("name"); + } + + @Test + public void parseHttpAnnotation_missingFieldFromMessage() { + FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); + ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); + assertEquals(testingService.getName(), "Testing"); + + Map messages = Parser.parseMessages(testingFileDescriptor); + + // VerityTest method. + MethodDescriptor rpcMethod = + testingService.getMethods().get(testingService.getMethods().size() - 1); + Message inputMessage = messages.get("CreateSessionRequest"); + assertThrows( + IllegalStateException.class, + () -> HttpRuleParser.parseHttpBindings(rpcMethod, inputMessage, messages)); + } +} From 28d3b3c9300dffdc7defd81f17d2bef9335bef8f Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 21 Oct 2020 13:23:48 -0700 Subject: [PATCH 4/5] feat: Generate RequestParamsExtractor in GrpcServiceStub --- .../GrpcServiceStubClassComposer.java | 164 ++++++-- .../GrpcServiceStubClassComposerTest.java | 40 +- .../composer/goldens/GrpcTestingStub.golden | 355 ++++++++++++++++++ test/integration/BUILD.bazel | 3 +- 4 files changed, 528 insertions(+), 34 deletions(-) create mode 100644 src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcTestingStub.golden diff --git a/src/main/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposer.java index 73f0780209..0d688dfc47 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposer.java @@ -22,9 +22,11 @@ import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.OperationCallable; +import com.google.api.gax.rpc.RequestParamsExtractor; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.AnonymousClassExpr; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ClassDefinition; import com.google.api.generator.engine.ast.ConcreteReference; @@ -51,6 +53,8 @@ import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; import com.google.longrunning.stub.GrpcOperationsStub; import io.grpc.MethodDescriptor; @@ -90,7 +94,7 @@ public class GrpcServiceStubClassComposer implements ClassComposer { private static final GrpcServiceStubClassComposer INSTANCE = new GrpcServiceStubClassComposer(); - private static final Map staticTypes = createStaticTypes(); + private static final Map STATIC_TYPES = createStaticTypes(); private GrpcServiceStubClassComposer() {} @@ -117,21 +121,21 @@ public GapicClass generate(Service service, Map ignore) { VariableExpr.withVariable( Variable.builder() .setName(BACKGROUND_RESOURCES_MEMBER_NAME) - .setType(staticTypes.get("BackgroundResource")) + .setType(STATIC_TYPES.get("BackgroundResource")) .build())); classMemberVarExprs.put( OPERATIONS_STUB_MEMBER_NAME, VariableExpr.withVariable( Variable.builder() .setName(OPERATIONS_STUB_MEMBER_NAME) - .setType(staticTypes.get("GrpcOperationsStub")) + .setType(STATIC_TYPES.get("GrpcOperationsStub")) .build())); classMemberVarExprs.put( CALLABLE_FACTORY_MEMBER_NAME, VariableExpr.withVariable( Variable.builder() .setName(CALLABLE_FACTORY_MEMBER_NAME) - .setType(staticTypes.get("GrpcStubCallableFactory")) + .setType(STATIC_TYPES.get("GrpcStubCallableFactory")) .build())); List classStatements = @@ -196,7 +200,7 @@ private static Statement createMethodDescriptorVariableDecl( MethodInvocationExpr methodDescriptorMaker = MethodInvocationExpr.builder() .setMethodName("newBuilder") - .setStaticReferenceType(staticTypes.get("MethodDescriptor")) + .setStaticReferenceType(STATIC_TYPES.get("MethodDescriptor")) .setGenerics(methodDescriptorVarExpr.variable().type().reference().generics()) .build(); @@ -226,7 +230,7 @@ private static Statement createMethodDescriptorVariableDecl( Function protoUtilsMarshallerFn = m -> MethodInvocationExpr.builder() - .setStaticReferenceType(staticTypes.get("ProtoUtils")) + .setStaticReferenceType(STATIC_TYPES.get("ProtoUtils")) .setMethodName("marshaller") .setArguments(Arrays.asList(m)) .build(); @@ -374,7 +378,7 @@ private static Map createCallableClassMembers( private static List createClassAnnotations() { return Arrays.asList( AnnotationNode.builder() - .setType(staticTypes.get("Generated")) + .setType(STATIC_TYPES.get("Generated")) .setDescription("by gapic-generator-java") .build()); } @@ -430,7 +434,7 @@ private static List createStaticCreatorMethods( VariableExpr.withVariable( Variable.builder().setName("settings").setType(stubSettingsType).build()); - TypeNode clientContextType = staticTypes.get("ClientContext"); + TypeNode clientContextType = STATIC_TYPES.get("ClientContext"); VariableExpr clientContextVarExpr = VariableExpr.withVariable( Variable.builder().setName("clientContext").setType(clientContextType).build()); @@ -439,7 +443,7 @@ private static List createStaticCreatorMethods( VariableExpr.withVariable( Variable.builder() .setName("callableFactory") - .setType(staticTypes.get("GrpcStubCallableFactory")) + .setType(STATIC_TYPES.get("GrpcStubCallableFactory")) .build()); MethodInvocationExpr clientContextCreateMethodExpr = @@ -492,7 +496,7 @@ private static List createConstructorMethods( VariableExpr.withVariable( Variable.builder().setName("settings").setType(stubSettingsType).build()); - TypeNode clientContextType = staticTypes.get("ClientContext"); + TypeNode clientContextType = STATIC_TYPES.get("ClientContext"); VariableExpr clientContextVarExpr = VariableExpr.withVariable( Variable.builder().setName("clientContext").setType(clientContextType).build()); @@ -501,7 +505,7 @@ private static List createConstructorMethods( VariableExpr.withVariable( Variable.builder() .setName("callableFactory") - .setType(staticTypes.get("GrpcStubCallableFactory")) + .setType(STATIC_TYPES.get("GrpcStubCallableFactory")) .build()); TypeNode thisClassType = types.get(getThisClassName(service.name())); @@ -562,7 +566,7 @@ private static List createConstructorMethods( operationsStubClassVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) .setValueExpr( MethodInvocationExpr.builder() - .setStaticReferenceType(staticTypes.get("GrpcOperationsStub")) + .setStaticReferenceType(STATIC_TYPES.get("GrpcOperationsStub")) .setMethodName("create") .setArguments(Arrays.asList(clientContextVarExpr, callableFactoryVarExpr)) .setReturnType(operationsStubClassVarExpr.type()) @@ -602,6 +606,7 @@ private static List createConstructorMethods( .map( m -> createTransportSettingsInitExpr( + m, javaStyleMethodNameToTransportSettingsVarExprs.get( JavaStyle.toLowerCamelCase(m.name())), protoMethodNameToDescriptorVarExprs.get(m.name()))) @@ -644,7 +649,7 @@ private static List createConstructorMethods( backgroundResourcesVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) .setValueExpr( NewObjectExpr.builder() - .setType(staticTypes.get("BackgroundResourceAggregation")) + .setType(STATIC_TYPES.get("BackgroundResourceAggregation")) .setArguments(Arrays.asList(getBackgroundResourcesMethodExpr)) .build()) .build()); @@ -662,10 +667,10 @@ private static List createConstructorMethods( } private static Expr createTransportSettingsInitExpr( - VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr) { + Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr) { MethodInvocationExpr callSettingsBuilderExpr = MethodInvocationExpr.builder() - .setStaticReferenceType(staticTypes.get("GrpcCallSettings")) + .setStaticReferenceType(STATIC_TYPES.get("GrpcCallSettings")) .setGenerics(transportSettingsVarExpr.type().reference().generics()) .setMethodName("newBuilder") .build(); @@ -675,6 +680,16 @@ private static Expr createTransportSettingsInitExpr( .setMethodName("setMethodDescriptor") .setArguments(Arrays.asList(methodDescriptorVarExpr)) .build(); + + if (method.hasHttpBindings()) { + callSettingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(callSettingsBuilderExpr) + .setMethodName("setParamsExtractor") + .setArguments(createRequestParamsExtractorAnonClass(method)) + .build(); + } + callSettingsBuilderExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(callSettingsBuilderExpr) @@ -687,6 +702,111 @@ private static Expr createTransportSettingsInitExpr( .build(); } + private static AnonymousClassExpr createRequestParamsExtractorAnonClass(Method method) { + Preconditions.checkState( + method.hasHttpBindings(), String.format("Method %s has no HTTP binding", method.name())); + + TypeNode paramsVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ImmutableMap.Builder.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + VariableExpr paramsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("params").setType(paramsVarType).build()); + VariableExpr reqeustVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("request").setType(method.inputType()).build()); + + Expr paramsAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(paramsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(STATIC_TYPES.get("ImmutableMap")) + .setMethodName("builder") + .setReturnType(paramsVarType) + .build()) + .build(); + List bodyExprs = new ArrayList<>(); + bodyExprs.add(paramsAssignExpr); + + VariableExpr requestVarExpr = + VariableExpr.withVariable( + Variable.builder().setType(method.inputType()).setName("request").build()); + + for (String httpBindingFieldName : method.httpBindings()) { + // Handle foo.bar cases by descending into the subfields. + MethodInvocationExpr.Builder requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + String[] descendantFields = httpBindingFieldName.split("\\."); + for (int i = 0; i < descendantFields.length; i++) { + String currFieldName = descendantFields[i]; + String bindingFieldMethodName = + String.format("get%s", JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldGetterExprBuilder = + requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); + if (i < descendantFields.length - 1) { + requestFieldGetterExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); + } + } + + MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); + Expr valueOfExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.STRING) + .setMethodName("valueOf") + .setArguments(requestBuilderExpr) + .build(); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExpr) + .setMethodName("put") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue(httpBindingFieldName)), + valueOfExpr) + .build(); + bodyExprs.add(paramsPutExpr); + } + + TypeNode returnType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + Expr returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(paramsVarExpr) + .setMethodName("build") + .setReturnType(returnType) + .build(); + + MethodDefinition extractMethod = + MethodDefinition.builder() + .setIsOverride(true) + .setScope(ScopeNode.PUBLIC) + .setReturnType(returnType) + .setName("extract") + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody( + bodyExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())) + .setReturnExpr(returnExpr) + .build(); + + TypeNode anonClassType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(RequestParamsExtractor.class) + .setGenerics(method.inputType().reference()) + .build()); + return AnonymousClassExpr.builder().setType(anonClassType).setMethods(extractMethod).build(); + } + private static Expr createCallableInitExpr( String callableVarName, VariableExpr callableVarExpr, @@ -845,7 +965,7 @@ private static List createStubOverrideMethods( VariableExpr.withVariable( Variable.builder().setName("duration").setType(TypeNode.LONG).build()), VariableExpr.withVariable( - Variable.builder().setName("unit").setType(staticTypes.get("TimeUnit")).build())); + Variable.builder().setName("unit").setType(STATIC_TYPES.get("TimeUnit")).build())); javaMethods.add( methodMakerStarterFn .apply("awaitTermination") @@ -854,7 +974,7 @@ private static List createStubOverrideMethods( awaitTerminationArgs.stream() .map(v -> v.toBuilder().setIsDecl(true).build()) .collect(Collectors.toList())) - .setThrowsExceptions(Arrays.asList(staticTypes.get("InterruptedException"))) + .setThrowsExceptions(Arrays.asList(STATIC_TYPES.get("InterruptedException"))) .setReturnExpr( MethodInvocationExpr.builder() .setExprReferenceExpr(backgroundResourcesVarExpr) @@ -881,12 +1001,14 @@ private static Map createStaticTypes() { GrpcCallSettings.class, GrpcOperationsStub.class, GrpcStubCallableFactory.class, + ImmutableMap.class, InterruptedException.class, IOException.class, MethodDescriptor.class, Operation.class, OperationCallable.class, ProtoUtils.class, + RequestParamsExtractor.class, ServerStreamingCallable.class, TimeUnit.class, UnaryCallable.class); @@ -934,16 +1056,16 @@ private static Map createDynamicTypes(Service service, String } private static TypeNode getCallableType(Method protoMethod) { - TypeNode callableType = staticTypes.get("UnaryCallable"); + TypeNode callableType = STATIC_TYPES.get("UnaryCallable"); switch (protoMethod.stream()) { case CLIENT: - callableType = staticTypes.get("ClientStreamingCallable"); + callableType = STATIC_TYPES.get("ClientStreamingCallable"); break; case SERVER: - callableType = staticTypes.get("ServerStreamingCallable"); + callableType = STATIC_TYPES.get("ServerStreamingCallable"); break; case BIDI: - callableType = staticTypes.get("BidiStreamingCallable"); + callableType = STATIC_TYPES.get("BidiStreamingCallable"); break; case NONE: // Fall through diff --git a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java index e3f63dcaa8..402d94450a 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java @@ -27,28 +27,22 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.showcase.v1beta1.EchoOuterClass; +import com.google.showcase.v1beta1.TestingOuterClass; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.junit.Before; import org.junit.Test; public class GrpcServiceStubClassComposerTest { - private ServiceDescriptor echoService; - private FileDescriptor echoFileDescriptor; - - @Before - public void setUp() { - echoFileDescriptor = EchoOuterClass.getDescriptor(); - echoService = echoFileDescriptor.getServices().get(0); + @Test + public void generateGrpcServiceStubClass_simple() { + FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor(); + ServiceDescriptor echoService = echoFileDescriptor.getServices().get(0); assertEquals(echoService.getName(), "Echo"); - } - @Test - public void generateServiceClasses() { Map messageTypes = Parser.parseMessages(echoFileDescriptor); Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); Set outputResourceNames = new HashSet<>(); @@ -64,4 +58,28 @@ public void generateServiceClasses() { Path goldenFilePath = Paths.get(ComposerConstants.GOLDENFILES_DIRECTORY, "GrpcEchoStub.golden"); Assert.assertCodeEquals(goldenFilePath, visitor.write()); } + + @Test + public void generateGrpcServiceStubClass_httpBindings() { + FileDescriptor testingFileDescriptor = TestingOuterClass.getDescriptor(); + ServiceDescriptor testingService = testingFileDescriptor.getServices().get(0); + assertEquals(testingService.getName(), "Testing"); + + Map messageTypes = Parser.parseMessages(testingFileDescriptor); + Map resourceNames = Parser.parseResourceNames(testingFileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService( + testingFileDescriptor, messageTypes, resourceNames, outputResourceNames); + Service testingProtoService = services.get(0); + GapicClass clazz = + GrpcServiceStubClassComposer.instance().generate(testingProtoService, messageTypes); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), "GrpcTestingStub.golden", visitor.write()); + Path goldenFilePath = + Paths.get(ComposerConstants.GOLDENFILES_DIRECTORY, "GrpcTestingStub.golden"); + Assert.assertCodeEquals(goldenFilePath, visitor.write()); + } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcTestingStub.golden b/src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcTestingStub.golden new file mode 100644 index 0000000000..8ab8396bb9 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcTestingStub.golden @@ -0,0 +1,355 @@ +package com.google.showcase.v1beta1.stub; + +import static com.google.showcase.v1beta1.TestingClient.ListSessionsPagedResponse; +import static com.google.showcase.v1beta1.TestingClient.ListTestsPagedResponse; + +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.grpc.GrpcCallSettings; +import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.RequestParamsExtractor; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.collect.ImmutableMap; +import com.google.longrunning.stub.GrpcOperationsStub; +import com.google.protobuf.Empty; +import com.google.showcase.v1beta1.CreateSessionRequest; +import com.google.showcase.v1beta1.DeleteSessionRequest; +import com.google.showcase.v1beta1.DeleteTestRequest; +import com.google.showcase.v1beta1.GetSessionRequest; +import com.google.showcase.v1beta1.ListSessionsRequest; +import com.google.showcase.v1beta1.ListSessionsResponse; +import com.google.showcase.v1beta1.ListTestsRequest; +import com.google.showcase.v1beta1.ListTestsResponse; +import com.google.showcase.v1beta1.ReportSessionRequest; +import com.google.showcase.v1beta1.ReportSessionResponse; +import com.google.showcase.v1beta1.Session; +import com.google.showcase.v1beta1.VerifyTestRequest; +import com.google.showcase.v1beta1.VerifyTestResponse; +import io.grpc.MethodDescriptor; +import io.grpc.protobuf.ProtoUtils; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * gRPC stub implementation for the Testing service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@Generated("by gapic-generator-java") +public class GrpcTestingStub extends TestingStub { + private static final MethodDescriptor + createSessionMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/CreateSession") + .setRequestMarshaller( + ProtoUtils.marshaller(CreateSessionRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Session.getDefaultInstance())) + .build(); + + private static final MethodDescriptor getSessionMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/GetSession") + .setRequestMarshaller(ProtoUtils.marshaller(GetSessionRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Session.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + listSessionsMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/ListSessions") + .setRequestMarshaller(ProtoUtils.marshaller(ListSessionsRequest.getDefaultInstance())) + .setResponseMarshaller( + ProtoUtils.marshaller(ListSessionsResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor deleteSessionMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/DeleteSession") + .setRequestMarshaller(ProtoUtils.marshaller(DeleteSessionRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + reportSessionMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/ReportSession") + .setRequestMarshaller( + ProtoUtils.marshaller(ReportSessionRequest.getDefaultInstance())) + .setResponseMarshaller( + ProtoUtils.marshaller(ReportSessionResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + listTestsMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/ListTests") + .setRequestMarshaller(ProtoUtils.marshaller(ListTestsRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(ListTestsResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor deleteTestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/DeleteTest") + .setRequestMarshaller(ProtoUtils.marshaller(DeleteTestRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + verifyTestMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.showcase.v1beta1.Testing/VerifyTest") + .setRequestMarshaller(ProtoUtils.marshaller(VerifyTestRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(VerifyTestResponse.getDefaultInstance())) + .build(); + + private final UnaryCallable createSessionCallable; + private final UnaryCallable getSessionCallable; + private final UnaryCallable listSessionsCallable; + private final UnaryCallable + listSessionsPagedCallable; + private final UnaryCallable deleteSessionCallable; + private final UnaryCallable reportSessionCallable; + private final UnaryCallable listTestsCallable; + private final UnaryCallable listTestsPagedCallable; + private final UnaryCallable deleteTestCallable; + private final UnaryCallable verifyTestCallable; + + private final BackgroundResource backgroundResources; + private final GrpcOperationsStub operationsStub; + private final GrpcStubCallableFactory callableFactory; + + public static final GrpcTestingStub create(TestingStubSettings settings) throws IOException { + return new GrpcTestingStub(settings, ClientContext.create(settings)); + } + + public static final GrpcTestingStub create(ClientContext clientContext) throws IOException { + return new GrpcTestingStub(TestingStubSettings.newBuilder().build(), clientContext); + } + + public static final GrpcTestingStub create( + ClientContext clientContext, GrpcStubCallableFactory callableFactory) throws IOException { + return new GrpcTestingStub( + TestingStubSettings.newBuilder().build(), clientContext, callableFactory); + } + + protected GrpcTestingStub(TestingStubSettings settings, ClientContext clientContext) + throws IOException { + this(settings, clientContext, new GrpcTestingCallableFactory()); + } + + protected GrpcTestingStub( + TestingStubSettings settings, + ClientContext clientContext, + GrpcStubCallableFactory callableFactory) + throws IOException { + this.callableFactory = callableFactory; + this.operationsStub = GrpcOperationsStub.create(clientContext, callableFactory); + + GrpcCallSettings createSessionTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(createSessionMethodDescriptor) + .build(); + GrpcCallSettings getSessionTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getSessionMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(GetSessionRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); + GrpcCallSettings listSessionsTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(listSessionsMethodDescriptor) + .build(); + GrpcCallSettings deleteSessionTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(deleteSessionMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(DeleteSessionRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); + GrpcCallSettings reportSessionTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(reportSessionMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(ReportSessionRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); + GrpcCallSettings listTestsTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(listTestsMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(ListTestsRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("parent", String.valueOf(request.getParent())); + return params.build(); + } + }) + .build(); + GrpcCallSettings deleteTestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(deleteTestMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(DeleteTestRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); + GrpcCallSettings verifyTestTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(verifyTestMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(VerifyTestRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); + + this.createSessionCallable = + callableFactory.createUnaryCallable( + createSessionTransportSettings, settings.createSessionSettings(), clientContext); + this.getSessionCallable = + callableFactory.createUnaryCallable( + getSessionTransportSettings, settings.getSessionSettings(), clientContext); + this.listSessionsCallable = + callableFactory.createUnaryCallable( + listSessionsTransportSettings, settings.listSessionsSettings(), clientContext); + this.listSessionsPagedCallable = + callableFactory.createPagedCallable( + listSessionsTransportSettings, settings.listSessionsSettings(), clientContext); + this.deleteSessionCallable = + callableFactory.createUnaryCallable( + deleteSessionTransportSettings, settings.deleteSessionSettings(), clientContext); + this.reportSessionCallable = + callableFactory.createUnaryCallable( + reportSessionTransportSettings, settings.reportSessionSettings(), clientContext); + this.listTestsCallable = + callableFactory.createUnaryCallable( + listTestsTransportSettings, settings.listTestsSettings(), clientContext); + this.listTestsPagedCallable = + callableFactory.createPagedCallable( + listTestsTransportSettings, settings.listTestsSettings(), clientContext); + this.deleteTestCallable = + callableFactory.createUnaryCallable( + deleteTestTransportSettings, settings.deleteTestSettings(), clientContext); + this.verifyTestCallable = + callableFactory.createUnaryCallable( + verifyTestTransportSettings, settings.verifyTestSettings(), clientContext); + + this.backgroundResources = + new BackgroundResourceAggregation(clientContext.getBackgroundResources()); + } + + public GrpcOperationsStub getOperationsStub() { + return operationsStub; + } + + public UnaryCallable createSessionCallable() { + return createSessionCallable; + } + + public UnaryCallable getSessionCallable() { + return getSessionCallable; + } + + public UnaryCallable listSessionsCallable() { + return listSessionsCallable; + } + + public UnaryCallable listSessionsPagedCallable() { + return listSessionsPagedCallable; + } + + public UnaryCallable deleteSessionCallable() { + return deleteSessionCallable; + } + + public UnaryCallable reportSessionCallable() { + return reportSessionCallable; + } + + public UnaryCallable listTestsCallable() { + return listTestsCallable; + } + + public UnaryCallable listTestsPagedCallable() { + return listTestsPagedCallable; + } + + public UnaryCallable deleteTestCallable() { + return deleteTestCallable; + } + + public UnaryCallable verifyTestCallable() { + return verifyTestCallable; + } + + @Override + public final void close() { + shutdown(); + } + + @Override + public void shutdown() { + backgroundResources.shutdown(); + } + + @Override + public boolean isShutdown() { + return backgroundResources.isShutdown(); + } + + @Override + public boolean isTerminated() { + return backgroundResources.isTerminated(); + } + + @Override + public void shutdownNow() { + backgroundResources.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return backgroundResources.awaitTermination(duration, unit); + } +} diff --git a/test/integration/BUILD.bazel b/test/integration/BUILD.bazel index 11f96c0dbe..a6f574438f 100644 --- a/test/integration/BUILD.bazel +++ b/test/integration/BUILD.bazel @@ -3,7 +3,6 @@ load( "proto_library_with_info", java_gapic_library = "java_gapic_library2", ) - load( "//:rules_bazel/java/integration_test.bzl", "integration_test", @@ -58,6 +57,6 @@ java_gapic_library( integration_test( name = "redis", - target = ":redis_java_gapic", data = ["//test/integration/goldens/redis:goldens_files"], + target = ":redis_java_gapic", ) From 529355182a3edb0da2d7fecf63c7095b0e2dfd1f Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 21 Oct 2020 13:50:40 -0700 Subject: [PATCH 5/5] feat: add GrpcPublisherStub test to exercise HTTP subfields --- .../GrpcServiceStubClassComposerTest.java | 32 ++ .../composer/goldens/GrpcPublisherStub.golden | 438 ++++++++++++++++++ 2 files changed, 470 insertions(+) create mode 100644 src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcPublisherStub.golden diff --git a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java index 402d94450a..28f7bec18f 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/GrpcServiceStubClassComposerTest.java @@ -26,10 +26,13 @@ import com.google.api.generator.test.framework.Utils; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.pubsub.v1.PubsubProto; import com.google.showcase.v1beta1.EchoOuterClass; import com.google.showcase.v1beta1.TestingOuterClass; +import google.cloud.CommonResources; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -82,4 +85,33 @@ public void generateGrpcServiceStubClass_httpBindings() { Paths.get(ComposerConstants.GOLDENFILES_DIRECTORY, "GrpcTestingStub.golden"); Assert.assertCodeEquals(goldenFilePath, visitor.write()); } + + @Test + public void generateGrpcServiceStubClass_httpBindingsWithSubMessageFields() { + FileDescriptor serviceFileDescriptor = PubsubProto.getDescriptor(); + FileDescriptor commonResourcesFileDescriptor = CommonResources.getDescriptor(); + ServiceDescriptor serviceDescriptor = serviceFileDescriptor.getServices().get(0); + assertEquals("Publisher", serviceDescriptor.getName()); + + Map resourceNames = new HashMap<>(); + resourceNames.putAll(Parser.parseResourceNames(serviceFileDescriptor)); + resourceNames.putAll(Parser.parseResourceNames(commonResourcesFileDescriptor)); + + Map messageTypes = Parser.parseMessages(serviceFileDescriptor); + + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService( + serviceFileDescriptor, messageTypes, resourceNames, outputResourceNames); + + Service service = services.get(0); + GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(service, messageTypes); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), "GrpcPublisherStub.golden", visitor.write()); + Path goldenFilePath = + Paths.get(ComposerConstants.GOLDENFILES_DIRECTORY, "GrpcPublisherStub.golden"); + Assert.assertCodeEquals(goldenFilePath, visitor.write()); + } } diff --git a/src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcPublisherStub.golden b/src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcPublisherStub.golden new file mode 100644 index 0000000000..719c3d3e03 --- /dev/null +++ b/src/test/java/com/google/api/generator/gapic/composer/goldens/GrpcPublisherStub.golden @@ -0,0 +1,438 @@ +package com.google.pubsub.v1.stub; + +import static com.google.pubsub.v1.PublisherClient.ListTopicSnapshotsPagedResponse; +import static com.google.pubsub.v1.PublisherClient.ListTopicSubscriptionsPagedResponse; +import static com.google.pubsub.v1.PublisherClient.ListTopicsPagedResponse; + +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.grpc.GrpcCallSettings; +import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.RequestParamsExtractor; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.collect.ImmutableMap; +import com.google.longrunning.stub.GrpcOperationsStub; +import com.google.protobuf.Empty; +import com.google.pubsub.v1.DeleteTopicRequest; +import com.google.pubsub.v1.DetachSubscriptionRequest; +import com.google.pubsub.v1.DetachSubscriptionResponse; +import com.google.pubsub.v1.GetTopicRequest; +import com.google.pubsub.v1.ListTopicSnapshotsRequest; +import com.google.pubsub.v1.ListTopicSnapshotsResponse; +import com.google.pubsub.v1.ListTopicSubscriptionsRequest; +import com.google.pubsub.v1.ListTopicSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicsRequest; +import com.google.pubsub.v1.ListTopicsResponse; +import com.google.pubsub.v1.PublishRequest; +import com.google.pubsub.v1.PublishResponse; +import com.google.pubsub.v1.Topic; +import com.google.pubsub.v1.UpdateTopicRequest; +import io.grpc.MethodDescriptor; +import io.grpc.protobuf.ProtoUtils; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * gRPC stub implementation for the Publisher service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@Generated("by gapic-generator-java") +public class GrpcPublisherStub extends PublisherStub { + private static final MethodDescriptor createTopicMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/CreateTopic") + .setRequestMarshaller(ProtoUtils.marshaller(Topic.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Topic.getDefaultInstance())) + .build(); + + private static final MethodDescriptor updateTopicMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/UpdateTopic") + .setRequestMarshaller(ProtoUtils.marshaller(UpdateTopicRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Topic.getDefaultInstance())) + .build(); + + private static final MethodDescriptor publishMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/Publish") + .setRequestMarshaller(ProtoUtils.marshaller(PublishRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(PublishResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor getTopicMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/GetTopic") + .setRequestMarshaller(ProtoUtils.marshaller(GetTopicRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Topic.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + listTopicsMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/ListTopics") + .setRequestMarshaller(ProtoUtils.marshaller(ListTopicsRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(ListTopicsResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor< + ListTopicSubscriptionsRequest, ListTopicSubscriptionsResponse> + listTopicSubscriptionsMethodDescriptor = + MethodDescriptor + .newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/ListTopicSubscriptions") + .setRequestMarshaller( + ProtoUtils.marshaller(ListTopicSubscriptionsRequest.getDefaultInstance())) + .setResponseMarshaller( + ProtoUtils.marshaller(ListTopicSubscriptionsResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + listTopicSnapshotsMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/ListTopicSnapshots") + .setRequestMarshaller( + ProtoUtils.marshaller(ListTopicSnapshotsRequest.getDefaultInstance())) + .setResponseMarshaller( + ProtoUtils.marshaller(ListTopicSnapshotsResponse.getDefaultInstance())) + .build(); + + private static final MethodDescriptor deleteTopicMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/DeleteTopic") + .setRequestMarshaller(ProtoUtils.marshaller(DeleteTopicRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) + .build(); + + private static final MethodDescriptor + detachSubscriptionMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.pubsub.v1.Publisher/DetachSubscription") + .setRequestMarshaller( + ProtoUtils.marshaller(DetachSubscriptionRequest.getDefaultInstance())) + .setResponseMarshaller( + ProtoUtils.marshaller(DetachSubscriptionResponse.getDefaultInstance())) + .build(); + + private final UnaryCallable createTopicCallable; + private final UnaryCallable updateTopicCallable; + private final UnaryCallable publishCallable; + private final UnaryCallable getTopicCallable; + private final UnaryCallable listTopicsCallable; + private final UnaryCallable listTopicsPagedCallable; + private final UnaryCallable + listTopicSubscriptionsCallable; + private final UnaryCallable + listTopicSubscriptionsPagedCallable; + private final UnaryCallable + listTopicSnapshotsCallable; + private final UnaryCallable + listTopicSnapshotsPagedCallable; + private final UnaryCallable deleteTopicCallable; + private final UnaryCallable + detachSubscriptionCallable; + + private final BackgroundResource backgroundResources; + private final GrpcOperationsStub operationsStub; + private final GrpcStubCallableFactory callableFactory; + + public static final GrpcPublisherStub create(PublisherStubSettings settings) throws IOException { + return new GrpcPublisherStub(settings, ClientContext.create(settings)); + } + + public static final GrpcPublisherStub create(ClientContext clientContext) throws IOException { + return new GrpcPublisherStub(PublisherStubSettings.newBuilder().build(), clientContext); + } + + public static final GrpcPublisherStub create( + ClientContext clientContext, GrpcStubCallableFactory callableFactory) throws IOException { + return new GrpcPublisherStub( + PublisherStubSettings.newBuilder().build(), clientContext, callableFactory); + } + + protected GrpcPublisherStub(PublisherStubSettings settings, ClientContext clientContext) + throws IOException { + this(settings, clientContext, new GrpcPublisherCallableFactory()); + } + + protected GrpcPublisherStub( + PublisherStubSettings settings, + ClientContext clientContext, + GrpcStubCallableFactory callableFactory) + throws IOException { + this.callableFactory = callableFactory; + this.operationsStub = GrpcOperationsStub.create(clientContext, callableFactory); + + GrpcCallSettings createTopicTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(createTopicMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(Topic request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); + GrpcCallSettings updateTopicTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(updateTopicMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(UpdateTopicRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("topic.name", String.valueOf(request.getTopic().getName())); + return params.build(); + } + }) + .build(); + GrpcCallSettings publishTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(publishMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(PublishRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("topic", String.valueOf(request.getTopic())); + return params.build(); + } + }) + .build(); + GrpcCallSettings getTopicTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getTopicMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(GetTopicRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("topic", String.valueOf(request.getTopic())); + return params.build(); + } + }) + .build(); + GrpcCallSettings listTopicsTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(listTopicsMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(ListTopicsRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("project", String.valueOf(request.getProject())); + return params.build(); + } + }) + .build(); + GrpcCallSettings + listTopicSubscriptionsTransportSettings = + GrpcCallSettings + .newBuilder() + .setMethodDescriptor(listTopicSubscriptionsMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(ListTopicSubscriptionsRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("topic", String.valueOf(request.getTopic())); + return params.build(); + } + }) + .build(); + GrpcCallSettings + listTopicSnapshotsTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(listTopicSnapshotsMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(ListTopicSnapshotsRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("topic", String.valueOf(request.getTopic())); + return params.build(); + } + }) + .build(); + GrpcCallSettings deleteTopicTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(deleteTopicMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(DeleteTopicRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("topic", String.valueOf(request.getTopic())); + return params.build(); + } + }) + .build(); + GrpcCallSettings + detachSubscriptionTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(detachSubscriptionMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(DetachSubscriptionRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("subscription", String.valueOf(request.getSubscription())); + return params.build(); + } + }) + .build(); + + this.createTopicCallable = + callableFactory.createUnaryCallable( + createTopicTransportSettings, settings.createTopicSettings(), clientContext); + this.updateTopicCallable = + callableFactory.createUnaryCallable( + updateTopicTransportSettings, settings.updateTopicSettings(), clientContext); + this.publishCallable = + callableFactory.createUnaryCallable( + publishTransportSettings, settings.publishSettings(), clientContext); + this.getTopicCallable = + callableFactory.createUnaryCallable( + getTopicTransportSettings, settings.getTopicSettings(), clientContext); + this.listTopicsCallable = + callableFactory.createUnaryCallable( + listTopicsTransportSettings, settings.listTopicsSettings(), clientContext); + this.listTopicsPagedCallable = + callableFactory.createPagedCallable( + listTopicsTransportSettings, settings.listTopicsSettings(), clientContext); + this.listTopicSubscriptionsCallable = + callableFactory.createUnaryCallable( + listTopicSubscriptionsTransportSettings, + settings.listTopicSubscriptionsSettings(), + clientContext); + this.listTopicSubscriptionsPagedCallable = + callableFactory.createPagedCallable( + listTopicSubscriptionsTransportSettings, + settings.listTopicSubscriptionsSettings(), + clientContext); + this.listTopicSnapshotsCallable = + callableFactory.createUnaryCallable( + listTopicSnapshotsTransportSettings, + settings.listTopicSnapshotsSettings(), + clientContext); + this.listTopicSnapshotsPagedCallable = + callableFactory.createPagedCallable( + listTopicSnapshotsTransportSettings, + settings.listTopicSnapshotsSettings(), + clientContext); + this.deleteTopicCallable = + callableFactory.createUnaryCallable( + deleteTopicTransportSettings, settings.deleteTopicSettings(), clientContext); + this.detachSubscriptionCallable = + callableFactory.createUnaryCallable( + detachSubscriptionTransportSettings, + settings.detachSubscriptionSettings(), + clientContext); + + this.backgroundResources = + new BackgroundResourceAggregation(clientContext.getBackgroundResources()); + } + + public GrpcOperationsStub getOperationsStub() { + return operationsStub; + } + + public UnaryCallable createTopicCallable() { + return createTopicCallable; + } + + public UnaryCallable updateTopicCallable() { + return updateTopicCallable; + } + + public UnaryCallable publishCallable() { + return publishCallable; + } + + public UnaryCallable getTopicCallable() { + return getTopicCallable; + } + + public UnaryCallable listTopicsCallable() { + return listTopicsCallable; + } + + public UnaryCallable listTopicsPagedCallable() { + return listTopicsPagedCallable; + } + + public UnaryCallable + listTopicSubscriptionsCallable() { + return listTopicSubscriptionsCallable; + } + + public UnaryCallable + listTopicSubscriptionsPagedCallable() { + return listTopicSubscriptionsPagedCallable; + } + + public UnaryCallable + listTopicSnapshotsCallable() { + return listTopicSnapshotsCallable; + } + + public UnaryCallable + listTopicSnapshotsPagedCallable() { + return listTopicSnapshotsPagedCallable; + } + + public UnaryCallable deleteTopicCallable() { + return deleteTopicCallable; + } + + public UnaryCallable + detachSubscriptionCallable() { + return detachSubscriptionCallable; + } + + @Override + public final void close() { + shutdown(); + } + + @Override + public void shutdown() { + backgroundResources.shutdown(); + } + + @Override + public boolean isShutdown() { + return backgroundResources.isShutdown(); + } + + @Override + public boolean isTerminated() { + return backgroundResources.isTerminated(); + } + + @Override + public void shutdownNow() { + backgroundResources.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return backgroundResources.awaitTermination(duration, unit); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy