newBuilder()
diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonRoutingHeadersStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonRoutingHeadersStub.golden
new file mode 100644
index 0000000000..56b00d49ea
--- /dev/null
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonRoutingHeadersStub.golden
@@ -0,0 +1,372 @@
+package com.google.explicit.dynamic.routing.header.stub;
+
+import com.google.api.core.BetaApi;
+import com.google.api.core.InternalApi;
+import com.google.api.gax.core.BackgroundResource;
+import com.google.api.gax.core.BackgroundResourceAggregation;
+import com.google.api.gax.httpjson.ApiMethodDescriptor;
+import com.google.api.gax.httpjson.HttpJsonCallSettings;
+import com.google.api.gax.httpjson.HttpJsonStubCallableFactory;
+import com.google.api.gax.httpjson.ProtoMessageRequestFormatter;
+import com.google.api.gax.httpjson.ProtoMessageResponseParser;
+import com.google.api.gax.httpjson.ProtoRestSerializer;
+import com.google.api.gax.rpc.ClientContext;
+import com.google.api.gax.rpc.RequestParamsBuilder;
+import com.google.api.gax.rpc.UnaryCallable;
+import com.google.api.pathtemplate.PathTemplate;
+import com.google.explicit.dynamic.routing.header.Request;
+import com.google.explicit.dynamic.routing.header.RequestWithNestedField;
+import com.google.protobuf.Empty;
+import com.google.protobuf.TypeRegistry;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Generated;
+
+// AUTO-GENERATED DOCUMENTATION AND CLASS.
+/**
+ * REST stub implementation for the ExplicitDynamicRoutingHeaderTesting service API.
+ *
+ * This class is for advanced usage and reflects the underlying API directly.
+ */
+@Generated("by gapic-generator-java")
+@BetaApi
+public class HttpJsonExplicitDynamicRoutingHeaderTestingStub
+ extends ExplicitDynamicRoutingHeaderTestingStub {
+ private static final TypeRegistry typeRegistry = TypeRegistry.newBuilder().build();
+
+ private static final ApiMethodDescriptor
+ backwardsCompatible1TestMethodDescriptor =
+ ApiMethodDescriptor.newBuilder()
+ .setFullMethodName(
+ "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/BackwardsCompatible1Test")
+ .setHttpMethod("GET")
+ .setType(ApiMethodDescriptor.MethodType.UNARY)
+ .setRequestFormatter(
+ ProtoMessageRequestFormatter.newBuilder()
+ .setPath(
+ "/v1beta1/{tableName=tests/*}",
+ request -> {
+ Map fields = new HashMap<>();
+ ProtoRestSerializer serializer = ProtoRestSerializer.create();
+ serializer.putPathParam(fields, "tableName", request.getTableName());
+ return fields;
+ })
+ .setQueryParamsExtractor(
+ request -> {
+ Map> fields = new HashMap<>();
+ ProtoRestSerializer serializer = ProtoRestSerializer.create();
+ serializer.putQueryParam(
+ fields, "appProfileId", request.getAppProfileId());
+ return fields;
+ })
+ .setRequestBodyExtractor(request -> null)
+ .build())
+ .setResponseParser(
+ ProtoMessageResponseParser.newBuilder()
+ .setDefaultInstance(Empty.getDefaultInstance())
+ .setDefaultTypeRegistry(typeRegistry)
+ .build())
+ .build();
+
+ private static final ApiMethodDescriptor
+ backwardsCompatible2TestMethodDescriptor =
+ ApiMethodDescriptor.newBuilder()
+ .setFullMethodName(
+ "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/BackwardsCompatible2Test")
+ .setHttpMethod("GET")
+ .setType(ApiMethodDescriptor.MethodType.UNARY)
+ .setRequestFormatter(
+ ProtoMessageRequestFormatter.newBuilder()
+ .setPath(
+ "/v1beta1/{tableName=tests/*}",
+ request -> {
+ Map fields = new HashMap<>();
+ ProtoRestSerializer serializer = ProtoRestSerializer.create();
+ serializer.putPathParam(fields, "tableName", request.getTableName());
+ return fields;
+ })
+ .setQueryParamsExtractor(
+ request -> {
+ Map> fields = new HashMap<>();
+ ProtoRestSerializer serializer = ProtoRestSerializer.create();
+ serializer.putQueryParam(
+ fields, "appProfileId", request.getAppProfileId());
+ return fields;
+ })
+ .setRequestBodyExtractor(request -> null)
+ .build())
+ .setResponseParser(
+ ProtoMessageResponseParser.newBuilder()
+ .setDefaultInstance(Empty.getDefaultInstance())
+ .setDefaultTypeRegistry(typeRegistry)
+ .build())
+ .build();
+
+ private static final ApiMethodDescriptor
+ backwardsCompatible3TestMethodDescriptor =
+ ApiMethodDescriptor.newBuilder()
+ .setFullMethodName(
+ "google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTesting/BackwardsCompatible3Test")
+ .setHttpMethod("GET")
+ .setType(ApiMethodDescriptor.MethodType.UNARY)
+ .setRequestFormatter(
+ ProtoMessageRequestFormatter.newBuilder()
+ .setPath(
+ "/v1beta1/{tableName=tests/*}",
+ request -> {
+ Map fields = new HashMap<>();
+ ProtoRestSerializer serializer = ProtoRestSerializer.create();
+ serializer.putPathParam(fields, "tableName", request.getTableName());
+ return fields;
+ })
+ .setQueryParamsExtractor(
+ request -> {
+ Map> fields = new HashMap<>();
+ ProtoRestSerializer serializer = ProtoRestSerializer.create();
+ serializer.putQueryParam(
+ fields, "appProfileId", request.getAppProfileId());
+ return fields;
+ })
+ .setRequestBodyExtractor(request -> null)
+ .build())
+ .setResponseParser(
+ ProtoMessageResponseParser.newBuilder()
+ .setDefaultInstance(Empty.getDefaultInstance())
+ .setDefaultTypeRegistry(typeRegistry)
+ .build())
+ .build();
+
+ private final UnaryCallable backwardsCompatible1TestCallable;
+ private final UnaryCallable backwardsCompatible2TestCallable;
+ private final UnaryCallable backwardsCompatible3TestCallable;
+
+ private final BackgroundResource backgroundResources;
+ private final HttpJsonStubCallableFactory callableFactory;
+
+ private static final PathTemplate BACKWARDS_COMPATIBLE1_TEST_0_PATH_TEMPLATE =
+ PathTemplate.create("{routing_id=projects/*}/**");
+
+ public static final HttpJsonExplicitDynamicRoutingHeaderTestingStub create(
+ ExplicitDynamicRoutingHeaderTestingStubSettings settings) throws IOException {
+ return new HttpJsonExplicitDynamicRoutingHeaderTestingStub(
+ settings, ClientContext.create(settings));
+ }
+
+ public static final HttpJsonExplicitDynamicRoutingHeaderTestingStub create(
+ ClientContext clientContext) throws IOException {
+ return new HttpJsonExplicitDynamicRoutingHeaderTestingStub(
+ ExplicitDynamicRoutingHeaderTestingStubSettings.newBuilder().build(), clientContext);
+ }
+
+ public static final HttpJsonExplicitDynamicRoutingHeaderTestingStub create(
+ ClientContext clientContext, HttpJsonStubCallableFactory callableFactory) throws IOException {
+ return new HttpJsonExplicitDynamicRoutingHeaderTestingStub(
+ ExplicitDynamicRoutingHeaderTestingStubSettings.newBuilder().build(),
+ clientContext,
+ callableFactory);
+ }
+
+ /**
+ * Constructs an instance of HttpJsonExplicitDynamicRoutingHeaderTestingStub, 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 HttpJsonExplicitDynamicRoutingHeaderTestingStub(
+ ExplicitDynamicRoutingHeaderTestingStubSettings settings, ClientContext clientContext)
+ throws IOException {
+ this(settings, clientContext, new HttpJsonExplicitDynamicRoutingHeaderTestingCallableFactory());
+ }
+
+ /**
+ * Constructs an instance of HttpJsonExplicitDynamicRoutingHeaderTestingStub, 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 HttpJsonExplicitDynamicRoutingHeaderTestingStub(
+ ExplicitDynamicRoutingHeaderTestingStubSettings settings,
+ ClientContext clientContext,
+ HttpJsonStubCallableFactory callableFactory)
+ throws IOException {
+ this.callableFactory = callableFactory;
+
+ HttpJsonCallSettings backwardsCompatible1TestTransportSettings =
+ HttpJsonCallSettings.newBuilder()
+ .setMethodDescriptor(backwardsCompatible1TestMethodDescriptor)
+ .setTypeRegistry(typeRegistry)
+ .setParamsExtractor(
+ request -> {
+ RequestParamsBuilder builder = RequestParamsBuilder.create();
+ builder.add(
+ request.getTableName(),
+ "routing_id",
+ BACKWARDS_COMPATIBLE1_TEST_0_PATH_TEMPLATE);
+ return builder.build();
+ })
+ .build();
+ HttpJsonCallSettings backwardsCompatible2TestTransportSettings =
+ HttpJsonCallSettings.newBuilder()
+ .setMethodDescriptor(backwardsCompatible2TestMethodDescriptor)
+ .setTypeRegistry(typeRegistry)
+ .build();
+ HttpJsonCallSettings backwardsCompatible3TestTransportSettings =
+ HttpJsonCallSettings.newBuilder()
+ .setMethodDescriptor(backwardsCompatible3TestMethodDescriptor)
+ .setTypeRegistry(typeRegistry)
+ .setParamsExtractor(
+ request -> {
+ RequestParamsBuilder builder = RequestParamsBuilder.create();
+ builder.add("table_name", String.valueOf(request.getTableName()));
+ return builder.build();
+ })
+ .build();
+
+ this.backwardsCompatible1TestCallable =
+ callableFactory.createUnaryCallable(
+ backwardsCompatible1TestTransportSettings,
+ settings.backwardsCompatible1TestSettings(),
+ clientContext);
+ this.backwardsCompatible2TestCallable =
+ callableFactory.createUnaryCallable(
+ backwardsCompatible2TestTransportSettings,
+ settings.backwardsCompatible2TestSettings(),
+ clientContext);
+ this.backwardsCompatible3TestCallable =
+ callableFactory.createUnaryCallable(
+ backwardsCompatible3TestTransportSettings,
+ settings.backwardsCompatible3TestSettings(),
+ clientContext);
+
+ this.backgroundResources =
+ new BackgroundResourceAggregation(clientContext.getBackgroundResources());
+ }
+
+ @InternalApi
+ public static List getMethodDescriptors() {
+ List methodDescriptors = new ArrayList<>();
+ methodDescriptors.add(backwardsCompatible1TestMethodDescriptor);
+ methodDescriptors.add(backwardsCompatible2TestMethodDescriptor);
+ methodDescriptors.add(backwardsCompatible3TestMethodDescriptor);
+ return methodDescriptors;
+ }
+
+ @Override
+ public UnaryCallable backwardsCompatible1TestCallable() {
+ return backwardsCompatible1TestCallable;
+ }
+
+ @Override
+ public UnaryCallable backwardsCompatible2TestCallable() {
+ return backwardsCompatible2TestCallable;
+ }
+
+ @Override
+ public UnaryCallable backwardsCompatible3TestCallable() {
+ return backwardsCompatible3TestCallable;
+ }
+
+ @Override
+ public UnaryCallable example1TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example1TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example2TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example2TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example3TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example3TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example3CTestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example3CTestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example4TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example4TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example5TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example5TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example6TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example6TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example7TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example7TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example8TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example8TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable example9TestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: example9TestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public UnaryCallable nestedFieldTestCallable() {
+ throw new UnsupportedOperationException(
+ "Not implemented: nestedFieldTestCallable(). REST transport is not implemented for this method yet.");
+ }
+
+ @Override
+ public final void close() {
+ try {
+ backgroundResources.close();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to close resource", e);
+ }
+ }
+
+ @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/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java b/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java
index 25f6b1a50c..79d37baf4f 100644
--- a/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java
+++ b/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java
@@ -41,6 +41,8 @@
import com.google.showcase.v1beta1.MessagingOuterClass;
import com.google.showcase.v1beta1.TestingOuterClass;
import com.google.testdata.v1.DeprecatedServiceOuterClass;
+import com.google.testgapic.v1beta1.NestedMessageProto;
+import com.google.types.testing.TypesTestingProto;
import google.cloud.CommonResources;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -131,6 +133,30 @@ public GapicContext parseBookshopService() {
.build();
}
+ public GapicContext parseNestedMessage() {
+ FileDescriptor fileDescriptor = TypesTestingProto.getDescriptor();
+ ServiceDescriptor serviceDescriptor = fileDescriptor.getServices().get(0);
+ assertEquals(serviceDescriptor.getName(), "NestedMessageService");
+ Map messageTypes = Parser.parseMessages(fileDescriptor);
+
+ FileDescriptor messageFileDescriptor = NestedMessageProto.getDescriptor();
+ messageTypes.putAll(Parser.parseMessages(messageFileDescriptor));
+
+ Map resourceNames = new HashMap<>();
+ Set outputResourceNames = new HashSet<>();
+ List services =
+ Parser.parseService(
+ fileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames);
+
+ return GapicContext.builder()
+ .setMessages(messageTypes)
+ .setResourceNames(resourceNames)
+ .setServices(services)
+ .setHelperResourceNames(outputResourceNames)
+ .setTransport(transport)
+ .build();
+ }
+
public GapicContext parseShowcaseEcho() {
FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor();
ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0);
diff --git a/gapic-generator-java/src/test/proto/types_testing.proto b/gapic-generator-java/src/test/proto/types_testing.proto
new file mode 100644
index 0000000000..40ba28c4ec
--- /dev/null
+++ b/gapic-generator-java/src/test/proto/types_testing.proto
@@ -0,0 +1,32 @@
+// Copyright 2023 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/client.proto";
+import "nested_message.proto";
+
+package google.types.testing;
+
+option java_package = "com.google.types.testing";
+option java_multiple_files = true;
+option java_outer_classname = "TypesTestingProto";
+
+// A service to test handling of deeply-nested types
+service NestedMessageService {
+ option (google.api.default_host) = "localhost:7469";
+ // Testing with nested message from a different package to require imports in generated code
+ rpc NestedMessageMethod(google.testgapic.Outer.Middle) returns (google.testgapic.Outer.Middle.Inner);
+}
+
diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties
index 097522b7d5..34b87afea2 100644
--- a/gax-java/dependencies.properties
+++ b/gax-java/dependencies.properties
@@ -8,22 +8,22 @@
# Versions of oneself
# {x-version-update-start:gax:current}
-version.gax=2.28.1
+version.gax=2.29.0
# {x-version-update-end}
# {x-version-update-start:gax:current}
-version.gax_grpc=2.28.1
+version.gax_grpc=2.29.0
# {x-version-update-end}
# {x-version-update-start:gax:current}
-version.gax_bom=2.28.1
+version.gax_bom=2.29.0
# {x-version-update-end}
# {x-version-update-start:gax-httpjson:current}
-version.gax_httpjson=0.113.1
+version.gax_httpjson=0.114.0
# {x-version-update-end}
# Versions for dependencies which actual artifacts differ between Bazel and Gradle.
# Gradle build depends on prebuilt maven artifacts, while Bazel build depends on Bazel workspaces
# with the sources.
-version.com_google_protobuf=3.21.12
+version.com_google_protobuf=3.23.2
version.google_java_format=1.15.0
version.io_grpc=1.54.0
@@ -34,8 +34,8 @@ version.io_grpc=1.54.0
# 2) Replace all characters which are neither alphabetic nor digits with the underscore ('_') character
maven.com_google_api_grpc_proto_google_common_protos=com.google.api.grpc:proto-google-common-protos:2.15.0
maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-google-common-protos:2.15.0
-maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.16.0
-maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.16.0
+maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.17.0
+maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.17.0
maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1
maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1
maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1
diff --git a/gax-java/gax-bom/pom.xml b/gax-java/gax-bom/pom.xml
index 7fccc6fb10..c294ad79d4 100644
--- a/gax-java/gax-bom/pom.xml
+++ b/gax-java/gax-bom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.api
gax-bom
- 2.28.1
+ 2.29.0
pom
GAX (Google Api eXtensions) for Java (BOM)
Google Api eXtensions for Java (BOM)
@@ -43,55 +43,55 @@
com.google.api
gax
- 2.28.1
+ 2.29.0
com.google.api
gax
- 2.28.1
+ 2.29.0
test-jar
testlib
com.google.api
gax
- 2.28.1
+ 2.29.0
testlib
com.google.api
gax-grpc
- 2.28.1
+ 2.29.0
com.google.api
gax-grpc
- 2.28.1
+ 2.29.0
test-jar
testlib
com.google.api
gax-grpc
- 2.28.1
+ 2.29.0
testlib
com.google.api
gax-httpjson
- 0.113.1
+ 0.114.0
com.google.api
gax-httpjson
- 0.113.1
+ 0.114.0
test-jar
testlib
com.google.api
gax-httpjson
- 0.113.1
+ 0.114.0
testlib
diff --git a/gax-java/gax-grpc/pom.xml b/gax-java/gax-grpc/pom.xml
index cdd93ae1bd..b1d2472302 100644
--- a/gax-java/gax-grpc/pom.xml
+++ b/gax-java/gax-grpc/pom.xml
@@ -3,7 +3,7 @@
4.0.0
gax-grpc
- 2.28.1
+ 2.29.0
jar
GAX (Google Api eXtensions) for Java (gRPC)
Google Api eXtensions for Java (gRPC)
@@ -11,7 +11,7 @@
com.google.api
gax-parent
- 2.28.1
+ 2.29.0
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/CallOptionsUtil.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/CallOptionsUtil.java
index f36da0d4d9..9d6e85bdaa 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/CallOptionsUtil.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/CallOptionsUtil.java
@@ -29,6 +29,7 @@
*/
package com.google.api.gax.grpc;
+import com.google.api.gax.rpc.internal.Headers;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import io.grpc.CallOptions;
@@ -45,7 +46,7 @@ class CallOptionsUtil {
"gax_dynamic_headers", Collections., String>emptyMap());
// this is the header name, it is transferred over the wire
static Metadata.Key REQUEST_PARAMS_HEADER_KEY =
- Metadata.Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER);
+ Metadata.Key.of(Headers.DYNAMIC_ROUTING_HEADER_KEY, Metadata.ASCII_STRING_MARSHALLER);
private static final CallOptions.Key METADATA_HANDLER_CALL_OPTION_KEY =
CallOptions.Key.createWithDefault("gax_metadata_handler", null);
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcServerStreamingRequestParamCallable.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcServerStreamingRequestParamCallable.java
index 5e0a59851e..27b4c56454 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcServerStreamingRequestParamCallable.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcServerStreamingRequestParamCallable.java
@@ -51,8 +51,7 @@ class GrpcServerStreamingRequestParamCallable
ServerStreamingCallable callable,
RequestParamsExtractor paramsExtractor) {
this.callable = Preconditions.checkNotNull(callable);
- this.paramsEncoder =
- new RequestUrlParamsEncoder<>(Preconditions.checkNotNull(paramsExtractor), false);
+ this.paramsEncoder = new RequestUrlParamsEncoder<>(Preconditions.checkNotNull(paramsExtractor));
}
@Override
@@ -62,8 +61,14 @@ public void call(
}
private ApiCallContext contextWithParamsEncoder(RequestT request, ApiCallContext inputContext) {
- return GrpcCallContext.createDefault()
- .nullToSelf(inputContext)
- .withRequestParamsDynamicHeaderOption(paramsEncoder.encode(request));
+ ApiCallContext newCallContext = inputContext;
+ String encodedHeader = paramsEncoder.encode(request);
+ if (!encodedHeader.isEmpty()) {
+ newCallContext =
+ GrpcCallContext.createDefault()
+ .nullToSelf(inputContext)
+ .withRequestParamsDynamicHeaderOption(encodedHeader);
+ }
+ return newCallContext;
}
}
diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcUnaryRequestParamCallable.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcUnaryRequestParamCallable.java
index 9eb78aca0d..22bbd8409b 100644
--- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcUnaryRequestParamCallable.java
+++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcUnaryRequestParamCallable.java
@@ -51,16 +51,19 @@ class GrpcUnaryRequestParamCallable
UnaryCallable callable,
RequestParamsExtractor paramsExtractor) {
this.callable = Preconditions.checkNotNull(callable);
- this.paramsEncoder =
- new RequestUrlParamsEncoder<>(Preconditions.checkNotNull(paramsExtractor), false);
+ this.paramsEncoder = new RequestUrlParamsEncoder<>(Preconditions.checkNotNull(paramsExtractor));
}
@Override
public ApiFuture futureCall(RequestT request, ApiCallContext inputContext) {
- GrpcCallContext newCallContext =
- GrpcCallContext.createDefault()
- .nullToSelf(inputContext)
- .withRequestParamsDynamicHeaderOption(paramsEncoder.encode(request));
+ ApiCallContext newCallContext = inputContext;
+ String encodedHeader = paramsEncoder.encode(request);
+ if (!encodedHeader.isEmpty()) {
+ newCallContext =
+ GrpcCallContext.createDefault()
+ .nullToSelf(inputContext)
+ .withRequestParamsDynamicHeaderOption(encodedHeader);
+ }
return callable.futureCall(request, newCallContext);
}
diff --git a/gax-java/gax-httpjson/pom.xml b/gax-java/gax-httpjson/pom.xml
index d9fc938995..a1cc6d7ced 100644
--- a/gax-java/gax-httpjson/pom.xml
+++ b/gax-java/gax-httpjson/pom.xml
@@ -3,7 +3,7 @@
4.0.0
gax-httpjson
- 0.113.1
+ 0.114.0
jar
GAX (Google Api eXtensions) for Java (HTTP JSON)
Google Api eXtensions for Java (HTTP JSON)
@@ -11,7 +11,7 @@
com.google.api
gax-parent
- 2.28.1
+ 2.29.0
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java
index ac013ed74a..7dd7732175 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java
@@ -29,23 +29,29 @@
*/
package com.google.api.gax.httpjson;
+import com.google.api.gax.rpc.RequestParamsExtractor;
import com.google.protobuf.TypeRegistry;
/** HTTP-specific settings for creating callables. */
public class HttpJsonCallSettings {
private final ApiMethodDescriptor methodDescriptor;
+ private final RequestParamsExtractor paramsExtractor;
private final TypeRegistry typeRegistry;
- private HttpJsonCallSettings(
- ApiMethodDescriptor methodDescriptor, TypeRegistry typeRegistry) {
- this.methodDescriptor = methodDescriptor;
- this.typeRegistry = typeRegistry;
+ private HttpJsonCallSettings(Builder builder) {
+ this.methodDescriptor = builder.methodDescriptor;
+ this.paramsExtractor = builder.paramsExtractor;
+ this.typeRegistry = builder.typeRegistry;
}
public ApiMethodDescriptor getMethodDescriptor() {
return methodDescriptor;
}
+ public RequestParamsExtractor getParamsExtractor() {
+ return paramsExtractor;
+ }
+
public TypeRegistry getTypeRegistry() {
return typeRegistry;
}
@@ -67,6 +73,7 @@ public Builder toBuilder() {
public static class Builder {
private ApiMethodDescriptor methodDescriptor;
+ private RequestParamsExtractor paramsExtractor;
private TypeRegistry typeRegistry;
private Builder() {}
@@ -81,13 +88,19 @@ public Builder setMethodDescriptor(
return this;
}
+ public Builder setParamsExtractor(
+ RequestParamsExtractor paramsExtractor) {
+ this.paramsExtractor = paramsExtractor;
+ return this;
+ }
+
public Builder setTypeRegistry(TypeRegistry typeRegistry) {
this.typeRegistry = typeRegistry;
return this;
}
public HttpJsonCallSettings build() {
- return new HttpJsonCallSettings<>(methodDescriptor, typeRegistry);
+ return new HttpJsonCallSettings<>(this);
}
}
}
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java
index 6131c911f8..d95751e3b0 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java
@@ -60,8 +60,16 @@ private HttpJsonCallableFactory() {}
private static UnaryCallable createDirectUnaryCallable(
HttpJsonCallSettings httpJsonCallSettings) {
- return new HttpJsonDirectCallable(
- httpJsonCallSettings.getMethodDescriptor(), httpJsonCallSettings.getTypeRegistry());
+ UnaryCallable callable =
+ new HttpJsonDirectCallable<>(
+ httpJsonCallSettings.getMethodDescriptor(), httpJsonCallSettings.getTypeRegistry());
+
+ if (httpJsonCallSettings.getParamsExtractor() != null) {
+ callable =
+ new HttpJsonUnaryRequestParamCallable<>(
+ callable, httpJsonCallSettings.getParamsExtractor());
+ }
+ return callable;
}
static UnaryCallable createUnaryCallable(
@@ -180,6 +188,12 @@ ServerStreamingCallable createServerStreamingCallable(
ServerStreamingCallable callable =
new HttpJsonDirectServerStreamingCallable<>(httpJsoncallSettings.getMethodDescriptor());
+ if (httpJsoncallSettings.getParamsExtractor() != null) {
+ callable =
+ new HttpJsonServerStreamingRequestParamCallable<>(
+ callable, httpJsoncallSettings.getParamsExtractor());
+ }
+
callable =
new HttpJsonExceptionServerStreamingCallable<>(
callable, streamingCallSettings.getRetryableCodes());
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java
index c0b9719574..ae1ae3ca84 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java
@@ -37,7 +37,7 @@
import org.threeten.bp.Duration;
/**
- * {@code HttpJsonClientCalls} creates a new {@code HttpJsonClientCAll} from the given call context.
+ * {@code HttpJsonClientCalls} creates a new {@code HttpJsonClientCall} from the given call context.
*
* Package-private for internal use.
*/
@@ -68,8 +68,8 @@ public static HttpJsonClientCall newC
.toBuilder()
.setTimeout(java.time.Duration.ofMillis(httpJsonContext.getTimeout().toMillis()))
.build();
- httpJsonContext = httpJsonContext.withCallOptions(callOptions);
}
+ httpJsonContext = httpJsonContext.withCallOptions(callOptions);
}
// TODO: add headers interceptor logic
@@ -77,10 +77,14 @@ public static HttpJsonClientCall newC
}
static ApiFuture futureUnaryCall(
- HttpJsonClientCall clientCall, RequestT request) {
+ HttpJsonClientCall clientCall,
+ RequestT request,
+ HttpJsonCallContext context) {
// Start the call
HttpJsonFuture future = new HttpJsonFuture<>(clientCall);
- clientCall.start(new FutureListener<>(future), HttpJsonMetadata.newBuilder().build());
+ clientCall.start(
+ new FutureListener<>(future),
+ HttpJsonMetadata.newBuilder().build().withHeaders(context.getExtraHeaders()));
// Send the request
try {
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java
index c631ca60c8..abf3290067 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectCallable.java
@@ -65,7 +65,7 @@ public ApiFuture futureCall(RequestT request, ApiCallContext inputCon
HttpJsonClientCall clientCall =
HttpJsonClientCalls.newCall(descriptor, context);
- return HttpJsonClientCalls.futureUnaryCall(clientCall, request);
+ return HttpJsonClientCalls.futureUnaryCall(clientCall, request, context);
}
@Override
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectServerStreamingCallable.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectServerStreamingCallable.java
index ed3bebde31..2f98ac177e 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectServerStreamingCallable.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectServerStreamingCallable.java
@@ -64,6 +64,6 @@ public void call(
HttpJsonClientCall call = HttpJsonClientCalls.newCall(descriptor, context);
HttpJsonDirectStreamController controller =
new HttpJsonDirectStreamController<>(call, responseObserver);
- controller.start(request);
+ controller.start(request, context);
}
}
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectStreamController.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectStreamController.java
index 5f56390f04..9e1b23b257 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectStreamController.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonDirectStreamController.java
@@ -29,6 +29,7 @@
*/
package com.google.api.gax.httpjson;
+import com.google.api.gax.rpc.ApiCallContext;
import com.google.api.gax.rpc.ResponseObserver;
import com.google.api.gax.rpc.StreamController;
import com.google.common.base.Preconditions;
@@ -80,10 +81,12 @@ public void request(int count) {
}
}
- void start(RequestT request) {
+ void start(RequestT request, ApiCallContext context) {
responseObserver.onStart(this);
this.hasStarted = true;
- clientCall.start(new ResponseObserverAdapter(), HttpJsonMetadata.newBuilder().build());
+ clientCall.start(
+ new ResponseObserverAdapter(),
+ HttpJsonMetadata.newBuilder().build().withHeaders(context.getExtraHeaders()));
if (autoflowControl) {
clientCall.request(1);
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonMetadata.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonMetadata.java
index 9fef6db5c5..7985396075 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonMetadata.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonMetadata.java
@@ -33,6 +33,8 @@
import com.google.api.core.InternalExtensionOnly;
import com.google.auto.value.AutoValue;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -54,6 +56,22 @@ public static HttpJsonMetadata.Builder newBuilder() {
return new AutoValue_HttpJsonMetadata.Builder().setHeaders(Collections.emptyMap());
}
+ public HttpJsonMetadata withHeaders(Map> headers) {
+ Map extraHeaders = new HashMap<>();
+ for (Map.Entry> entrySet : headers.entrySet()) {
+ // HeaderValueList is always non-null. Check that it contains at least one value.
+ // Should only ever contain one value, but take the first one if there are multiple.
+ // TODO(https://github.com/googleapis/sdk-platform-java/issues/1752):
+ // Investigate how to better support extraHeaders
+ List headerValueList = entrySet.getValue();
+ if (headerValueList.isEmpty()) {
+ continue;
+ }
+ extraHeaders.put(entrySet.getKey(), headerValueList.get(0));
+ }
+ return toBuilder().setHeaders(extraHeaders).build();
+ }
+
@AutoValue.Builder
abstract static class Builder {
abstract Builder setHeaders(Map headers);
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonServerStreamingRequestParamCallable.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonServerStreamingRequestParamCallable.java
new file mode 100644
index 0000000000..7b1d615961
--- /dev/null
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonServerStreamingRequestParamCallable.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.httpjson;
+
+import com.google.api.gax.rpc.ApiCallContext;
+import com.google.api.gax.rpc.RequestParamsExtractor;
+import com.google.api.gax.rpc.RequestUrlParamsEncoder;
+import com.google.api.gax.rpc.ResponseObserver;
+import com.google.api.gax.rpc.ServerStreamingCallable;
+import com.google.api.gax.rpc.internal.Headers;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class HttpJsonServerStreamingRequestParamCallable
+ extends ServerStreamingCallable {
+ private final ServerStreamingCallable callable;
+ private final RequestUrlParamsEncoder paramsEncoder;
+
+ HttpJsonServerStreamingRequestParamCallable(
+ ServerStreamingCallable callable,
+ RequestParamsExtractor paramsExtractor) {
+ this.callable = Preconditions.checkNotNull(callable);
+ this.paramsEncoder = new RequestUrlParamsEncoder<>(Preconditions.checkNotNull(paramsExtractor));
+ }
+
+ @Override
+ public void call(
+ RequestT request, ResponseObserver responseObserver, ApiCallContext context) {
+ ApiCallContext newCallContext = context;
+ String encodedHeader = paramsEncoder.encode(request);
+ if (!encodedHeader.isEmpty()) {
+ newCallContext =
+ HttpJsonCallContext.createDefault()
+ .nullToSelf(context)
+ .withExtraHeaders(
+ ImmutableMap.of(
+ Headers.DYNAMIC_ROUTING_HEADER_KEY, ImmutableList.of(encodedHeader)));
+ }
+ callable.call(request, responseObserver, newCallContext);
+ }
+}
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonUnaryRequestParamCallable.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonUnaryRequestParamCallable.java
new file mode 100644
index 0000000000..3770872e87
--- /dev/null
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonUnaryRequestParamCallable.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.httpjson;
+
+import com.google.api.core.ApiFuture;
+import com.google.api.gax.rpc.ApiCallContext;
+import com.google.api.gax.rpc.RequestParamsExtractor;
+import com.google.api.gax.rpc.RequestUrlParamsEncoder;
+import com.google.api.gax.rpc.UnaryCallable;
+import com.google.api.gax.rpc.internal.Headers;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * A {@code UnaryCallable} that extracts values from the fields of the request and inserts them into
+ * headers.
+ *
+ * Package-private for internal usage.
+ */
+class HttpJsonUnaryRequestParamCallable
+ extends UnaryCallable {
+ private final UnaryCallable callable;
+ private final RequestUrlParamsEncoder paramsEncoder;
+
+ public HttpJsonUnaryRequestParamCallable(
+ UnaryCallable callable,
+ RequestParamsExtractor paramsExtractor) {
+ this.callable = Preconditions.checkNotNull(callable);
+ this.paramsEncoder = new RequestUrlParamsEncoder<>(Preconditions.checkNotNull(paramsExtractor));
+ }
+
+ @Override
+ public ApiFuture futureCall(RequestT request, ApiCallContext context) {
+ ApiCallContext newCallContext = context;
+ String encodedHeader = paramsEncoder.encode(request);
+ if (!encodedHeader.isEmpty()) {
+ newCallContext =
+ HttpJsonCallContext.createDefault()
+ .nullToSelf(context)
+ .withExtraHeaders(
+ ImmutableMap.of(
+ Headers.DYNAMIC_ROUTING_HEADER_KEY, ImmutableList.of(encodedHeader)));
+ }
+ return callable.futureCall(request, newCallContext);
+ }
+}
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java
index 57b0ff1284..c9dd85442e 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java
@@ -186,6 +186,8 @@ private HttpJsonTransportChannel createChannel() throws IOException, GeneralSecu
httpTransportToUse = createHttpTransport();
}
+ // Pass the executor to the ManagedChannel. If no executor was provided (or null),
+ // the channel will use a default executor for the calls.
ManagedHttpJsonChannel channel =
ManagedHttpJsonChannel.newBuilder()
.setEndpoint(endpoint)
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java
index e6fe38d5ef..22e333752a 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java
@@ -34,6 +34,7 @@
import com.google.api.core.BetaApi;
import com.google.api.gax.core.BackgroundResource;
import com.google.api.gax.core.InstantiatingExecutorProvider;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.concurrent.Executor;
@@ -47,23 +48,24 @@
@BetaApi
public class ManagedHttpJsonChannel implements HttpJsonChannel, BackgroundResource {
- private static final ExecutorService DEFAULT_EXECUTOR =
- InstantiatingExecutorProvider.newBuilder().build().getExecutor();
-
private final Executor executor;
+ private final boolean usingDefaultExecutor;
private final String endpoint;
private final HttpTransport httpTransport;
private final ScheduledExecutorService deadlineScheduledExecutorService;
-
private boolean isTransportShutdown;
protected ManagedHttpJsonChannel() {
- this(null, null, null);
+ this(null, true, null, null);
}
private ManagedHttpJsonChannel(
- Executor executor, String endpoint, @Nullable HttpTransport httpTransport) {
+ Executor executor,
+ boolean usingDefaultExecutor,
+ String endpoint,
+ @Nullable HttpTransport httpTransport) {
this.executor = executor;
+ this.usingDefaultExecutor = usingDefaultExecutor;
this.endpoint = endpoint;
this.httpTransport = httpTransport == null ? new NetHttpTransport() : httpTransport;
this.deadlineScheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
@@ -82,46 +84,105 @@ public HttpJsonClientCall newCall(
deadlineScheduledExecutorService);
}
+ @VisibleForTesting
+ Executor getExecutor() {
+ return executor;
+ }
+
@Override
public synchronized void shutdown() {
+ // Calling shutdown/ shutdownNow() twice should no-op
if (isTransportShutdown) {
return;
}
try {
+ // Only shutdown the executor if it was created by Gax. External executors
+ // should be managed by the user.
+ if (shouldManageExecutor()) {
+ ((ExecutorService) executor).shutdown();
+ }
deadlineScheduledExecutorService.shutdown();
httpTransport.shutdown();
isTransportShutdown = true;
} catch (IOException e) {
- e.printStackTrace();
+ // TODO: Log this scenario once we implemented the Cloud SDK logging.
+ // Swallow error if httpTransport shutdown fails
}
}
@Override
public boolean isShutdown() {
- return isTransportShutdown;
+ // TODO(lawrenceqiu): Expose an isShutdown() method for HttpTransport
+ boolean isShutdown = isTransportShutdown && deadlineScheduledExecutorService.isShutdown();
+ // Check that the Gax's ExecutorService is shutdown as well
+ if (shouldManageExecutor()) {
+ isShutdown = isShutdown && ((ExecutorService) executor).isShutdown();
+ }
+ return isShutdown;
}
@Override
public boolean isTerminated() {
- return isTransportShutdown;
+ boolean isTerminated = deadlineScheduledExecutorService.isTerminated();
+ // Check that the Gax's ExecutorService is terminated as well
+ if (shouldManageExecutor()) {
+ isTerminated = isTerminated && ((ExecutorService) executor).isTerminated();
+ }
+ return isTerminated;
}
@Override
public void shutdownNow() {
- shutdown();
+ // Calling shutdown/ shutdownNow() twice should no-op
+ if (isTransportShutdown) {
+ return;
+ }
+ try {
+ // Only shutdown the executor if it was created by Gax. External executors
+ // should be managed by the user.
+ if (shouldManageExecutor()) {
+ ((ExecutorService) executor).shutdownNow();
+ }
+ deadlineScheduledExecutorService.shutdownNow();
+ httpTransport.shutdown();
+ isTransportShutdown = true;
+ } catch (IOException e) {
+ // TODO: Log this scenario once we implemented the Cloud SDK logging.
+ // Swallow error if httpTransport shutdown fails
+ }
}
@Override
public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException {
- // TODO
- return false;
+ long endTimeNanos = System.nanoTime() + unit.toNanos(duration);
+ long awaitTimeNanos = endTimeNanos - System.nanoTime();
+ if (awaitTimeNanos <= 0) {
+ return false;
+ }
+ // Only awaitTermination for the executor if it was created by Gax. External executors
+ // should be managed by the user.
+ if (usingDefaultExecutor && executor instanceof ExecutorService) {
+ boolean terminated = ((ExecutorService) executor).awaitTermination(awaitTimeNanos, unit);
+ // Termination duration has elapsed
+ if (!terminated) {
+ return false;
+ }
+ }
+ awaitTimeNanos = endTimeNanos - System.nanoTime();
+ return deadlineScheduledExecutorService.awaitTermination(awaitTimeNanos, unit);
+ }
+
+ private boolean shouldManageExecutor() {
+ return usingDefaultExecutor && executor instanceof ExecutorService;
}
@Override
- public void close() {}
+ public void close() {
+ shutdown();
+ }
public static Builder newBuilder() {
- return new Builder().setExecutor(DEFAULT_EXECUTOR);
+ return new Builder();
}
public static class Builder {
@@ -129,11 +190,14 @@ public static class Builder {
private Executor executor;
private String endpoint;
private HttpTransport httpTransport;
+ private boolean usingDefaultExecutor;
- private Builder() {}
+ private Builder() {
+ this.usingDefaultExecutor = false;
+ }
public Builder setExecutor(Executor executor) {
- this.executor = executor == null ? DEFAULT_EXECUTOR : executor;
+ this.executor = executor;
return this;
}
@@ -150,8 +214,20 @@ public Builder setHttpTransport(HttpTransport httpTransport) {
public ManagedHttpJsonChannel build() {
Preconditions.checkNotNull(endpoint);
+ // If the executor provided for this channel is null, gax will provide a
+ // default executor to used for the calls. Only the default executor's
+ // lifecycle will be managed by the channel. Any external executor needs to
+ // managed by the user.
+ if (executor == null) {
+ executor = InstantiatingExecutorProvider.newIOBuilder().build().getExecutor();
+ usingDefaultExecutor = true;
+ }
+
return new ManagedHttpJsonChannel(
- executor, endpoint, httpTransport == null ? new NetHttpTransport() : httpTransport);
+ executor,
+ usingDefaultExecutor,
+ endpoint,
+ httpTransport == null ? new NetHttpTransport() : httpTransport);
}
}
}
diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonInterceptorChannel.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonInterceptorChannel.java
index 05321e2fd7..56ab5a5abb 100644
--- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonInterceptorChannel.java
+++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonInterceptorChannel.java
@@ -30,6 +30,7 @@
package com.google.api.gax.httpjson;
import com.google.api.core.BetaApi;
+import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
@BetaApi
@@ -45,6 +46,11 @@ class ManagedHttpJsonInterceptorChannel extends ManagedHttpJsonChannel {
this.interceptor = interceptor;
}
+ @VisibleForTesting
+ ManagedHttpJsonChannel getChannel() {
+ return channel;
+ }
+
@Override
public HttpJsonClientCall newCall(
ApiMethodDescriptor methodDescriptor, HttpJsonCallOptions callOptions) {
diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java
index 4061ff8bad..416fcfb2da 100644
--- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java
+++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java
@@ -39,6 +39,7 @@
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;
+import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -50,20 +51,22 @@
@RunWith(JUnit4.class)
public class InstantiatingHttpJsonChannelProviderTest extends AbstractMtlsTransportChannelTest {
+ private static final String DEFAULT_ENDPOINT = "localhost:8080";
+ private static final Map DEFAULT_HEADER_MAP = Collections.emptyMap();
+
@Test
public void basicTest() throws IOException {
- String endpoint = "localhost:8080";
ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
executor.shutdown();
TransportChannelProvider provider = InstantiatingHttpJsonChannelProvider.newBuilder().build();
assertThat(provider.needsEndpoint()).isTrue();
- provider = provider.withEndpoint(endpoint);
+ provider = provider.withEndpoint(DEFAULT_ENDPOINT);
assertThat(provider.needsEndpoint()).isFalse();
assertThat(provider.needsHeaders()).isTrue();
- provider = provider.withHeaders(Collections.emptyMap());
+ provider = provider.withHeaders(DEFAULT_HEADER_MAP);
assertThat(provider.needsHeaders()).isFalse();
// Make sure getTransportChannel works without setting executor
@@ -103,6 +106,57 @@ public void basicTest() throws IOException {
provider.getTransportChannel().shutdownNow();
}
+ // Ensure that a default executor is created by the ManagedHttpJsonChannel even
+ // if not provided by the TransportChannelProvider
+ @Test
+ public void managedChannelUsesDefaultChannelExecutor() throws IOException {
+ InstantiatingHttpJsonChannelProvider instantiatingHttpJsonChannelProvider =
+ InstantiatingHttpJsonChannelProvider.newBuilder().setEndpoint(DEFAULT_ENDPOINT).build();
+ instantiatingHttpJsonChannelProvider =
+ (InstantiatingHttpJsonChannelProvider)
+ instantiatingHttpJsonChannelProvider.withHeaders(DEFAULT_HEADER_MAP);
+ HttpJsonTransportChannel httpJsonTransportChannel =
+ instantiatingHttpJsonChannelProvider.getTransportChannel();
+
+ // By default, the channel will be wrapped with ManagedHttpJsonInterceptorChannel
+ ManagedHttpJsonInterceptorChannel interceptorChannel =
+ (ManagedHttpJsonInterceptorChannel) httpJsonTransportChannel.getManagedChannel();
+ ManagedHttpJsonChannel managedHttpJsonChannel = interceptorChannel.getChannel();
+ assertThat(managedHttpJsonChannel.getExecutor()).isNotNull();
+
+ // Clean up the resources (executor, deadlineScheduler, httpTransport)
+ instantiatingHttpJsonChannelProvider.getTransportChannel().shutdownNow();
+ }
+
+ // Ensure that the user's executor is used by the ManagedHttpJsonChannel
+ @Test
+ public void managedChannelUsesCustomExecutor() throws IOException {
+ // Custom executor to use -- Lifecycle must be managed by this test
+ ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
+ executor.shutdown();
+
+ InstantiatingHttpJsonChannelProvider instantiatingHttpJsonChannelProvider =
+ InstantiatingHttpJsonChannelProvider.newBuilder()
+ .setEndpoint(DEFAULT_ENDPOINT)
+ .setExecutor(executor)
+ .build();
+ instantiatingHttpJsonChannelProvider =
+ (InstantiatingHttpJsonChannelProvider)
+ instantiatingHttpJsonChannelProvider.withHeaders(DEFAULT_HEADER_MAP);
+ HttpJsonTransportChannel httpJsonTransportChannel =
+ instantiatingHttpJsonChannelProvider.getTransportChannel();
+
+ // By default, the channel will be wrapped with ManagedHttpJsonInterceptorChannel
+ ManagedHttpJsonInterceptorChannel interceptorChannel =
+ (ManagedHttpJsonInterceptorChannel) httpJsonTransportChannel.getManagedChannel();
+ ManagedHttpJsonChannel managedHttpJsonChannel = interceptorChannel.getChannel();
+ assertThat(managedHttpJsonChannel.getExecutor()).isNotNull();
+ assertThat(managedHttpJsonChannel.getExecutor()).isEqualTo(executor);
+
+ // Clean up the resources (executor, deadlineScheduler, httpTransport)
+ instantiatingHttpJsonChannelProvider.getTransportChannel().shutdownNow();
+ }
+
@Override
protected Object getMtlsObjectFromTransportChannel(MtlsProvider provider)
throws IOException, GeneralSecurityException {
diff --git a/gax-java/gax/clirr-ignored-differences.xml b/gax-java/gax/clirr-ignored-differences.xml
index dcc0be350d..a0516a55d4 100644
--- a/gax-java/gax/clirr-ignored-differences.xml
+++ b/gax-java/gax/clirr-ignored-differences.xml
@@ -7,4 +7,10 @@
com/google/api/gax/paging/Page
* stream*(*)
+
+ 7006
+ com/google/api/gax/rpc/ServerStreamingCallSettings$Builder
+ *setWaitTimeout*
+ com.google.api.gax.rpc.ServerStreamingCallSettings$Builder
+
diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml
index 147435e11a..c367afcec5 100644
--- a/gax-java/gax/pom.xml
+++ b/gax-java/gax/pom.xml
@@ -3,7 +3,7 @@
4.0.0
gax
- 2.28.1
+ 2.29.0
jar
GAX (Google Api eXtensions) for Java (Core)
Google Api eXtensions for Java (Core)
@@ -11,7 +11,7 @@
com.google.api
gax-parent
- 2.28.1
+ 2.29.0
@@ -81,6 +81,16 @@
+
+ org.codehaus.mojo
+ clirr-maven-plugin
+
+
+
+ com/google/api/gax/rpc/RequestUrlParamsEncoder
+
+
+
\ No newline at end of file
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/core/InstantiatingExecutorProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/core/InstantiatingExecutorProvider.java
index c91b044cb9..1faee91a4a 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/core/InstantiatingExecutorProvider.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/core/InstantiatingExecutorProvider.java
@@ -54,6 +54,9 @@ public Thread newThread(Runnable runnable) {
return thread;
}
};
+ private static final int MIN_THREAD_AMOUNT = 4;
+ // Attempt to choose a reasonable default core pool multiplier for IO Bound operations
+ private static final int IO_THREAD_MULTIPLIER = 50;
// Package-private constructor prevents others from subclassing.
InstantiatingExecutorProvider() {}
@@ -76,9 +79,22 @@ public boolean shouldAutoClose() {
public abstract Builder toBuilder();
+ // Used for CPU Bound tasks as the thread count is at max the number of processors
+ // Thread count minimum is at least `MIN_CPU_AMOUNT`
public static Builder newBuilder() {
int numCpus = Runtime.getRuntime().availableProcessors();
- int numThreads = Math.max(4, numCpus);
+ int numThreads = Math.max(MIN_THREAD_AMOUNT, numCpus);
+
+ return new AutoValue_InstantiatingExecutorProvider.Builder()
+ .setExecutorThreadCount(numThreads)
+ .setThreadFactory(DEFAULT_THREAD_FACTORY);
+ }
+
+ // Used for IO Bound tasks as the thread count scales with the number of processors
+ // Thread count minimum is at least `MIN_CPU_AMOUNT` * `IO_THREAD_MULTIPLIER`
+ public static Builder newIOBuilder() {
+ int numCpus = Runtime.getRuntime().availableProcessors();
+ int numThreads = IO_THREAD_MULTIPLIER * Math.max(MIN_THREAD_AMOUNT, numCpus);
return new AutoValue_InstantiatingExecutorProvider.Builder()
.setExecutorThreadCount(numThreads)
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestParamsBuilder.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestParamsBuilder.java
index 4f234f0ce3..9e3914309c 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestParamsBuilder.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestParamsBuilder.java
@@ -40,7 +40,6 @@
*/
@BetaApi
public class RequestParamsBuilder {
-
private final ImmutableMap.Builder paramsBuilder;
private RequestParamsBuilder() {
@@ -63,7 +62,7 @@ public static RequestParamsBuilder create() {
* @param pathTemplate {@link PathTemplate} the path template used for match-and-extract
*/
public void add(String fieldValue, String headerKey, PathTemplate pathTemplate) {
- if (fieldValue == null) {
+ if (checkInvalidHeaderValues(headerKey, fieldValue)) {
return;
}
Map matchedValues = pathTemplate.match(fieldValue);
@@ -72,6 +71,25 @@ public void add(String fieldValue, String headerKey, PathTemplate pathTemplate)
}
}
+ /**
+ * Add an entry to paramsBuilder with key-value pairing of (headerKey, fieldValue). The only
+ * validation done is to ensure the headerKey and fieldValue are not null and non-empty. This
+ * method is used for implicit routing headers (backwards compatibility).
+ *
+ * @param headerKey the header key for the routing header param
+ * @param fieldValue the field value from a request
+ */
+ public void add(String headerKey, String fieldValue) {
+ if (checkInvalidHeaderValues(headerKey, fieldValue)) {
+ return;
+ }
+ paramsBuilder.put(headerKey, fieldValue);
+ }
+
+ private boolean checkInvalidHeaderValues(String headerKey, String fieldValue) {
+ return headerKey == null || headerKey.isEmpty() || fieldValue == null || fieldValue.isEmpty();
+ }
+
public Map build() {
return paramsBuilder.buildKeepingLast();
}
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestUrlParamsEncoder.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestUrlParamsEncoder.java
index 6bb1da2509..a5b50086cf 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestUrlParamsEncoder.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestUrlParamsEncoder.java
@@ -31,46 +31,40 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.api.client.util.escape.PercentEscaper;
import com.google.api.core.InternalApi;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
import java.util.Map;
/**
* The request params encoder, which encodes URL-encoded parameters in one URL parameters string.
- * This class expects that name-value pairs, returned from parameters extractor are already
- * URL-encoded and can perform optional validation of that, but does not encode the name-value pairs
- * themselves.
*
* @param request message type
*/
@InternalApi("For use by transport-specific implementations")
public class RequestUrlParamsEncoder implements RequestParamsEncoder {
- private static final String STR_ENCODING = "UTF-8";
-
+ // Per RFC 3986 Section 2.3, these are the four unreserved characters that don't need to be
+ // encoded
+ private static final PercentEscaper PERCENT_ESCAPER = new PercentEscaper("._-~");
private final RequestParamsExtractor paramsExtractor;
- private final boolean validateExtractedParameters;
/**
* Creates the encoder.
*
* @param paramsExtractor parameters extractor which returns already URL-encoded key-value pairs
- * @param validateExtractedParameters {@code true} if this class should validate that the
- * extracted parameters are URL-encoded, {@code false} otherwise
*/
- public RequestUrlParamsEncoder(
- RequestParamsExtractor paramsExtractor, boolean validateExtractedParameters) {
+ public RequestUrlParamsEncoder(RequestParamsExtractor paramsExtractor) {
this.paramsExtractor = checkNotNull(paramsExtractor);
- this.validateExtractedParameters = validateExtractedParameters;
}
/**
* Encodes the {@code request} in a form of a URL parameters string, for example {@code
* "param1=value+1¶m2=value2%26"}. This method may optionally validate that the name-value
- * paris are URL-encoded, but it will not perform the actual encoding of them (it will only
- * concatenate the valid individual name-value pairs in a valid URL parameters string). This is
- * so, because in most practical cases the name-value paris are already URL-encoded.
+ * paris are URL-encoded. It will URL encode the key and values if there are any non-allowed
+ * characters. It will then concatenate the valid individual name-value pairs in a valid URL
+ * parameters string.
+ *
+ * Note: This will url-encode the key and values during concatenation. Double url-encoding may
+ * occur if the input has values that are already url-encoded.
*
* @param request request message
* @throws IllegalArgumentException if is not
@@ -86,37 +80,17 @@ public String encode(RequestT request) {
if (sb.length() > 0) {
sb.append("&");
}
- String name = entry.getKey();
- String value = entry.getValue();
- if (name == null) {
- throw new IllegalArgumentException("Request parameter name cannot be null");
- }
-
- // Let the server decide if the value is required.
- // Empty value is allowed.
- if (value != null) {
- if (!isValid(name, value)) {
- throw new IllegalArgumentException(
- "Invalid url-encoded request parameter name-value pair: " + name + "=" + value);
- }
- sb.append(name).append("=").append(value);
- }
+ // RequestParamsExtractor checks that the values are non-null and non-empty
+ String encodedKey = percentEncodeString(entry.getKey());
+ String encodedValue = percentEncodeString(entry.getValue());
+ sb.append(encodedKey).append("=").append(encodedValue);
}
return sb.toString();
}
- // Not sure if we need this at all.
- private boolean isValid(String name, String value) {
- try {
- // hoping that encode/decode do not lose information in the middle
- // (at least for practical use cases)
- return !validateExtractedParameters
- || name.equals(URLEncoder.encode(URLDecoder.decode(name, STR_ENCODING), STR_ENCODING))
- && value.equals(
- URLEncoder.encode(URLDecoder.decode(value, STR_ENCODING), STR_ENCODING));
- } catch (UnsupportedEncodingException e) {
- return false;
- }
+ // Percent encode the value passed in.
+ private String percentEncodeString(String value) {
+ return PERCENT_ESCAPER.escape(value);
}
}
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ServerStreamingCallSettings.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ServerStreamingCallSettings.java
index da40b657b7..48e5242b80 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ServerStreamingCallSettings.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ServerStreamingCallSettings.java
@@ -299,8 +299,9 @@ public Duration getWaitTimeout() {
* Set the maximum amount of time to wait for the next message from the server. {@link
* Duration#ZERO} disables the check for abandoned streams.
*/
- public void setWaitTimeout(@Nonnull Duration waitTimeout) {
+ public Builder setWaitTimeout(@Nonnull Duration waitTimeout) {
this.waitTimeout = waitTimeout;
+ return this;
}
@Override
diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/Headers.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/Headers.java
index ddd6600362..49d4c049ab 100644
--- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/Headers.java
+++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/Headers.java
@@ -38,6 +38,8 @@
@InternalApi
public class Headers {
+ public static final String DYNAMIC_ROUTING_HEADER_KEY = "x-goog-request-params";
+
public static ImmutableMap> mergeHeaders(
Map> oldHeaders, Map> newHeaders) {
ImmutableMap.Builder> headersBuilder = ImmutableMap.builder();
diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestParamsBuilderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestParamsBuilderTest.java
index 30f4d7e628..d3a16960fd 100644
--- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestParamsBuilderTest.java
+++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestParamsBuilderTest.java
@@ -58,6 +58,29 @@ public void add_happyPath() {
assertThat(actual).containsExactly("table_location", "instances/living_room");
}
+ @Test
+ public void add_twoParams_happyPath() {
+ requestParamsBuilder.add("table_location", "instances/living_room");
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).containsExactly("table_location", "instances/living_room");
+ }
+
+ @Test
+ public void add_containsNonEncodedHeaderAndValue() {
+ PathTemplate pathTemplate = PathTemplate.create("projects/**/{table$$_++location=instances/*}");
+ requestParamsBuilder.add(
+ "projects/my_cozy_home/instances/living_room", "table$$_++location", pathTemplate);
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).containsExactly("table$$_++location", "instances/living_room");
+ }
+
+ @Test
+ public void add_twoParams_containsNonEncodedHeaderAndValue() {
+ requestParamsBuilder.add("table$$_++location", "instances/living_room");
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).containsExactly("table$$_++location", "instances/living_room");
+ }
+
@Test
public void build_shouldKeepLastEntryIfMultipleEntriesHaveTheSameKeyRatherThanErrorOut() {
requestParamsBuilder.add(
@@ -103,6 +126,50 @@ public void add_nullFieldValue() {
assertThat(actual).isEmpty();
}
+ @Test
+ public void addWithTwoParams_nullFieldValue() {
+ requestParamsBuilder.add("test", null);
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ public void add_emptyString_noMatches() {
+ // protobuf's default value for string is empty string. The `**` pathtemplate would match
+ // for empty string if it's not explicitly filtered out
+ Map actual = getRoutingHeaders("{table_location=**}", "");
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ public void addWithTwoParams_emptyString_noMatches() {
+ // protobuf's default value for string is empty string. The `**` pathtemplate would match
+ // for empty string if it's not explicitly filtered out
+ Map actual = getRoutingHeaders("{table_location=**}", "");
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ public void add_nullHeader_noMatches() {
+ requestParamsBuilder.add(null, "projects/does_not_matter");
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ public void addWithTwoParams_nullHeader_noMatches() {
+ requestParamsBuilder.add(null, "hello");
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ public void addWithTwoParams_emptyHeader_noMatches() {
+ requestParamsBuilder.add("", "hello");
+ Map actual = requestParamsBuilder.build();
+ assertThat(actual).isEmpty();
+ }
+
private Map getRoutingHeaders(String patternString, String fieldValue) {
PathTemplate pathTemplate = PathTemplate.create(patternString);
requestParamsBuilder.add(fieldValue, "table_location", pathTemplate);
diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestUrlParamsEncoderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestUrlParamsEncoderTest.java
index 3239bd148f..a4260305de 100644
--- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestUrlParamsEncoderTest.java
+++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/RequestUrlParamsEncoderTest.java
@@ -31,7 +31,6 @@
package com.google.api.gax.rpc;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
@@ -42,87 +41,50 @@
public class RequestUrlParamsEncoderTest {
- @Test
- public void testEncodeValidationSuccess() throws Exception {
- @SuppressWarnings("unchecked")
- RequestParamsExtractor extractor =
- getMockExtractor(2, ImmutableMap.of("param1", "value+1", "param2", "value+2+%26"));
-
- RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor, true);
- String encodedParams = encoder.encode(2);
+ private final String key = "key";
- assertEquals("param1=value+1¶m2=value+2+%26", encodedParams);
- }
+ @Test
+ public void testEncode() {
+ RequestParamsExtractor extractor = getMockExtractor(ImmutableMap.of("param", "value"));
+ RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor);
+ String encodedParams = encoder.encode(key);
- @Test(expected = IllegalArgumentException.class)
- public void testEncodeValidationFail() throws Exception {
- RequestParamsExtractor extractor =
- getMockExtractor(1, ImmutableMap.of("param1", "value+1", "param2", "value+2+&"));
- RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor, true);
- encoder.encode(1);
+ assertEquals("param=value", encodedParams);
}
@Test
- public void testEncodeNoValidationSuccess() throws Exception {
- RequestParamsExtractor extractor =
- getMockExtractor(1, ImmutableMap.of("param1", "value+1", "param2", "value+2+&"));
- RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor, false);
- String encodedParams = encoder.encode(1);
+ public void testEncode_multipleEntriesConcatenation() {
+ RequestParamsExtractor extractor =
+ getMockExtractor(ImmutableMap.of("param1", "value1", "param2", "value2"));
+ RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor);
+ String encodedParams = encoder.encode(key);
- assertEquals("param1=value+1¶m2=value+2+&", encodedParams);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testEncodeNullName() throws Exception {
- RequestParamsExtractor extractor =
- getMockExtractor(1, Collections.singletonMap((String) null, "value1"));
- RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor, false);
- encoder.encode(1);
+ assertEquals("param1=value1¶m2=value2", encodedParams);
}
@Test
- public void testEncodeNullValue() throws Exception {
- RequestParamsExtractor extractor =
- getMockExtractor(1, Collections.singletonMap("param1", (String) null));
- RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor, false);
- String encodedParams = encoder.encode(1);
- assertEquals("", encodedParams);
- }
+ public void testEncode_multipleEntriesConcatenation_encodedKeyAndValues() {
+ RequestParamsExtractor extractor =
+ getMockExtractor(ImmutableMap.of("param1!@#", "value+1", "param2()", "value+2+&"));
+ RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor);
+ String encodedParams = encoder.encode(key);
- @Test
- public void testEncodeEmptyValue() throws Exception {
- RequestParamsExtractor extractor =
- getMockExtractor(1, Collections.singletonMap("param1", ""));
- RequestUrlParamsEncoder encoder = new RequestUrlParamsEncoder<>(extractor, false);
- String encodedParams = encoder.encode(1);
- assertEquals("param1=", encodedParams);
+ assertEquals("param1%21%40%23=value%2B1¶m2%28%29=value%2B2%2B%26", encodedParams);
}
@Test
- public void testEncodeNullAndEmptyParams() throws Exception {
- RequestParamsExtractor extractor =
- getMockExtractor(1, Collections.emptyMap());
- RequestUrlParamsEncoder