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