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: 0.2.8
Show newest version
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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 java.nio.charset.StandardCharsets.UTF_8;

import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.cloud.spi.ServiceRpcFactory;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

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.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Abstract class representing service options.
 *
 * @param  the service subclass
 * @param  the spi-layer class corresponding to the service
 * @param  the {@code ServiceOptions} subclass corresponding to the service
 */
public abstract class ServiceOptions, ServiceRpcT,
    OptionsT extends ServiceOptions> implements Serializable {

  private static final String DEFAULT_HOST = "https://www.googleapis.com";
  private static final long serialVersionUID = 1203687993961393350L;
  private static final String PROJECT_ENV_NAME = "GCLOUD_PROJECT";
  private static final String MANIFEST_ARTIFACT_ID_KEY = "artifactId";
  private static final String MANIFEST_VERSION_KEY = "Implementation-Version";
  private static final String ARTIFACT_ID = "gcloud-java-core";
  private static final String APPLICATION_BASE_NAME = "gcloud-java";
  private static final String APPLICATION_NAME = getApplicationName();

  private final String projectId;
  private final String host;
  private final String httpTransportFactoryClassName;
  private final RestorableState authCredentialsState;
  private final RetryParams retryParams;
  private final String serviceRpcFactoryClassName;
  private final String serviceFactoryClassName;
  private final int connectTimeout;
  private final int readTimeout;
  private final Clock clock;

  private transient HttpTransportFactory httpTransportFactory;
  private transient AuthCredentials authCredentials;
  private transient ServiceRpcFactory serviceRpcFactory;
  private transient ServiceFactory serviceFactory;
  private transient ServiceT service;
  private transient ServiceRpcT rpc;

  /**
   * A base interface for all {@link HttpTransport} factories.
   *
   * 

Implementation must provide a public no-arg constructor. Loading of a factory implementation * is done via {@link java.util.ServiceLoader}. */ public interface HttpTransportFactory { HttpTransport create(); } public static class DefaultHttpTransportFactory implements HttpTransportFactory { private static final HttpTransportFactory INSTANCE = new DefaultHttpTransportFactory(); @Override public HttpTransport create() { // Consider App Engine if (appEngineAppId() != null) { try { return new UrlFetchTransport(); } catch (Exception ignore) { // Maybe not on App Engine } } return new NetHttpTransport(); } } /** * Builder for {@code ServiceOptions}. * * @param the service subclass * @param the spi-layer class corresponding to the service * @param the {@code ServiceOptions} subclass corresponding to the service * @param the {@code ServiceOptions} builder */ protected abstract static class Builder, ServiceRpcT, OptionsT extends ServiceOptions, B extends Builder> { private String projectId; private String host; private HttpTransportFactory httpTransportFactory; private AuthCredentials authCredentials; private RetryParams retryParams; private ServiceFactory serviceFactory; private ServiceRpcFactory serviceRpcFactory; private int connectTimeout = -1; private int readTimeout = -1; private Clock clock; protected Builder() {} protected Builder(ServiceOptions options) { projectId = options.projectId; host = options.host; httpTransportFactory = options.httpTransportFactory; authCredentials = options.authCredentials; retryParams = options.retryParams; serviceFactory = options.serviceFactory; serviceRpcFactory = options.serviceRpcFactory; connectTimeout = options.connectTimeout; readTimeout = options.readTimeout; clock = options.clock; } protected abstract ServiceOptions build(); @SuppressWarnings("unchecked") protected B self() { return (B) this; } /** * Sets the service factory. */ public B serviceFactory(ServiceFactory serviceFactory) { this.serviceFactory = serviceFactory; return self(); } /** * Sets the service's clock. The clock is mainly used for testing purpose. {@link Clock} will be * replaced by Java8's {@code java.time.Clock}. * * @param clock the clock to set * @return the builder */ public B clock(Clock clock) { this.clock = clock; return self(); } /** * Sets project id. * * @return the builder */ public B projectId(String projectId) { this.projectId = projectId; return self(); } /** * Sets service host. * * @return the builder */ public B host(String host) { this.host = host; return self(); } /** * Sets the transport factory. * * @return the builder */ public B httpTransportFactory(HttpTransportFactory httpTransportFactory) { this.httpTransportFactory = httpTransportFactory; return self(); } /** * Sets the service authentication credentials. * * @return the builder */ public B authCredentials(AuthCredentials authCredentials) { this.authCredentials = authCredentials; return self(); } /** * Sets configuration parameters for request retries. If no configuration is set * {@link RetryParams#defaultInstance()} is used. To disable retries, supply * {@link RetryParams#noRetries()} here. * * @return the builder */ public B retryParams(RetryParams retryParams) { this.retryParams = retryParams; return self(); } /** * Sets the factory for rpc services. * * @return the builder */ public B serviceRpcFactory(ServiceRpcFactory serviceRpcFactory) { this.serviceRpcFactory = serviceRpcFactory; return self(); } /** * Sets the timeout in milliseconds to establish a connection. * * @param connectTimeout connection timeout in milliseconds. 0 for an infinite timeout, a * negative number for the default value (20000). * @return the builder */ public B connectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; return self(); } /** * Sets the timeout in milliseconds to read data from an established connection. * * @param readTimeout read timeout in milliseconds. 0 for an infinite timeout, a negative number * for the default value (20000). * @return the builder */ public B readTimeout(int readTimeout) { this.readTimeout = readTimeout; return self(); } } protected ServiceOptions(Class> serviceFactoryClass, Class> rpcFactoryClass, Builder builder) { projectId = builder.projectId != null ? builder.projectId : defaultProject(); 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, defaultHost()); httpTransportFactory = firstNonNull(builder.httpTransportFactory, getFromServiceLoader(HttpTransportFactory.class, DefaultHttpTransportFactory.INSTANCE)); httpTransportFactoryClassName = httpTransportFactory.getClass().getName(); authCredentials = builder.authCredentials != null ? builder.authCredentials : defaultAuthCredentials(); authCredentialsState = authCredentials != null ? authCredentials.capture() : null; retryParams = firstNonNull(builder.retryParams, defaultRetryParams()); serviceFactory = firstNonNull(builder.serviceFactory, getFromServiceLoader(serviceFactoryClass, defaultServiceFactory())); serviceFactoryClassName = serviceFactory.getClass().getName(); serviceRpcFactory = firstNonNull(builder.serviceRpcFactory, getFromServiceLoader(rpcFactoryClass, defaultRpcFactory())); serviceRpcFactoryClassName = serviceRpcFactory.getClass().getName(); connectTimeout = builder.connectTimeout; readTimeout = builder.readTimeout; clock = firstNonNull(builder.clock, Clock.defaultClock()); } /** * 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 AuthCredentials defaultAuthCredentials() { // Consider App Engine. if (appEngineAppId() != null) { try { return AuthCredentials.createForAppEngine(); } catch (Exception ignore) { // Maybe not on App Engine } } try { return AuthCredentials.createApplicationDefaults(); } catch (Exception ex) { return null; } } protected static String appEngineAppId() { return System.getProperty("com.google.appengine.application.id"); } protected String defaultHost() { return DEFAULT_HOST; } protected String defaultProject() { String projectId = System.getProperty(PROJECT_ENV_NAME, System.getenv(PROJECT_ENV_NAME)); if (projectId == null) { projectId = appEngineProjectId(); } if (projectId == null) { projectId = serviceAccountProjectId(); } return projectId != null ? projectId : googleCloudProjectId(); } private static String activeGoogleCloudConfig(File configDir) { String activeGoogleCloudConfig = null; try { activeGoogleCloudConfig = Files.readFirstLine(new File(configDir, "active_config"), Charset.defaultCharset()); } catch (IOException ex) { // ignore } // if reading active_config failed or the file is empty we try default return firstNonNull(activeGoogleCloudConfig, "default"); } protected static String googleCloudProjectId() { 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 = activeGoogleCloudConfig(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 } } try { URL url = new URL("http://metadata/computeMetadata/v1/project/project-id"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("X-Google-Metadata-Request", "True"); InputStream input = connection.getInputStream(); if (connection.getResponseCode() == 200) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, UTF_8))) { return reader.readLine(); } } } catch (IOException ignore) { // ignore } // return null if can't determine return null; } private static boolean isWindows() { return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"); } protected static String appEngineProjectId() { try { Class factoryClass = Class.forName("com.google.appengine.api.appidentity.AppIdentityServiceFactory"); Class serviceClass = Class.forName("com.google.appengine.api.appidentity.AppIdentityService"); Method method = factoryClass.getMethod("getAppIdentityService"); Object appIdentityService = method.invoke(null); method = serviceClass.getMethod("getServiceAccountName"); String serviceAccountName = (String) method.invoke(appIdentityService); int indexOfAtSign = serviceAccountName.indexOf('@'); return serviceAccountName.substring(0, indexOfAtSign); } catch (Exception ignore) { // return null if can't determine return null; } } protected static String serviceAccountProjectId() { String project = null; String credentialsPath = System.getenv("GOOGLE_APPLICATION_CREDENTIALS"); if (credentialsPath != null) { try (InputStream credentialsStream = new FileInputStream(credentialsPath)) { JSONObject json = new JSONObject(new JSONTokener(credentialsStream)); project = json.getString("project_id"); } catch (IOException | JSONException ex) { // ignore } } return project; } @SuppressWarnings("unchecked") public ServiceT service() { if (service == null) { service = serviceFactory.create((OptionsT) this); } return service; } @SuppressWarnings("unchecked") public ServiceRpcT rpc() { if (rpc == null) { rpc = serviceRpcFactory.create((OptionsT) this); } return rpc; } /** * Returns the project id. Return value can be null (for services that don't require a project * id). */ public String projectId() { return projectId; } /** * Returns the service host. */ public String host() { return host; } /** * Returns the transport factory. */ public HttpTransportFactory httpTransportFactory() { return httpTransportFactory; } /** * Returns the authentication credentials. */ public AuthCredentials authCredentials() { return authCredentials; } /** * Returns configuration parameters for request retries. By default requests are retried: * {@link RetryParams#defaultInstance()} is used. */ public RetryParams retryParams() { return retryParams; } /** * Returns a request initializer responsible for initializing requests according to service * options. */ public HttpRequestInitializer httpRequestInitializer() { final HttpRequestInitializer delegate = authCredentials() != null && authCredentials.credentials() != null ? new HttpCredentialsAdapter(authCredentials().credentials().createScoped(scopes())) : null; return new HttpRequestInitializer() { @Override public void initialize(HttpRequest httpRequest) throws IOException { if (delegate != null) { delegate.initialize(httpRequest); } if (connectTimeout >= 0) { httpRequest.setConnectTimeout(connectTimeout); } if (readTimeout >= 0) { httpRequest.setReadTimeout(readTimeout); } } }; } /** * Returns the timeout in milliseconds to establish a connection. 0 is an infinite timeout, a * negative number is the default value (20000). */ public int connectTimeout() { return connectTimeout; } /** * Returns the timeout in milliseconds to read from an established connection. 0 is an infinite * timeout, a negative number is the default value (20000). */ public int readTimeout() { return readTimeout; } /** * Returns the service's clock. Default time source uses {@link System#currentTimeMillis()} to get * current time. */ public Clock clock() { return clock; } /** * Returns the application's name as a string in the format {@code gcloud-java/[version]}. */ public String applicationName() { return APPLICATION_NAME; } protected int baseHashCode() { return Objects.hash(projectId, host, httpTransportFactoryClassName, authCredentialsState, retryParams, serviceFactoryClassName, serviceRpcFactoryClassName, connectTimeout, readTimeout, clock); } protected boolean baseEquals(ServiceOptions other) { return Objects.equals(projectId, other.projectId) && Objects.equals(host, other.host) && Objects.equals(httpTransportFactoryClassName, other.httpTransportFactoryClassName) && Objects.equals(authCredentialsState, other.authCredentialsState) && Objects.equals(retryParams, other.retryParams) && Objects.equals(serviceFactoryClassName, other.serviceFactoryClassName) && Objects.equals(serviceRpcFactoryClassName, other.serviceRpcFactoryClassName) && Objects.equals(connectTimeout, other.connectTimeout) && Objects.equals(readTimeout, other.readTimeout) && Objects.equals(clock, clock); } private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { input.defaultReadObject(); httpTransportFactory = newInstance(httpTransportFactoryClassName); serviceFactory = newInstance(serviceFactoryClassName); serviceRpcFactory = newInstance(serviceRpcFactoryClassName); authCredentials = authCredentialsState != null ? authCredentialsState.restore() : null; } @SuppressWarnings("unchecked") private static T newInstance(String className) throws IOException, ClassNotFoundException { try { return (T) Class.forName(className).newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new IOException(e); } } protected abstract ServiceFactory defaultServiceFactory(); protected abstract ServiceRpcFactory defaultRpcFactory(); protected abstract Set scopes(); 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 RetryParams}. */ protected RetryParams defaultRetryParams() { return RetryParams.defaultInstance(); } private static T getFromServiceLoader(Class clazz, T defaultInstance) { return Iterables.getFirst(ServiceLoader.load(clazz), defaultInstance); } private static String getApplicationName() { String version = null; try { Enumeration resources = ServiceOptions.class.getClassLoader().getResources(JarFile.MANIFEST_NAME); while (resources.hasMoreElements() && version == null) { Manifest manifest = new Manifest(resources.nextElement().openStream()); Attributes manifestAttributes = manifest.getMainAttributes(); String artifactId = manifestAttributes.getValue(MANIFEST_ARTIFACT_ID_KEY); if (artifactId != null && artifactId.equals(ARTIFACT_ID)) { version = manifestAttributes.getValue(MANIFEST_VERSION_KEY); } } } catch (IOException e) { // ignore } return version != null ? APPLICATION_BASE_NAME + "/" + version : APPLICATION_BASE_NAME; } }