// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.appengine.tools.remoteapi;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.compute.ComputeCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.apphosting.api.ApiProxy;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.List;
/**
* A mutable object containing settings for installing the remote API.
*
* <p>Example for connecting to a development app server:</p>
*
* <pre>
* RemoteApiOptions options = new RemoteApiOptions()
* .server("localhost", 8888),
* .credentials("username", "password does not matter");
* </pre>
*
* <p>Example for connecting to a deployed app:</p>
*
* <pre>
* RemoteApiOptions options = new RemoteApiOptions()
* .server("myappid.appspot.com", 443),
* .credentials(adminUsername, adminPassword);
* </pre>
*
* <p>
* The options should be passed to {@link RemoteApiInstaller#install}.
* </p>
*
*/
public class RemoteApiOptions {
private static final List<String> OAUTH_SCOPES = ImmutableList.of(
"https://www.googleapis.com/auth/appengine.apis",
"https://www.googleapis.com/auth/userinfo.email");
private static final String LOCAL_USER = "test@example.com";
private static final String LOCAL_PASSWORD = "";
private String hostname;
private int port;
private String userEmail;
private String password;
private String credentialsToReuse;
private String remoteApiPath = "/remote_api";
private int maxConcurrentRequests = 5;
private int datastoreQueryFetchSize = 500;
private int maxHttpResponseSize = 33 * 1024 * 1024;
private Credential oauthCredential;
private HttpTransport httpTransport;
public RemoteApiOptions() {}
RemoteApiOptions(RemoteApiOptions original) {
this.hostname = original.hostname;
this.port = original.port;
this.userEmail = original.userEmail;
this.password = original.password;
this.credentialsToReuse = original.credentialsToReuse;
this.remoteApiPath = original.remoteApiPath;
this.maxConcurrentRequests = original.maxConcurrentRequests;
this.datastoreQueryFetchSize = original.datastoreQueryFetchSize;
this.maxHttpResponseSize = original.maxHttpResponseSize;
this.oauthCredential = original.oauthCredential;
this.httpTransport = original.httpTransport;
}
/**
* Sets the host and port port where we will connect.
*/
public RemoteApiOptions server(String newHostname, int newPort) {
hostname = newHostname;
port = newPort;
return this;
}
/**
* Sets a username and password to be used for logging in via the
* ClientLogin API. Overrides any previously-provided credentials.
*/
public RemoteApiOptions credentials(String newUserEMail, String newPassword) {
userEmail = newUserEMail;
password = newPassword;
credentialsToReuse = null;
oauthCredential = null;
return this;
}
/**
* Reuses credentials from another AppEngineClient. Credentials can only
* be reused from a client with the same hostname and user. Overrides any
* previously-provided credentials.
* @param newUserEmail the email address of the user we want to log in as.
* @param serializedCredentials a string returned by calling
* {@link AppEngineClient#serializeCredentials} on the previous client
*/
public RemoteApiOptions reuseCredentials(String newUserEmail, String serializedCredentials) {
userEmail = newUserEmail;
password = null;
credentialsToReuse = serializedCredentials;
oauthCredential = null;
return this;
}
/**
* Use a Compute Engine credential for authentication. Overrides any
* previously-provided credentials.
*/
RemoteApiOptions useComputeEngineCredential() {
try {
HttpTransport transport = getOrCreateHttpTransportForOAuth();
ComputeCredential credential = new ComputeCredential(transport, new JacksonFactory());
credential.refreshToken();
setOAuthCredential(credential);
} catch (IOException|GeneralSecurityException e) {
throw new RuntimeException("Failed to acquire Google Compute Engine credential.", e);
}
return this;
}
/**
* Use a service account credential. Overrides any previously-provided
* credentials.
*/
RemoteApiOptions useServiceAccountCredential(String serviceAccount,
String p12PrivateKeyFile) {
try {
Credential credential = getCredentialBuilder(serviceAccount)
.setServiceAccountPrivateKeyFromP12File(new File(p12PrivateKeyFile))
.build();
setOAuthCredential(credential);
} catch (IOException|GeneralSecurityException e) {
throw new RuntimeException("Failed to build service account credential.", e);
}
return this;
}
/**
* Use a service account credential. Overrides any previously-provided
* credentials.
*/
RemoteApiOptions useServiceAccountCredential(String serviceAccount,
PrivateKey privateKey) {
try {
Credential credential = getCredentialBuilder(serviceAccount)
.setServiceAccountPrivateKey(privateKey)
.build();
setOAuthCredential(credential);
} catch (IOException|GeneralSecurityException e) {
throw new RuntimeException("Failed to build service account credential.", e);
}
return this;
}
/**
* Use credentials appropriate for talking to the Development Server.
* Overrides any previously-provided credentials.
*/
RemoteApiOptions useDevelopmentServerCredential() {
credentials(LOCAL_USER, LOCAL_PASSWORD);
return this;
}
private GoogleCredential.Builder getCredentialBuilder(
String serviceAccount) throws GeneralSecurityException, IOException {
HttpTransport transport = getOrCreateHttpTransportForOAuth();
JacksonFactory jsonFactory = new JacksonFactory();
return new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(serviceAccount)
.setServiceAccountScopes(OAUTH_SCOPES);
}
RemoteApiOptions oauthCredential(Credential oauthCredential) {
setOAuthCredential(oauthCredential);
return this;
}
private void setOAuthCredential(Credential oauthCredential) {
userEmail = null;
password = null;
credentialsToReuse = null;
this.oauthCredential = oauthCredential;
}
RemoteApiOptions httpTransport(HttpTransport httpTransport) {
this.httpTransport = httpTransport;
return this;
}
/**
* Sets the path used to access the remote API. If not set, the default
* is /remote_api.
*/
public RemoteApiOptions remoteApiPath(String newPath) {
remoteApiPath = newPath;
return this;
}
/**
* This parameter controls the maximum number of async API requests that will be
* in flight at once. Each concurrent request will likely be handled by a separate
* <a href="http://code.google.com/appengine/docs/adminconsole/instances.html"
* >instance</a> of your App. Having more instances increases throughput but may
* result in errors due to exceeding quota. Defaults to 5.
*/
public RemoteApiOptions maxConcurrentRequests(int newValue) {
maxConcurrentRequests = newValue;
return this;
}
/**
* When executing a datastore query, this is the number of results to fetch
* per HTTP request. Increasing this value will reduce the number of round trips
* when running large queries, but too high a value can be wasteful when not
* all results are needed. Defaults to 500.
*
* <p>(This value can be overridden by the code using the datastore API.)</p>
*/
public RemoteApiOptions datastoreQueryFetchSize(int newValue) {
datastoreQueryFetchSize = newValue;
return this;
}
/**
* When making a remote call, this is the maximum size of the HTTP response.
* The default is 33M. Normally there's no reason to change this. This
* setting has no effect when running in an App Engine container.
*/
public RemoteApiOptions maxHttpResponseSize(int newValue) {
maxHttpResponseSize = newValue;
return this;
}
public RemoteApiOptions copy() {
return new RemoteApiOptions(this);
}
/**
* Create an {@link HttpTransport} appropriate to this environment or return
* the one that's already been created. This method ensures that the
* determination of whether we're running in App Engine happens early
* (specifically, before the Remote API has been installed) and that said
* determination is remembered.
*/
private HttpTransport getOrCreateHttpTransportForOAuth()
throws IOException, GeneralSecurityException {
if (httpTransport != null) {
return httpTransport;
}
if (ApiProxy.getCurrentEnvironment() != null) {
throw new IllegalStateException(
"OAuth-based authorization not supported for clients running on App Engine");
}
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
return httpTransport;
}
HttpTransport getHttpTransport() {
return httpTransport;
}
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
public String getUserEmail() {
return userEmail;
}
public String getPassword() {
return password;
}
public String getCredentialsToReuse() {
return credentialsToReuse;
}
Credential getOAuthCredential() {
return oauthCredential;
}
public String getRemoteApiPath() {
return remoteApiPath;
}
public int getMaxConcurrentRequests() {
return maxConcurrentRequests;
}
public int getDatastoreQueryFetchSize() {
return datastoreQueryFetchSize;
}
public int getMaxHttpResponseSize() {
return maxHttpResponseSize;
}
}