1 /* 2 * Copyright 2017 Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 package com.google.api.gax.rpc; 31 32 import com.google.api.core.ApiClock; 33 import com.google.api.core.BetaApi; 34 import com.google.api.core.NanoClock; 35 import com.google.api.gax.core.BackgroundResource; 36 import com.google.api.gax.core.ExecutorAsBackgroundResource; 37 import com.google.api.gax.core.ExecutorProvider; 38 import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials; 39 import com.google.api.gax.rpc.mtls.MtlsProvider; 40 import com.google.api.gax.tracing.ApiTracerFactory; 41 import com.google.api.gax.tracing.BaseApiTracerFactory; 42 import com.google.auth.Credentials; 43 import com.google.auto.value.AutoValue; 44 import com.google.common.collect.ImmutableList; 45 import com.google.common.collect.ImmutableMap; 46 import com.google.common.collect.Sets; 47 import java.io.IOException; 48 import java.util.Collections; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Set; 53 import java.util.concurrent.Executors; 54 import java.util.concurrent.ScheduledExecutorService; 55 import javax.annotation.Nonnull; 56 import javax.annotation.Nullable; 57 import org.threeten.bp.Duration; 58 59 /** 60 * Encapsulates client state, including executor, credentials, and transport channel. 61 * 62 * <p>Unlike {@link ClientSettings} which allows users to configure the client, {@code 63 * ClientContext} is intended to be used in generated code. Most users will not need to use it. 64 */ 65 @AutoValue 66 public abstract class ClientContext { 67 private static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; 68 69 /** 70 * The objects that need to be closed in order to clean up the resources created in the process of 71 * creating this ClientContext. This will include the closeables from the transport context. 72 */ getBackgroundResources()73 public abstract List<BackgroundResource> getBackgroundResources(); 74 75 /** 76 * Gets the executor to use for running scheduled API call logic (such as retries and long-running 77 * operations). 78 */ getExecutor()79 public abstract ScheduledExecutorService getExecutor(); 80 81 @Nullable getCredentials()82 public abstract Credentials getCredentials(); 83 84 @Nullable getTransportChannel()85 public abstract TransportChannel getTransportChannel(); 86 getHeaders()87 public abstract Map<String, String> getHeaders(); 88 getInternalHeaders()89 protected abstract Map<String, String> getInternalHeaders(); 90 getClock()91 public abstract ApiClock getClock(); 92 getDefaultCallContext()93 public abstract ApiCallContext getDefaultCallContext(); 94 95 @Nullable getStreamWatchdog()96 public abstract Watchdog getStreamWatchdog(); 97 98 @Nonnull getStreamWatchdogCheckInterval()99 public abstract Duration getStreamWatchdogCheckInterval(); 100 101 @Nullable getEndpoint()102 public abstract String getEndpoint(); 103 104 @Nullable getQuotaProjectId()105 public abstract String getQuotaProjectId(); 106 107 /** Gets the {@link ApiTracerFactory} that will be used to generate traces for operations. */ 108 @BetaApi("The surface for tracing is not stable yet and may change in the future.") 109 @Nonnull getTracerFactory()110 public abstract ApiTracerFactory getTracerFactory(); 111 newBuilder()112 public static Builder newBuilder() { 113 return new AutoValue_ClientContext.Builder() 114 .setBackgroundResources(Collections.<BackgroundResource>emptyList()) 115 .setExecutor(Executors.newScheduledThreadPool(0)) 116 .setHeaders(Collections.<String, String>emptyMap()) 117 .setInternalHeaders(Collections.<String, String>emptyMap()) 118 .setClock(NanoClock.getDefaultClock()) 119 .setStreamWatchdog(null) 120 .setStreamWatchdogCheckInterval(Duration.ZERO) 121 .setTracerFactory(BaseApiTracerFactory.getInstance()) 122 .setQuotaProjectId(null); 123 } 124 toBuilder()125 public abstract Builder toBuilder(); 126 127 /** 128 * Instantiates the executor, credentials, and transport context based on the given client 129 * settings. 130 */ create(ClientSettings settings)131 public static ClientContext create(ClientSettings settings) throws IOException { 132 return create(settings.getStubSettings()); 133 } 134 135 /** Returns the endpoint that should be used. See https://google.aip.dev/auth/4114. */ getEndpoint( String endpoint, String mtlsEndpoint, boolean switchToMtlsEndpointAllowed, MtlsProvider mtlsProvider)136 static String getEndpoint( 137 String endpoint, 138 String mtlsEndpoint, 139 boolean switchToMtlsEndpointAllowed, 140 MtlsProvider mtlsProvider) 141 throws IOException { 142 if (switchToMtlsEndpointAllowed) { 143 switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { 144 case ALWAYS: 145 return mtlsEndpoint; 146 case NEVER: 147 return endpoint; 148 default: 149 if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { 150 return mtlsEndpoint; 151 } 152 return endpoint; 153 } 154 } 155 return endpoint; 156 } 157 158 /** 159 * Instantiates the executor, credentials, and transport context based on the given client 160 * settings. 161 */ create(StubSettings settings)162 public static ClientContext create(StubSettings settings) throws IOException { 163 ApiClock clock = settings.getClock(); 164 165 ExecutorProvider backgroundExecutorProvider = settings.getBackgroundExecutorProvider(); 166 final ScheduledExecutorService backgroundExecutor = backgroundExecutorProvider.getExecutor(); 167 168 Credentials credentials = settings.getCredentialsProvider().getCredentials(); 169 170 if (settings.getQuotaProjectId() != null) { 171 // If the quotaProjectId is set, wrap original credentials with correct quotaProjectId as 172 // QuotaProjectIdHidingCredentials. 173 // Ensure that a custom set quota project id takes priority over one detected by credentials. 174 // Avoid the backend receiving possibly conflict values of quotaProjectId 175 credentials = new QuotaProjectIdHidingCredentials(credentials); 176 } 177 178 TransportChannelProvider transportChannelProvider = settings.getTransportChannelProvider(); 179 // After needsExecutor and StubSettings#setExecutorProvider are deprecated, transport channel 180 // executor can only be set from TransportChannelProvider#withExecutor directly, and a provider 181 // will have a default executor if it needs one. 182 if (transportChannelProvider.needsExecutor() && settings.getExecutorProvider() != null) { 183 transportChannelProvider = transportChannelProvider.withExecutor(backgroundExecutor); 184 } 185 Map<String, String> headers = getHeadersFromSettings(settings); 186 if (transportChannelProvider.needsHeaders()) { 187 transportChannelProvider = transportChannelProvider.withHeaders(headers); 188 } 189 if (transportChannelProvider.needsCredentials() && credentials != null) { 190 transportChannelProvider = transportChannelProvider.withCredentials(credentials); 191 } 192 String endpoint = 193 getEndpoint( 194 settings.getEndpoint(), 195 settings.getMtlsEndpoint(), 196 settings.getSwitchToMtlsEndpointAllowed(), 197 new MtlsProvider()); 198 if (transportChannelProvider.needsEndpoint()) { 199 transportChannelProvider = transportChannelProvider.withEndpoint(endpoint); 200 } 201 TransportChannel transportChannel = transportChannelProvider.getTransportChannel(); 202 203 ApiCallContext defaultCallContext = 204 transportChannel.getEmptyCallContext().withTransportChannel(transportChannel); 205 if (credentials != null) { 206 defaultCallContext = defaultCallContext.withCredentials(credentials); 207 } 208 209 WatchdogProvider watchdogProvider = settings.getStreamWatchdogProvider(); 210 @Nullable Watchdog watchdog = null; 211 212 if (watchdogProvider != null) { 213 if (watchdogProvider.needsCheckInterval()) { 214 watchdogProvider = 215 watchdogProvider.withCheckInterval(settings.getStreamWatchdogCheckInterval()); 216 } 217 if (watchdogProvider.needsClock()) { 218 watchdogProvider = watchdogProvider.withClock(clock); 219 } 220 if (watchdogProvider.needsExecutor()) { 221 watchdogProvider = watchdogProvider.withExecutor(backgroundExecutor); 222 } 223 watchdog = watchdogProvider.getWatchdog(); 224 } 225 226 ImmutableList.Builder<BackgroundResource> backgroundResources = ImmutableList.builder(); 227 228 if (transportChannelProvider.shouldAutoClose()) { 229 backgroundResources.add(transportChannel); 230 } 231 if (backgroundExecutorProvider.shouldAutoClose()) { 232 backgroundResources.add(new ExecutorAsBackgroundResource(backgroundExecutor)); 233 } 234 if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) { 235 backgroundResources.add(watchdog); 236 } 237 238 return newBuilder() 239 .setBackgroundResources(backgroundResources.build()) 240 .setExecutor(backgroundExecutor) 241 .setCredentials(credentials) 242 .setTransportChannel(transportChannel) 243 .setHeaders(ImmutableMap.copyOf(settings.getHeaderProvider().getHeaders())) 244 .setInternalHeaders(ImmutableMap.copyOf(settings.getInternalHeaderProvider().getHeaders())) 245 .setClock(clock) 246 .setDefaultCallContext(defaultCallContext) 247 .setEndpoint(settings.getEndpoint()) 248 .setQuotaProjectId(settings.getQuotaProjectId()) 249 .setStreamWatchdog(watchdog) 250 .setStreamWatchdogCheckInterval(settings.getStreamWatchdogCheckInterval()) 251 .setTracerFactory(settings.getTracerFactory()) 252 .build(); 253 } 254 255 /** 256 * Getting a header map from HeaderProvider and InternalHeaderProvider from settings with Quota 257 * Project Id. 258 */ getHeadersFromSettings(StubSettings settings)259 private static Map<String, String> getHeadersFromSettings(StubSettings settings) { 260 // Resolve conflicts when merging headers from multiple sources 261 Map<String, String> userHeaders = settings.getHeaderProvider().getHeaders(); 262 Map<String, String> internalHeaders = settings.getInternalHeaderProvider().getHeaders(); 263 Map<String, String> conflictResolution = new HashMap<>(); 264 265 Set<String> conflicts = Sets.intersection(userHeaders.keySet(), internalHeaders.keySet()); 266 for (String key : conflicts) { 267 if ("user-agent".equals(key)) { 268 conflictResolution.put(key, userHeaders.get(key) + " " + internalHeaders.get(key)); 269 continue; 270 } 271 // Backwards compat: quota project id can conflict if its overriden in settings 272 if (QUOTA_PROJECT_ID_HEADER_KEY.equals(key) && settings.getQuotaProjectId() != null) { 273 continue; 274 } 275 throw new IllegalArgumentException("Header provider can't override the header: " + key); 276 } 277 if (settings.getQuotaProjectId() != null) { 278 conflictResolution.put(QUOTA_PROJECT_ID_HEADER_KEY, settings.getQuotaProjectId()); 279 } 280 281 Map<String, String> effectiveHeaders = new HashMap<>(); 282 effectiveHeaders.putAll(internalHeaders); 283 effectiveHeaders.putAll(userHeaders); 284 effectiveHeaders.putAll(conflictResolution); 285 286 return ImmutableMap.copyOf(effectiveHeaders); 287 } 288 289 @AutoValue.Builder 290 public abstract static class Builder { 291 setBackgroundResources(List<BackgroundResource> backgroundResources)292 public abstract Builder setBackgroundResources(List<BackgroundResource> backgroundResources); 293 294 /** 295 * Sets the executor to use for running scheduled API call logic (such as retries and 296 * long-running operations). 297 */ setExecutor(ScheduledExecutorService value)298 public abstract Builder setExecutor(ScheduledExecutorService value); 299 setCredentials(Credentials value)300 public abstract Builder setCredentials(Credentials value); 301 setTransportChannel(TransportChannel transportChannel)302 public abstract Builder setTransportChannel(TransportChannel transportChannel); 303 setHeaders(Map<String, String> headers)304 public abstract Builder setHeaders(Map<String, String> headers); 305 setInternalHeaders(Map<String, String> headers)306 protected abstract Builder setInternalHeaders(Map<String, String> headers); 307 setClock(ApiClock clock)308 public abstract Builder setClock(ApiClock clock); 309 setDefaultCallContext(ApiCallContext defaultCallContext)310 public abstract Builder setDefaultCallContext(ApiCallContext defaultCallContext); 311 setEndpoint(String endpoint)312 public abstract Builder setEndpoint(String endpoint); 313 setQuotaProjectId(String QuotaProjectId)314 public abstract Builder setQuotaProjectId(String QuotaProjectId); 315 setStreamWatchdog(Watchdog watchdog)316 public abstract Builder setStreamWatchdog(Watchdog watchdog); 317 setStreamWatchdogCheckInterval(Duration duration)318 public abstract Builder setStreamWatchdogCheckInterval(Duration duration); 319 320 /** 321 * Set the {@link ApiTracerFactory} that will be used to generate traces for operations. 322 * 323 * @param tracerFactory an instance {@link ApiTracerFactory}. 324 */ 325 @BetaApi("The surface for tracing is not stable yet and may change in the future.") setTracerFactory(ApiTracerFactory tracerFactory)326 public abstract Builder setTracerFactory(ApiTracerFactory tracerFactory); 327 build()328 public abstract ClientContext build(); 329 } 330 } 331