• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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