// Copyright 2011 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.api.ads.adwords.lib.client;
import com.google.api.ads.adwords.lib.client.reporting.ReportingConfiguration;
import com.google.api.ads.common.lib.auth.OAuth2Compatible;
import com.google.api.ads.common.lib.client.AdsSession;
import com.google.api.ads.common.lib.conf.ConfigurationHelper;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.auth.oauth2.Credential;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.apache.commons.configuration.Configuration;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* A {@code AdWordsSession} represents a single session of AdWords use.
*
* <p>
* Implementation is not thread-safe.
* </p>
*/
public class AdWordsSession implements AdsSession, OAuth2Compatible {
private String clientCustomerId;
private Long expressBusinessId;
private String expressPlusPageId;
private Boolean isValidateOnly;
private Boolean isPartialFailure;
private Credential oAuth2Credential;
private ReportingConfiguration reportingConfiguration;
private final String userAgent;
private final String developerToken;
private final String endpoint;
public static final String DEFAULT_ENDPOINT = "https://adwords.google.com/";
private static final String DEFAULT_USER_AGENT = "INSERT_USERAGENT_HERE";
/** The user agent to use if one is not specified. */
@VisibleForTesting
static final String UNKNOWN_USER_AGENT = "unknown";
/**
* Private constructor.
*
* @param builder the AdWordsSession builder
*/
private AdWordsSession(Builder builder) {
this.clientCustomerId = builder.clientCustomerId;
this.developerToken = builder.developerToken;
this.endpoint = builder.endpoint;
this.isPartialFailure = builder.isPartialFailure;
this.isValidateOnly = builder.isValidateOnly;
this.oAuth2Credential = builder.oAuth2Credential;
this.userAgent = builder.userAgent;
this.reportingConfiguration = builder.reportingConfiguration;
}
/**
* Gets the client customer ID.
*/
public String getClientCustomerId() {
return clientCustomerId;
}
/**
* Sets the client customer ID.
*/
public void setClientCustomerId(String clientCustomerId) {
this.clientCustomerId = clientCustomerId;
}
/**
* Gets the AdWords Express business ID used by AdWords Express PromotionService.
*/
@Nullable
public Long getExpressBusinessId() {
return expressBusinessId;
}
/**
* Sets the AdWords Express business ID used by AdWords Express PromotionService.
*
* <p>When using PromotionService, either set this value or the express plus page ID,
* but not both.
*/
public void setExpressBusinessId(@Nullable Long businessId) {
this.expressBusinessId = businessId;
}
/**
* Gets the Google+ page ID for the Google My Business location used by AdWords Express
* PromotionService.
*/
@Nullable
public String getExpressPlusPageId() {
return expressPlusPageId;
}
/**
* Sets the Google+ page ID for the Google My Business location used by AdWords Express
* PromotionService.
*
* <p>When using PromotionService, either set this value or the express business ID,
* but not both.
*/
public void setExpressPlusPageId(String expressPlusPageId) {
this.expressPlusPageId = expressPlusPageId;
}
/**
* Returns {@code true} if the session should only validate the request.
*/
@Nullable
public Boolean isValidateOnly() {
return isValidateOnly;
}
/**
* Sets whether this session should only validate the request.
*/
public void setValidateOnly(@Nullable Boolean isValidateOnly) {
this.isValidateOnly = isValidateOnly;
}
/**
* @return the userAgent
*/
public String getUserAgent() {
return userAgent;
}
/**
* @return the developerToken
*/
public String getDeveloperToken() {
return developerToken;
}
/**
* @return the isPartialFailure
*/
@Nullable
public Boolean isPartialFailure() {
return isPartialFailure;
}
/**
* Sets whether this session should allow partial failure.
*/
public void setPartialFailure(@Nullable Boolean isPartialFailure) {
this.isPartialFailure = isPartialFailure;
}
/**
* Gets the OAuth2 credentials.
*/
@Override
public Credential getOAuth2Credential() {
return oAuth2Credential;
}
/**
* Sets the OAuth2 credential. Any other authentication credentials on the
* session will be removed.
*/
public void setOAuth2Credential(Credential oAuth2Credential) {
Preconditions.checkNotNull(oAuth2Credential, "oAuth2Credential cannot be null.");
clearAuthentication();
this.oAuth2Credential = oAuth2Credential;
}
/**
* Gets the reporting configuration.
*/
@Nullable
public ReportingConfiguration getReportingConfiguration() {
return reportingConfiguration;
}
/**
* Sets the reporting configuration.
*/
public void setReportingConfiguration(@Nullable ReportingConfiguration reportingConfiguration) {
this.reportingConfiguration = reportingConfiguration;
}
/**
* Clears all the authentication credentials from this session.
*/
private void clearAuthentication() {
oAuth2Credential = null;
}
/**
* @return the endpoint
*/
@Override
public String getEndpoint() {
return endpoint;
}
/**
* Returns a new {@link Builder} with all settings copied from this session. This is <em>not</em>
* thread-safe unless this session is an {@link ImmutableAdWordsSession}.
*/
public Builder newBuilder() {
return new Builder(this);
}
/**
* Immutable, thread-safe implementation of AdWordsSession.
*/
@ThreadSafe
public static final class ImmutableAdWordsSession extends AdWordsSession {
private ImmutableAdWordsSession(Builder builder) {
super(builder);
}
private void throwUnsupportedOperationException(String attributeName) {
throw new UnsupportedOperationException(
String.format(
"Cannot set %s. ImmutableAdWordsSession is immutable.", attributeName));
}
@Override
public final void setClientCustomerId(String clientCustomerId) {
throwUnsupportedOperationException("clientCustomerId");
}
@Override
public final void setExpressBusinessId(@Nullable Long businessId) {
throwUnsupportedOperationException("businessId");
}
@Override
public final void setExpressPlusPageId(String expressPlusPageId) {
throwUnsupportedOperationException("expressPlusPageId");
}
@Override
public final void setValidateOnly(@Nullable Boolean isValidateOnly) {
throwUnsupportedOperationException("isValidateOnly");
}
@Override
public final void setPartialFailure(@Nullable Boolean isPartialFailure) {
throwUnsupportedOperationException("isPartialFailure");
}
@Override
public final void setOAuth2Credential(Credential oAuth2Credential) {
throwUnsupportedOperationException("oAuth2Credential");
}
@Override
public final void setReportingConfiguration(
@Nullable ReportingConfiguration reportingConfiguration) {
throwUnsupportedOperationException("reportingConfiguration");
}
}
/**
* Builder for AdWordsSession.
*
* <p>
* Implementation is not thread-safe.
* </p>
*/
public static class Builder implements
com.google.api.ads.common.lib.utils.Builder<AdWordsSession> {
private String endpoint;
private String userAgent;
private String developerToken;
private String clientCustomerId;
private Boolean isPartialFailure;
private Boolean isValidateOnly;
private Credential oAuth2Credential;
private ReportingConfiguration reportingConfiguration;
private final ConfigurationHelper configHelper;
/**
* Constructs an empty builder. To construct a builder initialized to the settings of
* an existing {@link AdWordsSession}, use {@link AdWordsSession#newBuilder()} instead.
*/
public Builder() {
this.configHelper = new ConfigurationHelper();
}
private Builder(AdWordsSession sessionToClone) {
this();
this.endpoint = sessionToClone.getEndpoint();
this.userAgent = sessionToClone.getUserAgent();
this.developerToken = sessionToClone.getDeveloperToken();
this.clientCustomerId = sessionToClone.getClientCustomerId();
this.isPartialFailure = sessionToClone.isPartialFailure();
this.isValidateOnly = sessionToClone.isValidateOnly();
this.oAuth2Credential = sessionToClone.getOAuth2Credential();
this.reportingConfiguration = sessionToClone.getReportingConfiguration();
}
@Override
public Builder fromFile() throws ConfigurationLoadException {
return fromFile(Builder.DEFAULT_CONFIGURATION_FILENAME);
}
@Override
public Builder fromFile(String path) throws ConfigurationLoadException {
return from(configHelper.fromFile(path));
}
@Override
public Builder fromFile(File path) throws ConfigurationLoadException {
return from(configHelper.fromFile(path));
}
@Override
public Builder fromFile(URL path) throws ConfigurationLoadException {
return from(configHelper.fromFile(path));
}
/**
* Reads properties from the provided {@link Configuration} object.<br><br>
* Known properties:
* <ul>
* <li>api.adwords.clientCustomerId</li>
* <li>api.adwords.userAgent</li>
* <li>api.adwords.developerToken</li>
* <li>api.adwords.isPartialFailure</li>
* <li>api.adwords.endpoint</li>
* <li>api.adwords.reporting.skipHeader</li>
* <li>api.adwords.reporting.skipColumnHeader</li>
* <li>api.adwords.reporting.skipSummary</li>
* </ul>
*
* @param config
* @return Builder populated from the Configuration
*/
@Override
public Builder from(Configuration config) {
this.clientCustomerId = config.getString("api.adwords.clientCustomerId", null);
this.userAgent = config.getString("api.adwords.userAgent", null);
this.developerToken = config.getString("api.adwords.developerToken", null);
this.isPartialFailure = config.getBoolean("api.adwords.isPartialFailure", null);
this.endpoint = config.getString("api.adwords.endpoint", null);
// Only create a ReportConfiguration for this object if at least one reporting
// configuration config value is present.
Boolean isSkipReportHeader = config.getBoolean("api.adwords.reporting.skipHeader", null);
Boolean isSkipColumnHeader =
config.getBoolean("api.adwords.reporting.skipColumnHeader", null);
Boolean isSkipReportSummary = config.getBoolean("api.adwords.reporting.skipSummary", null);
Boolean isUseRawEnumValues =
config.getBoolean("api.adwords.reporting.useRawEnumValues", null);
if (isSkipReportHeader != null
|| isSkipColumnHeader != null
|| isSkipReportSummary != null
|| isUseRawEnumValues != null) {
this.reportingConfiguration =
new ReportingConfiguration.Builder()
.skipReportHeader(isSkipReportHeader)
.skipColumnHeader(isSkipColumnHeader)
.skipReportSummary(isSkipReportSummary)
.useRawEnumValues(isUseRawEnumValues)
.build();
}
return this;
}
/**
* Includes OAuth2 credential to be used for OAuth2 authentication.
*/
public Builder withOAuth2Credential(Credential oAuth2Credential) {
clearAuthentication();
this.oAuth2Credential = oAuth2Credential;
return this;
}
public Builder withReportingConfiguration(ReportingConfiguration reportingConfiguration) {
this.reportingConfiguration = reportingConfiguration;
return this;
}
/**
* Includes a developer token. Required.
*/
public Builder withDeveloperToken(String developerToken) {
this.developerToken = developerToken;
return this;
}
/**
* Includes user agent.
*/
public Builder withUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Override the endpoint server. Optional and defaults to
* https://adwords.google.com.
*/
public Builder withEndpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
/**
* Includes a clientCustomerId.
*/
public Builder withClientCustomerId(String clientCustomerId) {
this.clientCustomerId = clientCustomerId;
return this;
}
/**
* Enables partial failure. Default is disabled.
*/
public Builder enablePartialFailure() {
this.isPartialFailure = true;
return this;
}
/**
* Enables validate only. Default is disabled.
*/
public Builder enableValidateOnly() {
this.isValidateOnly = true;
return this;
}
/**
* Clears all the authentication credentials from this session.
*/
private void clearAuthentication() {
oAuth2Credential = null;
}
/**
* Builds the {@code AdWordsSession}.
*
* @return the built {@code AdWordsSession}
* @throws ValidationException if the attributes of this builder fail validation
*/
@Override
public AdWordsSession build() throws ValidationException {
defaultOptionals();
validate();
return new AdWordsSession(this);
}
/**
* Builds a thread-safe {@link ImmutableAdWordsSession}.
* @return the built {@code ImmutableAdWordsSession}
* @throws ValidationException if the attributes of this builder fail validation
*/
public ImmutableAdWordsSession buildImmutable() throws ValidationException {
defaultOptionals();
validate();
return new ImmutableAdWordsSession(this);
}
/**
* Fills in defaults if {@code null}.
*/
private void defaultOptionals() {
if (this.endpoint == null) {
this.endpoint = DEFAULT_ENDPOINT;
}
}
/**
* Validates the properties for the AdWords session.
*/
private void validate() throws ValidationException {
// Check for at least one authentication mechanism.
if (this.oAuth2Credential == null) {
throw new ValidationException("OAuth2 authentication must be used.", "");
}
// Check that the developer token is set.
if (this.developerToken == null) {
throw new ValidationException("A developer token must be set.", "developerToken");
}
// Check that user agent is not empty or the default.
if (Strings.isNullOrEmpty(userAgent)
|| userAgent.contains(DEFAULT_USER_AGENT)) {
this.userAgent = UNKNOWN_USER_AGENT;
} else if (!CharMatcher.ascii().matchesAllOf(userAgent)) {
throw new ValidationException(
String.format("User agent [%s] contains non-ASCII characters.", userAgent),
"userAgent");
}
// Make sure they specify an endpoint.
try {
new URL(this.endpoint);
} catch (MalformedURLException e) {
throw new ValidationException(String.format("Endpoint [%s] not recognized as a valid URL.",
this.endpoint), "endpoint", e);
}
}
}
}