1 /*
2  * Copyright 2015 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.cloud;
18 
19 import static com.google.common.base.MoreObjects.firstNonNull;
20 import static com.google.common.base.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 
23 import com.google.api.client.http.GenericUrl;
24 import com.google.api.client.http.HttpHeaders;
25 import com.google.api.client.http.HttpRequest;
26 import com.google.api.client.http.HttpRequestFactory;
27 import com.google.api.client.http.HttpResponse;
28 import com.google.api.client.http.HttpTransport;
29 import com.google.api.client.http.javanet.NetHttpTransport;
30 import com.google.api.client.json.GenericJson;
31 import com.google.api.client.json.JsonFactory;
32 import com.google.api.client.json.JsonObjectParser;
33 import com.google.api.client.json.gson.GsonFactory;
34 import com.google.api.core.ApiClock;
35 import com.google.api.core.BetaApi;
36 import com.google.api.core.CurrentMillisClock;
37 import com.google.api.core.InternalApi;
38 import com.google.api.gax.core.GaxProperties;
39 import com.google.api.gax.retrying.RetrySettings;
40 import com.google.api.gax.rpc.FixedHeaderProvider;
41 import com.google.api.gax.rpc.HeaderProvider;
42 import com.google.api.gax.rpc.NoHeaderProvider;
43 import com.google.auth.Credentials;
44 import com.google.auth.oauth2.GoogleCredentials;
45 import com.google.auth.oauth2.QuotaProjectIdProvider;
46 import com.google.auth.oauth2.ServiceAccountCredentials;
47 import com.google.cloud.spi.ServiceRpcFactory;
48 import com.google.common.base.Preconditions;
49 import com.google.common.collect.ImmutableMap;
50 import com.google.common.collect.ImmutableSet;
51 import com.google.common.collect.Iterables;
52 import com.google.common.io.Files;
53 import java.io.BufferedReader;
54 import java.io.File;
55 import java.io.FileInputStream;
56 import java.io.FileNotFoundException;
57 import java.io.FileReader;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.io.ObjectInputStream;
61 import java.io.Serializable;
62 import java.nio.charset.Charset;
63 import java.nio.charset.StandardCharsets;
64 import java.util.Locale;
65 import java.util.Map;
66 import java.util.Objects;
67 import java.util.ServiceLoader;
68 import java.util.Set;
69 import java.util.regex.Matcher;
70 import java.util.regex.Pattern;
71 import org.threeten.bp.Duration;
72 
73 /**
74  * Abstract class representing service options.
75  *
76  * @param <ServiceT> the service subclass
77  * @param <OptionsT> the {@code ServiceOptions} subclass corresponding to the service
78  */
79 public abstract class ServiceOptions<
80         ServiceT extends Service<OptionsT>, OptionsT extends ServiceOptions<ServiceT, OptionsT>>
81     implements Serializable {
82 
83   public static final String CREDENTIAL_ENV_NAME = "GOOGLE_APPLICATION_CREDENTIALS";
84 
85   private static final String DEFAULT_HOST = "https://www.googleapis.com";
86   private static final String LEGACY_PROJECT_ENV_NAME = "GCLOUD_PROJECT";
87   private static final String PROJECT_ENV_NAME = "GOOGLE_CLOUD_PROJECT";
88 
89   private static final RetrySettings DEFAULT_RETRY_SETTINGS =
90       getDefaultRetrySettingsBuilder().build();
91   private static final RetrySettings NO_RETRY_SETTINGS =
92       getDefaultRetrySettingsBuilder().setMaxAttempts(1).build();
93 
94   private static final long serialVersionUID = 9198896031667942014L;
95   protected final String clientLibToken;
96 
97   private final String projectId;
98   private final String host;
99   private final RetrySettings retrySettings;
100   private final String serviceRpcFactoryClassName;
101   private final String serviceFactoryClassName;
102   private final ApiClock clock;
103   protected Credentials credentials;
104   private final TransportOptions transportOptions;
105   private final HeaderProvider headerProvider;
106   private final String quotaProjectId;
107 
108   private transient ServiceRpcFactory<OptionsT> serviceRpcFactory;
109   private transient ServiceFactory<ServiceT, OptionsT> serviceFactory;
110   private transient ServiceT service;
111   private transient ServiceRpc rpc;
112 
113   /**
114    * Builder for {@code ServiceOptions}.
115    *
116    * @param <ServiceT> the service subclass
117    * @param <OptionsT> the {@code ServiceOptions} subclass corresponding to the service
118    * @param <B> the {@code ServiceOptions} builder
119    */
120   public abstract static class Builder<
121       ServiceT extends Service<OptionsT>,
122       OptionsT extends ServiceOptions<ServiceT, OptionsT>,
123       B extends Builder<ServiceT, OptionsT, B>> {
124 
125     private final ImmutableSet<String> allowedClientLibTokens =
126         ImmutableSet.of(ServiceOptions.getGoogApiClientLibName());
127     private String projectId;
128     private String host;
129     protected Credentials credentials;
130     private RetrySettings retrySettings;
131     private ServiceFactory<ServiceT, OptionsT> serviceFactory;
132     private ServiceRpcFactory<OptionsT> serviceRpcFactory;
133     private ApiClock clock;
134     private TransportOptions transportOptions;
135     private HeaderProvider headerProvider;
136     private String clientLibToken = ServiceOptions.getGoogApiClientLibName();
137     private String quotaProjectId;
138 
139     @InternalApi("This class should only be extended within google-cloud-java")
Builder()140     protected Builder() {}
141 
142     @InternalApi("This class should only be extended within google-cloud-java")
Builder(ServiceOptions<ServiceT, OptionsT> options)143     protected Builder(ServiceOptions<ServiceT, OptionsT> options) {
144       projectId = options.projectId;
145       host = options.host;
146       credentials = options.credentials;
147       retrySettings = options.retrySettings;
148       serviceFactory = options.serviceFactory;
149       serviceRpcFactory = options.serviceRpcFactory;
150       clock = options.clock;
151       transportOptions = options.transportOptions;
152       clientLibToken = options.clientLibToken;
153       quotaProjectId = options.quotaProjectId;
154     }
155 
build()156     protected abstract ServiceOptions<ServiceT, OptionsT> build();
157 
158     @SuppressWarnings("unchecked")
self()159     protected B self() {
160       return (B) this;
161     }
162 
163     /** Sets the service factory. */
setServiceFactory(ServiceFactory<ServiceT, OptionsT> serviceFactory)164     public B setServiceFactory(ServiceFactory<ServiceT, OptionsT> serviceFactory) {
165       this.serviceFactory = serviceFactory;
166       return self();
167     }
168 
169     /**
170      * Sets the service's clock. The clock is mainly used for testing purpose. {@link ApiClock} will
171      * be replaced by Java8's {@code java.time.Clock}.
172      *
173      * @param clock the clock to set
174      * @return the builder
175      */
setClock(ApiClock clock)176     public B setClock(ApiClock clock) {
177       this.clock = clock;
178       return self();
179     }
180 
181     /**
182      * Sets the project ID. If no project ID is set, {@link #getDefaultProjectId()} will be used to
183      * attempt getting the project ID from the environment.
184      *
185      * @return the builder
186      */
setProjectId(String projectId)187     public B setProjectId(String projectId) {
188       this.projectId = projectId;
189       return self();
190     }
191 
192     /**
193      * Sets service host.
194      *
195      * @return the builder
196      */
setHost(String host)197     public B setHost(String host) {
198       this.host = host;
199       return self();
200     }
201 
202     /**
203      * Sets the service authentication credentials. If no credentials are set, {@link
204      * GoogleCredentials#getApplicationDefault()} will be used to attempt getting credentials from
205      * the environment. Use {@link NoCredentials#getInstance()} to skip authentication, this is
206      * typically useful when using local service emulators.
207      *
208      * @param credentials authentication credentials, should not be {@code null}
209      * @return the builder
210      * @throws NullPointerException if {@code credentials} is {@code null}. To disable
211      *     authentication use {@link NoCredentials#getInstance()}
212      */
setCredentials(Credentials credentials)213     public B setCredentials(Credentials credentials) {
214       this.credentials = checkNotNull(credentials);
215       // set project id if available
216       if (this.projectId == null && credentials instanceof ServiceAccountCredentials) {
217         this.projectId = ((ServiceAccountCredentials) credentials).getProjectId();
218       }
219 
220       if (this.quotaProjectId == null && credentials instanceof QuotaProjectIdProvider) {
221         this.quotaProjectId = ((QuotaProjectIdProvider) credentials).getQuotaProjectId();
222       }
223       return self();
224     }
225 
226     /**
227      * Sets configuration parameters for request retries.
228      *
229      * @return the builder
230      */
setRetrySettings(RetrySettings retrySettings)231     public B setRetrySettings(RetrySettings retrySettings) {
232       this.retrySettings = retrySettings;
233       return self();
234     }
235 
236     /**
237      * Sets the factory for rpc services.
238      *
239      * @return the builder
240      */
setServiceRpcFactory(ServiceRpcFactory<OptionsT> serviceRpcFactory)241     public B setServiceRpcFactory(ServiceRpcFactory<OptionsT> serviceRpcFactory) {
242       this.serviceRpcFactory = serviceRpcFactory;
243       return self();
244     }
245 
246     /**
247      * Sets the transport options.
248      *
249      * @return the builder
250      */
setTransportOptions(TransportOptions transportOptions)251     public B setTransportOptions(TransportOptions transportOptions) {
252       this.transportOptions = transportOptions;
253       return self();
254     }
255 
256     /**
257      * Sets the static header provider. The header provider will be called during client
258      * construction only once. The headers returned by the provider will be cached and supplied as
259      * is for each request issued by the constructed client. Some reserved headers can be overridden
260      * (e.g. Content-Type) or merged with the default value (e.g. User-Agent) by the underlying
261      * transport layer.
262      *
263      * @param headerProvider the header provider
264      * @return the builder
265      */
266     @BetaApi
setHeaderProvider(HeaderProvider headerProvider)267     public B setHeaderProvider(HeaderProvider headerProvider) {
268       this.headerProvider = headerProvider;
269       return self();
270     }
271 
272     @InternalApi
setClientLibToken(String clientLibToken)273     public B setClientLibToken(String clientLibToken) {
274       Preconditions.checkArgument(
275           getAllowedClientLibTokens().contains(clientLibToken), "Illegal client lib token");
276       this.clientLibToken = clientLibToken;
277       return self();
278     }
279 
280     /**
281      * Sets the quotaProjectId that specifies the project used for quota and billing purposes.
282      *
283      * @see <a href="https://cloud.google.com/apis/docs/system-parameters">See system parameter
284      *     $userProject</a>
285      */
setQuotaProjectId(String quotaProjectId)286     public B setQuotaProjectId(String quotaProjectId) {
287       this.quotaProjectId = quotaProjectId;
288       return self();
289     }
290 
getAllowedClientLibTokens()291     protected Set<String> getAllowedClientLibTokens() {
292       return allowedClientLibTokens;
293     }
294   }
295 
296   @InternalApi("This class should only be extended within google-cloud-java")
ServiceOptions( Class<? extends ServiceFactory<ServiceT, OptionsT>> serviceFactoryClass, Class<? extends ServiceRpcFactory<OptionsT>> rpcFactoryClass, Builder<ServiceT, OptionsT, ?> builder, ServiceDefaults<ServiceT, OptionsT> serviceDefaults)297   protected ServiceOptions(
298       Class<? extends ServiceFactory<ServiceT, OptionsT>> serviceFactoryClass,
299       Class<? extends ServiceRpcFactory<OptionsT>> rpcFactoryClass,
300       Builder<ServiceT, OptionsT, ?> builder,
301       ServiceDefaults<ServiceT, OptionsT> serviceDefaults) {
302     projectId = builder.projectId != null ? builder.projectId : getDefaultProject();
303     if (projectIdRequired()) {
304       checkArgument(
305           projectId != null,
306           "A project ID is required for this service but could not be determined from the builder "
307               + "or the environment.  Please set a project ID using the builder.");
308     }
309     host = firstNonNull(builder.host, getDefaultHost());
310     credentials = builder.credentials != null ? builder.credentials : defaultCredentials();
311     retrySettings = firstNonNull(builder.retrySettings, getDefaultRetrySettings());
312     serviceFactory =
313         firstNonNull(
314             builder.serviceFactory,
315             getFromServiceLoader(serviceFactoryClass, serviceDefaults.getDefaultServiceFactory()));
316     serviceFactoryClassName = serviceFactory.getClass().getName();
317     serviceRpcFactory =
318         firstNonNull(
319             builder.serviceRpcFactory,
320             getFromServiceLoader(rpcFactoryClass, serviceDefaults.getDefaultRpcFactory()));
321     serviceRpcFactoryClassName = serviceRpcFactory.getClass().getName();
322     clock = firstNonNull(builder.clock, CurrentMillisClock.getDefaultClock());
323     transportOptions =
324         firstNonNull(builder.transportOptions, serviceDefaults.getDefaultTransportOptions());
325     headerProvider = firstNonNull(builder.headerProvider, new NoHeaderProvider());
326     clientLibToken = builder.clientLibToken;
327     quotaProjectId =
328         builder.quotaProjectId != null
329             ? builder.quotaProjectId
330             : getValueFromCredentialsFile(getCredentialsPath(), "quota_project_id");
331   }
332 
getCredentialsPath()333   private static String getCredentialsPath() {
334     return System.getProperty(CREDENTIAL_ENV_NAME, System.getenv(CREDENTIAL_ENV_NAME));
335   }
336 
337   /**
338    * Returns whether a service requires a project ID. This method may be overridden in
339    * service-specific Options objects.
340    *
341    * @return true if a project ID is required to use the service, false if not
342    */
projectIdRequired()343   protected boolean projectIdRequired() {
344     return true;
345   }
346 
defaultCredentials()347   private static GoogleCredentials defaultCredentials() {
348     try {
349       return GoogleCredentials.getApplicationDefault();
350     } catch (Exception ex) {
351       return null;
352     }
353   }
354 
getDefaultHost()355   protected String getDefaultHost() {
356     return DEFAULT_HOST;
357   }
358 
getDefaultProject()359   protected String getDefaultProject() {
360     return getDefaultProjectId();
361   }
362 
363   /**
364    * Returns the default project ID, or {@code null} if no default project ID could be found. This
365    * method returns the first available project ID among the following sources:
366    *
367    * <ol>
368    *   <li>The project ID specified by the GOOGLE_CLOUD_PROJECT environment variable
369    *   <li>The App Engine project ID
370    *   <li>The project ID specified in the JSON credentials file pointed by the {@code
371    *       GOOGLE_APPLICATION_CREDENTIALS} environment variable
372    *   <li>The Google Cloud SDK project ID
373    *   <li>The Compute Engine project ID
374    * </ol>
375    */
getDefaultProjectId()376   public static String getDefaultProjectId() {
377     String projectId = System.getProperty(PROJECT_ENV_NAME, System.getenv(PROJECT_ENV_NAME));
378     if (projectId == null) {
379       projectId =
380           System.getProperty(LEGACY_PROJECT_ENV_NAME, System.getenv(LEGACY_PROJECT_ENV_NAME));
381     }
382     if (projectId == null) {
383       projectId = getAppEngineProjectId();
384     }
385     if (projectId == null) {
386       projectId = getServiceAccountProjectId();
387     }
388     return projectId != null ? projectId : getGoogleCloudProjectId();
389   }
390 
getAppEngineAppId()391   public static String getAppEngineAppId() {
392     return System.getProperty("com.google.appengine.application.id");
393   }
394 
getActiveGoogleCloudConfig(File configDir)395   private static String getActiveGoogleCloudConfig(File configDir) {
396     String activeGoogleCloudConfig = null;
397     try {
398       activeGoogleCloudConfig =
399           Files.asCharSource(new File(configDir, "active_config"), Charset.defaultCharset())
400               .readFirstLine();
401     } catch (IOException ex) {
402       // ignore
403     }
404     // if reading active_config failed or the file is empty we try default
405     return firstNonNull(activeGoogleCloudConfig, "default");
406   }
407 
getGoogleCloudProjectId()408   protected static String getGoogleCloudProjectId() {
409     File configDir;
410     if (System.getenv().containsKey("CLOUDSDK_CONFIG")) {
411       configDir = new File(System.getenv("CLOUDSDK_CONFIG"));
412     } else if (isWindows() && System.getenv().containsKey("APPDATA")) {
413       configDir = new File(System.getenv("APPDATA"), "gcloud");
414     } else {
415       configDir = new File(System.getProperty("user.home"), ".config/gcloud");
416     }
417     String activeConfig = getActiveGoogleCloudConfig(configDir);
418     FileReader fileReader = null;
419     try {
420       fileReader = new FileReader(new File(configDir, "configurations/config_" + activeConfig));
421     } catch (FileNotFoundException newConfigFileNotFoundEx) {
422       try {
423         fileReader = new FileReader(new File(configDir, "properties"));
424       } catch (FileNotFoundException oldConfigFileNotFoundEx) {
425         // ignore
426       }
427     }
428     if (fileReader != null) {
429       try (BufferedReader reader = new BufferedReader(fileReader)) {
430         String line;
431         String section = null;
432         Pattern projectPattern = Pattern.compile("^project\\s*=\\s*(.*)$");
433         Pattern sectionPattern = Pattern.compile("^\\[(.*)\\]$");
434         while ((line = reader.readLine()) != null) {
435           if (line.isEmpty() || line.startsWith(";")) {
436             continue;
437           }
438           line = line.trim();
439           Matcher matcher = sectionPattern.matcher(line);
440           if (matcher.matches()) {
441             section = matcher.group(1);
442           } else if (section == null || section.equals("core")) {
443             matcher = projectPattern.matcher(line);
444             if (matcher.matches()) {
445               return matcher.group(1);
446             }
447           }
448         }
449       } catch (IOException ex) {
450         // ignore
451       }
452     }
453     // return project id from metadata config
454     return MetadataConfig.getProjectId();
455   }
456 
isWindows()457   private static boolean isWindows() {
458     return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows");
459   }
460 
getAppEngineProjectId()461   protected static String getAppEngineProjectId() {
462     String projectId = null;
463     if (PlatformInformation.isOnGAEStandard7()) {
464       projectId = getAppEngineProjectIdFromAppId();
465     } else {
466       // for GAE flex and standard Java 8 environment
467       projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
468       if (projectId == null) {
469         projectId = System.getenv("GCLOUD_PROJECT");
470       }
471       if (projectId == null) {
472         projectId = getAppEngineProjectIdFromAppId();
473       }
474       if (projectId == null) {
475         try {
476           projectId = getAppEngineProjectIdFromMetadataServer();
477         } catch (IOException ignore) {
478           projectId = null;
479         }
480       }
481     }
482     return projectId;
483   }
484 
getAppEngineProjectIdFromAppId()485   protected static String getAppEngineProjectIdFromAppId() {
486     String projectId = getAppEngineAppId();
487     if (projectId != null && projectId.contains(":")) {
488       int colonIndex = projectId.indexOf(":");
489       projectId = projectId.substring(colonIndex + 1);
490     }
491     return projectId;
492   }
493 
getAppEngineProjectIdFromMetadataServer()494   private static String getAppEngineProjectIdFromMetadataServer() throws IOException {
495     String metadata = "http://metadata.google.internal";
496     String projectIdURL = "/computeMetadata/v1/project/project-id";
497     GenericUrl url = new GenericUrl(metadata + projectIdURL);
498 
499     HttpTransport netHttpTransport = new NetHttpTransport();
500     HttpRequestFactory requestFactory = netHttpTransport.createRequestFactory();
501     HttpRequest request =
502         requestFactory
503             .buildGetRequest(url)
504             .setConnectTimeout(500)
505             .setReadTimeout(500)
506             .setHeaders(new HttpHeaders().set("Metadata-Flavor", "Google"));
507     HttpResponse response = request.execute();
508     return headerContainsMetadataFlavor(response) ? response.parseAsString() : null;
509   }
510 
511   @InternalApi("Visible for testing")
headerContainsMetadataFlavor(HttpResponse response)512   static boolean headerContainsMetadataFlavor(HttpResponse response) {
513     String metadataFlavorValue = response.getHeaders().getFirstHeaderStringValue("Metadata-Flavor");
514     return "Google".equals(metadataFlavorValue);
515   }
516 
getServiceAccountProjectId()517   protected static String getServiceAccountProjectId() {
518     return getValueFromCredentialsFile(getCredentialsPath(), "project_id");
519   }
520 
521   @InternalApi("Visible for testing")
getValueFromCredentialsFile(String credentialsPath, String key)522   static String getValueFromCredentialsFile(String credentialsPath, String key) {
523     if (credentialsPath != null) {
524       try (InputStream credentialsStream = new FileInputStream(credentialsPath)) {
525         JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
526         JsonObjectParser parser = new JsonObjectParser(jsonFactory);
527         GenericJson fileContents =
528             parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
529         return (String) fileContents.get(key);
530       } catch (IOException | IllegalArgumentException ex) {
531         return null;
532       }
533     }
534     return null;
535   }
536 
537   /**
538    * Returns a Service object for the current service. For instance, when using Google Cloud
539    * Storage, it returns a Storage object.
540    */
541   @SuppressWarnings("unchecked")
getService()542   public ServiceT getService() {
543     if (shouldRefreshService(service)) {
544       service = serviceFactory.create((OptionsT) this);
545     }
546     return service;
547   }
548 
549   /**
550    * @param cachedService The currently cached service object
551    * @return true if the currently cached service object should be refreshed.
552    */
shouldRefreshService(ServiceT cachedService)553   protected boolean shouldRefreshService(ServiceT cachedService) {
554     return cachedService == null;
555   }
556 
557   /**
558    * Returns a Service RPC object for the current service. For instance, when using Google Cloud
559    * Storage, it returns a StorageRpc object.
560    */
561   @SuppressWarnings("unchecked")
getRpc()562   public ServiceRpc getRpc() {
563     if (shouldRefreshRpc(rpc)) {
564       rpc = serviceRpcFactory.create((OptionsT) this);
565     }
566     return rpc;
567   }
568 
569   /**
570    * @param cachedRpc The currently cached service object
571    * @return true if the currently cached service object should be refreshed.
572    */
shouldRefreshRpc(ServiceRpc cachedRpc)573   protected boolean shouldRefreshRpc(ServiceRpc cachedRpc) {
574     return cachedRpc == null;
575   }
576 
577   /**
578    * Returns the project ID. Return value can be null (for services that don't require a project
579    * ID).
580    */
getProjectId()581   public String getProjectId() {
582     return projectId;
583   }
584 
585   /** Returns the service host. */
getHost()586   public String getHost() {
587     return host;
588   }
589 
590   /** Returns the authentication credentials. */
getCredentials()591   public Credentials getCredentials() {
592     return credentials;
593   }
594 
595   /** Returns the authentication credentials. If required, credentials are scoped. */
getScopedCredentials()596   public Credentials getScopedCredentials() {
597     Credentials credentialsToReturn = credentials;
598     if (credentials instanceof GoogleCredentials
599         && ((GoogleCredentials) credentials).createScopedRequired()) {
600       credentialsToReturn = ((GoogleCredentials) credentials).createScoped(getScopes());
601     }
602     return credentialsToReturn;
603   }
604 
605   /** Returns configuration parameters for request retries. */
getRetrySettings()606   public RetrySettings getRetrySettings() {
607     return retrySettings;
608   }
609 
610   /**
611    * Returns the service's clock. Default time source uses {@link System#currentTimeMillis()} to get
612    * current time.
613    */
getClock()614   public ApiClock getClock() {
615     return clock;
616   }
617 
618   /** Returns the transport-specific options for this service. */
getTransportOptions()619   public TransportOptions getTransportOptions() {
620     return transportOptions;
621   }
622 
623   /**
624    * Returns the application's name as a string in the format {@code gcloud-java/[version]},
625    * optionally prepended with externally supplied User-Agent header value (via setting custom
626    * header provider).
627    */
getApplicationName()628   public String getApplicationName() {
629     String libraryVersion = getLibraryVersion();
630 
631     // We have to do the following since underlying layers often do not appreciate User-Agent
632     // provided as a normal header and override it or treat setting "application name" as the only
633     // way to append something to User-Agent header.
634     StringBuilder sb = new StringBuilder();
635     String customUserAgentValue = getUserAgent();
636     if (customUserAgentValue != null) {
637       sb.append(customUserAgentValue).append(' ');
638     }
639     if (libraryVersion == null) {
640       sb.append(getLibraryName());
641     } else {
642       sb.append(getLibraryName()).append('/').append(libraryVersion);
643     }
644 
645     return sb.toString();
646   }
647 
648   /** Returns the library's name, {@code gcloud-java}, as a string. */
getLibraryName()649   public static String getLibraryName() {
650     return "gcloud-java";
651   }
652 
653   /** Returns the library's name used by x-goog-api-client header as a string. */
getGoogApiClientLibName()654   public static String getGoogApiClientLibName() {
655     return "gccl";
656   }
657 
658   /** Returns the library's version as a string. */
getLibraryVersion()659   public String getLibraryVersion() {
660     return GaxProperties.getLibraryVersion(this.getClass());
661   }
662 
663   @InternalApi
getMergedHeaderProvider(HeaderProvider internalHeaderProvider)664   public final HeaderProvider getMergedHeaderProvider(HeaderProvider internalHeaderProvider) {
665     Map<String, String> mergedHeaders =
666         ImmutableMap.<String, String>builder()
667             .putAll(internalHeaderProvider.getHeaders())
668             .putAll(headerProvider.getHeaders())
669             .build();
670     return FixedHeaderProvider.create(mergedHeaders);
671   }
672 
673   @InternalApi
getUserAgent()674   public final String getUserAgent() {
675     if (headerProvider != null) {
676       for (Map.Entry<String, String> entry : headerProvider.getHeaders().entrySet()) {
677         if ("user-agent".equals(entry.getKey().toLowerCase())) {
678           return entry.getValue();
679         }
680       }
681     }
682     return null;
683   }
684 
baseHashCode()685   protected int baseHashCode() {
686     return Objects.hash(
687         projectId,
688         host,
689         credentials,
690         retrySettings,
691         serviceFactoryClassName,
692         serviceRpcFactoryClassName,
693         clock,
694         quotaProjectId);
695   }
696 
baseEquals(ServiceOptions<?, ?> other)697   protected boolean baseEquals(ServiceOptions<?, ?> other) {
698     return Objects.equals(projectId, other.projectId)
699         && Objects.equals(host, other.host)
700         && Objects.equals(credentials, other.credentials)
701         && Objects.equals(retrySettings, other.retrySettings)
702         && Objects.equals(serviceFactoryClassName, other.serviceFactoryClassName)
703         && Objects.equals(serviceRpcFactoryClassName, other.serviceRpcFactoryClassName)
704         && Objects.equals(clock, other.clock)
705         && Objects.equals(quotaProjectId, other.quotaProjectId);
706   }
707 
readObject(ObjectInputStream input)708   private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
709     input.defaultReadObject();
710     serviceFactory = newInstance(serviceFactoryClassName);
711     serviceRpcFactory = newInstance(serviceRpcFactoryClassName);
712   }
713 
714   @SuppressWarnings("unchecked")
715   @InternalApi
newInstance(String className)716   public static <T> T newInstance(String className) throws IOException, ClassNotFoundException {
717     try {
718       return (T) Class.forName(className).newInstance();
719     } catch (InstantiationException | IllegalAccessException e) {
720       throw new IOException(e);
721     }
722   }
723 
getDefaultRetrySettings()724   public static RetrySettings getDefaultRetrySettings() {
725     return DEFAULT_RETRY_SETTINGS;
726   }
727 
getNoRetrySettings()728   public static RetrySettings getNoRetrySettings() {
729     return NO_RETRY_SETTINGS;
730   }
731 
getDefaultRetrySettingsBuilder()732   private static RetrySettings.Builder getDefaultRetrySettingsBuilder() {
733     return RetrySettings.newBuilder()
734         .setMaxAttempts(6)
735         .setInitialRetryDelay(Duration.ofMillis(1000L))
736         .setMaxRetryDelay(Duration.ofMillis(32_000L))
737         .setRetryDelayMultiplier(2.0)
738         .setTotalTimeout(Duration.ofMillis(50_000L))
739         .setInitialRpcTimeout(Duration.ofMillis(50_000L))
740         .setRpcTimeoutMultiplier(1.0)
741         .setMaxRpcTimeout(Duration.ofMillis(50_000L));
742   }
743 
getScopes()744   protected abstract Set<String> getScopes();
745 
toBuilder()746   public abstract <B extends Builder<ServiceT, OptionsT, B>> B toBuilder();
747 
748   /**
749    * Some services may have different backoff requirements listed in their SLAs. Be sure to override
750    * this method in options subclasses when the service's backoff requirement differs from the
751    * default parameters listed in {@link RetrySettings}.
752    */
defaultRetrySettings()753   protected RetrySettings defaultRetrySettings() {
754     return getDefaultRetrySettings();
755   }
756 
757   @InternalApi
getFromServiceLoader(Class<? extends T> clazz, T defaultInstance)758   public static <T> T getFromServiceLoader(Class<? extends T> clazz, T defaultInstance) {
759     return Iterables.getFirst(ServiceLoader.load(clazz), defaultInstance);
760   }
761 
getClientLibToken()762   public String getClientLibToken() {
763     return clientLibToken;
764   }
765 
766   /** Returns the quotaProjectId that specifies the project used for quota and billing purposes. */
getQuotaProjectId()767   public String getQuotaProjectId() {
768     return quotaProjectId;
769   }
770 }
771