All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.ServiceOptions Maven / Gradle / Ivy

There is a newer version: 2.43.0
Show newest version
/*
 * Copyright 2015 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
 *
 *       http://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.
 */

package com.google.cloud;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Charsets;
import com.google.api.core.ApiClock;
import com.google.api.core.BetaApi;
import com.google.api.core.CurrentMillisClock;
import com.google.api.core.InternalApi;
import com.google.api.gax.core.GaxProperties;
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.rpc.FixedHeaderProvider;
import com.google.api.gax.rpc.HeaderProvider;
import com.google.api.gax.rpc.NoHeaderProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.spi.ServiceRpcFactory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.threeten.bp.Duration;

/**
 * Abstract class representing service options.
 *
 * @param  the service subclass
 * @param  the {@code ServiceOptions} subclass corresponding to the service
 */
public abstract class ServiceOptions<
        ServiceT extends Service, OptionsT extends ServiceOptions>
    implements Serializable {

  public static final String CREDENTIAL_ENV_NAME = "GOOGLE_APPLICATION_CREDENTIALS";

  private static final String DEFAULT_HOST = "https://www.googleapis.com";
  private static final String LEGACY_PROJECT_ENV_NAME = "GCLOUD_PROJECT";
  private static final String PROJECT_ENV_NAME = "GOOGLE_CLOUD_PROJECT";

  private static final RetrySettings DEFAULT_RETRY_SETTINGS =
      getDefaultRetrySettingsBuilder().build();
  private static final RetrySettings NO_RETRY_SETTINGS =
      getDefaultRetrySettingsBuilder().setMaxAttempts(1).build();

  private static final long serialVersionUID = 9198896031667942014L;

  private final String projectId;
  private final String host;
  private final RetrySettings retrySettings;
  private final String serviceRpcFactoryClassName;
  private final String serviceFactoryClassName;
  private final ApiClock clock;
  protected Credentials credentials;
  private final TransportOptions transportOptions;
  private final HeaderProvider headerProvider;

  private transient ServiceRpcFactory serviceRpcFactory;
  private transient ServiceFactory serviceFactory;
  private transient ServiceT service;
  private transient ServiceRpc rpc;

  /**
   * Builder for {@code ServiceOptions}.
   *
   * @param  the service subclass
   * @param  the {@code ServiceOptions} subclass corresponding to the service
   * @param  the {@code ServiceOptions} builder
   */
  public abstract static class Builder<
      ServiceT extends Service,
      OptionsT extends ServiceOptions,
      B extends Builder> {

    private String projectId;
    private String host;
    protected Credentials credentials;
    private RetrySettings retrySettings;
    private ServiceFactory serviceFactory;
    private ServiceRpcFactory serviceRpcFactory;
    private ApiClock clock;
    private TransportOptions transportOptions;
    private HeaderProvider headerProvider;

    @InternalApi("This class should only be extended within google-cloud-java")
    protected Builder() {}

    @InternalApi("This class should only be extended within google-cloud-java")
    protected Builder(ServiceOptions options) {
      projectId = options.projectId;
      host = options.host;
      credentials = options.credentials;
      retrySettings = options.retrySettings;
      serviceFactory = options.serviceFactory;
      serviceRpcFactory = options.serviceRpcFactory;
      clock = options.clock;
      transportOptions = options.transportOptions;
    }

    protected abstract ServiceOptions build();

    @SuppressWarnings("unchecked")
    protected B self() {
      return (B) this;
    }

    /** Sets the service factory. */
    public B setServiceFactory(ServiceFactory serviceFactory) {
      this.serviceFactory = serviceFactory;
      return self();
    }

    /**
     * Sets the service's clock. The clock is mainly used for testing purpose. {@link ApiClock} will
     * be replaced by Java8's {@code java.time.Clock}.
     *
     * @param clock the clock to set
     * @return the builder
     */
    public B setClock(ApiClock clock) {
      this.clock = clock;
      return self();
    }

    /**
     * Sets the project ID. If no project ID is set, {@link #getDefaultProjectId()} will be used to
     * attempt getting the project ID from the environment.
     *
     * @return the builder
     */
    public B setProjectId(String projectId) {
      this.projectId = projectId;
      return self();
    }

    /**
     * Sets service host.
     *
     * @return the builder
     */
    public B setHost(String host) {
      this.host = host;
      return self();
    }

    /**
     * Sets the service authentication credentials. If no credentials are set, {@link
     * GoogleCredentials#getApplicationDefault()} will be used to attempt getting credentials from
     * the environment. Use {@link NoCredentials#getInstance()} to skip authentication, this is
     * typically useful when using local service emulators.
     *
     * @param credentials authentication credentials, should not be {@code null}
     * @return the builder
     * @throws NullPointerException if {@code credentials} is {@code null}. To disable
     *     authentication use {@link NoCredentials#getInstance()}
     */
    public B setCredentials(Credentials credentials) {
      this.credentials = checkNotNull(credentials);
      // set project id if available
      if (this.projectId == null && credentials instanceof ServiceAccountCredentials) {
        this.projectId = ((ServiceAccountCredentials) credentials).getProjectId();
      }
      return self();
    }

    /**
     * Sets configuration parameters for request retries.
     *
     * @return the builder
     */
    public B setRetrySettings(RetrySettings retrySettings) {
      this.retrySettings = retrySettings;
      return self();
    }

    /**
     * Sets the factory for rpc services.
     *
     * @return the builder
     */
    public B setServiceRpcFactory(ServiceRpcFactory serviceRpcFactory) {
      this.serviceRpcFactory = serviceRpcFactory;
      return self();
    }

    /**
     * Sets the transport options.
     *
     * @return the builder
     */
    public B setTransportOptions(TransportOptions transportOptions) {
      this.transportOptions = transportOptions;
      return self();
    }

    /**
     * Sets the static header provider. The header provider will be called during client
     * construction only once. The headers returned by the provider will be cached and supplied as
     * is for each request issued by the constructed client. Some reserved headers can be overridden
     * (e.g. Content-Type) or merged with the default value (e.g. User-Agent) by the underlying
     * transport layer.
     *
     * @param headerProvider the header provider
     * @return the builder
     */
    @BetaApi
    public B setHeaderProvider(HeaderProvider headerProvider) {
      this.headerProvider = headerProvider;
      return self();
    }
  }

  @InternalApi("This class should only be extended within google-cloud-java")
  protected ServiceOptions(
      Class> serviceFactoryClass,
      Class> rpcFactoryClass,
      Builder builder,
      ServiceDefaults serviceDefaults) {
    projectId = builder.projectId != null ? builder.projectId : getDefaultProject();
    if (projectIdRequired()) {
      checkArgument(
          projectId != null,
          "A project ID is required for this service but could not be determined from the builder "
              + "or the environment.  Please set a project ID using the builder.");
    }
    host = firstNonNull(builder.host, getDefaultHost());
    credentials = builder.credentials != null ? builder.credentials : defaultCredentials();
    retrySettings = firstNonNull(builder.retrySettings, getDefaultRetrySettings());
    serviceFactory =
        firstNonNull(
            builder.serviceFactory,
            getFromServiceLoader(serviceFactoryClass, serviceDefaults.getDefaultServiceFactory()));
    serviceFactoryClassName = serviceFactory.getClass().getName();
    serviceRpcFactory =
        firstNonNull(
            builder.serviceRpcFactory,
            getFromServiceLoader(rpcFactoryClass, serviceDefaults.getDefaultRpcFactory()));
    serviceRpcFactoryClassName = serviceRpcFactory.getClass().getName();
    clock = firstNonNull(builder.clock, CurrentMillisClock.getDefaultClock());
    transportOptions =
        firstNonNull(builder.transportOptions, serviceDefaults.getDefaultTransportOptions());
    headerProvider = firstNonNull(builder.headerProvider, new NoHeaderProvider());
  }

  /**
   * Returns whether a service requires a project ID. This method may be overridden in
   * service-specific Options objects.
   *
   * @return true if a project ID is required to use the service, false if not
   */
  protected boolean projectIdRequired() {
    return true;
  }

  private static GoogleCredentials defaultCredentials() {
    try {
      return GoogleCredentials.getApplicationDefault();
    } catch (Exception ex) {
      return null;
    }
  }

  protected String getDefaultHost() {
    return DEFAULT_HOST;
  }

  protected String getDefaultProject() {
    return getDefaultProjectId();
  }

  /**
   * Returns the default project ID, or {@code null} if no default project ID could be found. This
   * method returns the first available project ID among the following sources:
   *
   * 
    *
  1. The project ID specified by the GOOGLE_CLOUD_PROJECT environment variable *
  2. The App Engine project ID *
  3. The project ID specified in the JSON credentials file pointed by the {@code * GOOGLE_APPLICATION_CREDENTIALS} environment variable *
  4. The Google Cloud SDK project ID *
  5. The Compute Engine project ID *
*/ public static String getDefaultProjectId() { String projectId = System.getProperty(PROJECT_ENV_NAME, System.getenv(PROJECT_ENV_NAME)); if (projectId == null) { projectId = System.getProperty(LEGACY_PROJECT_ENV_NAME, System.getenv(LEGACY_PROJECT_ENV_NAME)); } if (projectId == null) { projectId = getAppEngineProjectId(); } if (projectId == null) { projectId = getServiceAccountProjectId(); } return projectId != null ? projectId : getGoogleCloudProjectId(); } public static String getAppEngineAppId() { return System.getProperty("com.google.appengine.application.id"); } private static String getActiveGoogleCloudConfig(File configDir) { String activeGoogleCloudConfig = null; try { activeGoogleCloudConfig = Files.asCharSource(new File(configDir, "active_config"), Charset.defaultCharset()) .readFirstLine(); } catch (IOException ex) { // ignore } // if reading active_config failed or the file is empty we try default return firstNonNull(activeGoogleCloudConfig, "default"); } protected static String getGoogleCloudProjectId() { File configDir; if (System.getenv().containsKey("CLOUDSDK_CONFIG")) { configDir = new File(System.getenv("CLOUDSDK_CONFIG")); } else if (isWindows() && System.getenv().containsKey("APPDATA")) { configDir = new File(System.getenv("APPDATA"), "gcloud"); } else { configDir = new File(System.getProperty("user.home"), ".config/gcloud"); } String activeConfig = getActiveGoogleCloudConfig(configDir); FileReader fileReader = null; try { fileReader = new FileReader(new File(configDir, "configurations/config_" + activeConfig)); } catch (FileNotFoundException newConfigFileNotFoundEx) { try { fileReader = new FileReader(new File(configDir, "properties")); } catch (FileNotFoundException oldConfigFileNotFoundEx) { // ignore } } if (fileReader != null) { try (BufferedReader reader = new BufferedReader(fileReader)) { String line; String section = null; Pattern projectPattern = Pattern.compile("^project\\s*=\\s*(.*)$"); Pattern sectionPattern = Pattern.compile("^\\[(.*)\\]$"); while ((line = reader.readLine()) != null) { if (line.isEmpty() || line.startsWith(";")) { continue; } line = line.trim(); Matcher matcher = sectionPattern.matcher(line); if (matcher.matches()) { section = matcher.group(1); } else if (section == null || section.equals("core")) { matcher = projectPattern.matcher(line); if (matcher.matches()) { return matcher.group(1); } } } } catch (IOException ex) { // ignore } } // return project id from metadata config return MetadataConfig.getProjectId(); } private static boolean isWindows() { return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"); } protected static String getAppEngineProjectId() { String projectId = null; if (PlatformInformation.isOnGAEStandard7()) { projectId = getAppEngineProjectIdFromAppId(); } else { // for GAE flex and standard Java 8 environment projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); if (projectId == null) { projectId = System.getenv("GCLOUD_PROJECT"); } if (projectId == null) { projectId = getAppEngineProjectIdFromAppId(); } if (projectId == null) { try { projectId = getAppEngineProjectIdFromMetadataServer(); } catch (IOException ignore) { projectId = null; } } } return projectId; } protected static String getAppEngineProjectIdFromAppId() { String projectId = getAppEngineAppId(); if (projectId != null && projectId.contains(":")) { int colonIndex = projectId.indexOf(":"); projectId = projectId.substring(colonIndex + 1); } return projectId; } private static String getAppEngineProjectIdFromMetadataServer() throws IOException { String metadata = "http://metadata.google.internal"; String projectIdURL = "/computeMetadata/v1/project/project-id"; GenericUrl url = new GenericUrl(metadata + projectIdURL); HttpTransport netHttpTransport = new NetHttpTransport(); HttpRequestFactory requestFactory = netHttpTransport.createRequestFactory(); HttpRequest request = requestFactory .buildGetRequest(url) .setConnectTimeout(500) .setReadTimeout(500) .setHeaders(new HttpHeaders().set("Metadata-Flavor", "Google")); HttpResponse response = request.execute(); return headerContainsMetadataFlavor(response) ? response.parseAsString() : null; } @InternalApi("Visible for testing") static boolean headerContainsMetadataFlavor(HttpResponse response) { String metadataFlavorValue = response.getHeaders().getFirstHeaderStringValue("Metadata-Flavor"); return "Google".equals(metadataFlavorValue); } protected static String getServiceAccountProjectId() { return getServiceAccountProjectId(System.getenv(CREDENTIAL_ENV_NAME)); } @InternalApi("Visible for testing") static String getServiceAccountProjectId(String credentialsPath) { String project = null; if (credentialsPath != null) { try (InputStream credentialsStream = new FileInputStream(credentialsPath)) { JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); JsonObjectParser parser = new JsonObjectParser(jsonFactory); GenericJson fileContents = parser.parseAndClose(credentialsStream, Charsets.UTF_8, GenericJson.class); project = (String) fileContents.get("project_id"); } catch (IOException e) { // ignore } } return project; } /** * Returns a Service object for the current service. For instance, when using Google Cloud * Storage, it returns a Storage object. */ @SuppressWarnings("unchecked") public ServiceT getService() { if (shouldRefreshService(service)) { service = serviceFactory.create((OptionsT) this); } return service; } /** * @param cachedService The currently cached service object * @return true if the currently cached service object should be refreshed. */ protected boolean shouldRefreshService(ServiceT cachedService) { return cachedService == null; } /** * Returns a Service RPC object for the current service. For instance, when using Google Cloud * Storage, it returns a StorageRpc object. */ @SuppressWarnings("unchecked") public ServiceRpc getRpc() { if (shouldRefreshRpc(rpc)) { rpc = serviceRpcFactory.create((OptionsT) this); } return rpc; } /** * @param cachedRpc The currently cached service object * @return true if the currently cached service object should be refreshed. */ protected boolean shouldRefreshRpc(ServiceRpc cachedRpc) { return cachedRpc == null; } /** * Returns the project ID. Return value can be null (for services that don't require a project * ID). */ public String getProjectId() { return projectId; } /** Returns the service host. */ public String getHost() { return host; } /** Returns the authentication credentials. */ public Credentials getCredentials() { return credentials; } /** Returns the authentication credentials. If required, credentials are scoped. */ public Credentials getScopedCredentials() { Credentials credentialsToReturn = credentials; if (credentials instanceof GoogleCredentials && ((GoogleCredentials) credentials).createScopedRequired()) { credentialsToReturn = ((GoogleCredentials) credentials).createScoped(getScopes()); } return credentialsToReturn; } /** Returns configuration parameters for request retries. */ public RetrySettings getRetrySettings() { return retrySettings; } /** * Returns the service's clock. Default time source uses {@link System#currentTimeMillis()} to get * current time. */ public ApiClock getClock() { return clock; } /** Returns the transport-specific options for this service. */ public TransportOptions getTransportOptions() { return transportOptions; } /** * Returns the application's name as a string in the format {@code gcloud-java/[version]}, * optionally prepended with externally supplied User-Agent header value (via setting custom * header provider). */ public String getApplicationName() { String libraryVersion = getLibraryVersion(); // We have to do the following since underlying layers often do not appreciate User-Agent // provided as a normal header and override it or treat setting "application name" as the only // way to append something to User-Agent header. StringBuilder sb = new StringBuilder(); String customUserAgentValue = getUserAgent(); if (customUserAgentValue != null) { sb.append(customUserAgentValue).append(' '); } if (libraryVersion == null) { sb.append(getLibraryName()); } else { sb.append(getLibraryName()).append('/').append(libraryVersion); } return sb.toString(); } /** Returns the library's name, {@code gcloud-java}, as a string. */ public static String getLibraryName() { return "gcloud-java"; } /** Returns the library's name used by x-goog-api-client header as a string. */ public static String getGoogApiClientLibName() { return "gccl"; } /** Returns the library's version as a string. */ public String getLibraryVersion() { return GaxProperties.getLibraryVersion(this.getClass()); } @InternalApi public final HeaderProvider getMergedHeaderProvider(HeaderProvider internalHeaderProvider) { Map mergedHeaders = ImmutableMap.builder() .putAll(internalHeaderProvider.getHeaders()) .putAll(headerProvider.getHeaders()) .build(); return FixedHeaderProvider.create(mergedHeaders); } @InternalApi public final String getUserAgent() { if (headerProvider != null) { for (Map.Entry entry : headerProvider.getHeaders().entrySet()) { if ("user-agent".equals(entry.getKey().toLowerCase())) { return entry.getValue(); } } } return null; } protected int baseHashCode() { return Objects.hash( projectId, host, credentials, retrySettings, serviceFactoryClassName, serviceRpcFactoryClassName, clock); } protected boolean baseEquals(ServiceOptions other) { return Objects.equals(projectId, other.projectId) && Objects.equals(host, other.host) && Objects.equals(credentials, other.credentials) && Objects.equals(retrySettings, other.retrySettings) && Objects.equals(serviceFactoryClassName, other.serviceFactoryClassName) && Objects.equals(serviceRpcFactoryClassName, other.serviceRpcFactoryClassName) && Objects.equals(clock, clock); } private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { input.defaultReadObject(); serviceFactory = newInstance(serviceFactoryClassName); serviceRpcFactory = newInstance(serviceRpcFactoryClassName); } @SuppressWarnings("unchecked") @InternalApi public static T newInstance(String className) throws IOException, ClassNotFoundException { try { return (T) Class.forName(className).newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new IOException(e); } } public static RetrySettings getDefaultRetrySettings() { return DEFAULT_RETRY_SETTINGS; } public static RetrySettings getNoRetrySettings() { return NO_RETRY_SETTINGS; } private static RetrySettings.Builder getDefaultRetrySettingsBuilder() { return RetrySettings.newBuilder() .setMaxAttempts(6) .setInitialRetryDelay(Duration.ofMillis(1000L)) .setMaxRetryDelay(Duration.ofMillis(32_000L)) .setRetryDelayMultiplier(2.0) .setTotalTimeout(Duration.ofMillis(50_000L)) .setInitialRpcTimeout(Duration.ofMillis(50_000L)) .setRpcTimeoutMultiplier(1.0) .setMaxRpcTimeout(Duration.ofMillis(50_000L)); } protected abstract Set getScopes(); public abstract > B toBuilder(); /** * Some services may have different backoff requirements listed in their SLAs. Be sure to override * this method in options subclasses when the service's backoff requirement differs from the * default parameters listed in {@link RetrySettings}. */ protected RetrySettings defaultRetrySettings() { return getDefaultRetrySettings(); } @InternalApi public static T getFromServiceLoader(Class clazz, T defaultInstance) { return Iterables.getFirst(ServiceLoader.load(clazz), defaultInstance); } }