com.google.api.client.googleapis.services.AbstractGoogleClientRequest.java Source code

Java tutorial

Introduction

Here is the source code for com.google.api.client.googleapis.services.AbstractGoogleClientRequest.java

Source

/*
 * 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.client.googleapis.services;

import static com.google.common.base.StandardSystemProperty.OS_NAME;
import static com.google.common.base.StandardSystemProperty.OS_VERSION;

import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.googleapis.MethodOverride;
import com.google.api.client.googleapis.batch.BatchCallback;
import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.media.MediaHttpDownloader;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.http.AbstractInputStreamContent;
import com.google.api.client.http.EmptyContent;
import com.google.api.client.http.GZipEncoding;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpResponseInterceptor;
import com.google.api.client.http.UriTemplate;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.Preconditions;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Abstract Google client request for a {@link AbstractGoogleClient}.
 *
 * <p>
 * Implementation is not thread-safe.
 * </p>
 *
 * @param <T> type of the response
 *
 * @since 1.12
 * @author Yaniv Inbar
 */
public abstract class AbstractGoogleClientRequest<T> extends GenericData {

    /**
     * User agent suffix for all requests.
     *
     * @since 1.20
     */
    public static final String USER_AGENT_SUFFIX = "Google-API-Java-Client";

    private static final String API_VERSION_HEADER = "X-Goog-Api-Client";

    /** Google client. */
    private final AbstractGoogleClient abstractGoogleClient;

    /** HTTP method. */
    private final String requestMethod;

    /** URI template for the path relative to the base URL. */
    private final String uriTemplate;

    /** HTTP content or {@code null} for none. */
    private final HttpContent httpContent;

    /** HTTP headers used for the Google client request. */
    private HttpHeaders requestHeaders = new HttpHeaders();

    /** HTTP headers of the last response or {@code null} before request has been executed. */
    private HttpHeaders lastResponseHeaders;

    /** Status code of the last response or {@code -1} before request has been executed. */
    private int lastStatusCode = -1;

    /** Status message of the last response or {@code null} before request has been executed. */
    private String lastStatusMessage;

    /** Whether to disable GZip compression of HTTP content. */
    private boolean disableGZipContent;

    /** Whether to return raw input stream in {@link HttpResponse#getContent()}. */
    private boolean returnRawInputStream;

    /** Response class to parse into. */
    private Class<T> responseClass;

    /** Media HTTP uploader or {@code null} for none. */
    private MediaHttpUploader uploader;

    /** Media HTTP downloader or {@code null} for none. */
    private MediaHttpDownloader downloader;

    /**
     * @param abstractGoogleClient Google client
     * @param requestMethod HTTP Method
     * @param uriTemplate URI template for the path relative to the base URL. If it starts with a "/"
     *        the base path from the base URL will be stripped out. The URI template can also be a
     *        full URL. URI template expansion is done using
     *        {@link UriTemplate#expand(String, String, Object, boolean)}
     * @param httpContent HTTP content or {@code null} for none
     * @param responseClass response class to parse into
     */
    protected AbstractGoogleClientRequest(AbstractGoogleClient abstractGoogleClient, String requestMethod,
            String uriTemplate, HttpContent httpContent, Class<T> responseClass) {
        this.responseClass = Preconditions.checkNotNull(responseClass);
        this.abstractGoogleClient = Preconditions.checkNotNull(abstractGoogleClient);
        this.requestMethod = Preconditions.checkNotNull(requestMethod);
        this.uriTemplate = Preconditions.checkNotNull(uriTemplate);
        this.httpContent = httpContent;
        // application name
        String applicationName = abstractGoogleClient.getApplicationName();
        if (applicationName != null) {
            requestHeaders.setUserAgent(applicationName + " " + USER_AGENT_SUFFIX + "/" + GoogleUtils.VERSION);
        } else {
            requestHeaders.setUserAgent(USER_AGENT_SUFFIX + "/" + GoogleUtils.VERSION);
        }
        // Set the header for the Api Client version (Java and OS version)
        requestHeaders.set(API_VERSION_HEADER, ApiClientVersion.DEFAULT_VERSION);
    }

    /**
     * Internal class to help build the X-Goog-Api-Client header. This header identifies the API
     * Client version and environment.
     *
     * <p>See <a href="https://cloud.google.com/apis/docs/system-parameters"></a>
     */
    static class ApiClientVersion {
        static final String DEFAULT_VERSION = new ApiClientVersion().toString();
        private final String versionString;

        ApiClientVersion() {
            this(getJavaVersion(), OS_NAME.value(), OS_VERSION.value(), GoogleUtils.VERSION);
        }

        ApiClientVersion(String javaVersion, String osName, String osVersion, String clientVersion) {
            StringBuilder sb = new StringBuilder("gl-java/");
            sb.append(formatSemver(javaVersion));
            sb.append(" gdcl/");
            sb.append(formatSemver(clientVersion));
            if (osName != null && osVersion != null) {
                sb.append(" ");
                sb.append(formatName(osName));
                sb.append("/");
                sb.append(formatSemver(osVersion));
            }
            this.versionString = sb.toString();
        }

        public String toString() {
            return versionString;
        }

        private static String getJavaVersion() {
            String version = System.getProperty("java.version");
            if (version == null) {
                return null;
            }

            // Try parsing the full semver
            String formatted = formatSemver(version, null);
            if (formatted != null) {
                return formatted;
            }

            // Some java versions start with the version number and may contain extra info
            // e.g. Java 9 reports something like 9-Debian+0-x-y while Java 11 reports "11"
            Matcher m = Pattern.compile("^(\\d+)[^\\d]?").matcher(version);
            if (m.find()) {
                return m.group(1) + ".0.0";
            }

            return null;
        }

        private static String formatName(String name) {
            // Only lowercase letters, digits, and "-" are allowed
            return name.toLowerCase().replaceAll("[^\\w\\d\\-]", "-");
        }

        private static String formatSemver(String version) {
            return formatSemver(version, version);
        }

        private static String formatSemver(String version, String defaultValue) {
            if (version == null) {
                return null;
            }

            // Take only the semver version: x.y.z-a_b_c -> x.y.z
            Matcher m = Pattern.compile("(\\d+\\.\\d+\\.\\d+).*").matcher(version);
            if (m.find()) {
                return m.group(1);
            } else {
                return defaultValue;
            }
        }
    }

    /** Returns whether to disable GZip compression of HTTP content. */
    public final boolean getDisableGZipContent() {
        return disableGZipContent;
    }

    /**
     * Returns whether response should return raw input stream.
     *
     * @since 1.30
     * */
    public final boolean getReturnRawInputSteam() {
        return returnRawInputStream;
    }

    /**
     * Sets whether to disable GZip compression of HTTP content.
     *
     * <p>
     * By default it is {@code false}.
     * </p>
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public AbstractGoogleClientRequest<T> setDisableGZipContent(boolean disableGZipContent) {
        this.disableGZipContent = disableGZipContent;
        return this;
    }

    /**
     * Sets whether the response should return raw input stream or not.
     *
     * <p>
     * By default it is {@code false}.
     * </p>
     *
     * <p>
     * When the response contains a known content-encoding header, the response stream is wrapped
     * with an InputStream that decodes the content. This fails when we download large files in
     * chunks (see <a href="https://github.com/googleapis/google-api-java-client/issues/1009">#1009
     * </a>). Setting this to true will make the response return the raw input stream.
     * </p>
     *
     * @since 1.30
     */
    public AbstractGoogleClientRequest<T> setReturnRawInputStream(boolean returnRawInputStream) {
        this.returnRawInputStream = returnRawInputStream;
        return this;
    }

    /** Returns the HTTP method. */
    public final String getRequestMethod() {
        return requestMethod;
    }

    /** Returns the URI template for the path relative to the base URL. */
    public final String getUriTemplate() {
        return uriTemplate;
    }

    /** Returns the HTTP content or {@code null} for none. */
    public final HttpContent getHttpContent() {
        return httpContent;
    }

    /**
     * Returns the Google client.
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public AbstractGoogleClient getAbstractGoogleClient() {
        return abstractGoogleClient;
    }

    /** Returns the HTTP headers used for the Google client request. */
    public final HttpHeaders getRequestHeaders() {
        return requestHeaders;
    }

    /**
     * Sets the HTTP headers used for the Google client request.
     *
     * <p>
     * These headers are set on the request after {@link #buildHttpRequest} is called, this means that
     * {@link HttpRequestInitializer#initialize} is called first.
     * </p>
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public AbstractGoogleClientRequest<T> setRequestHeaders(HttpHeaders headers) {
        this.requestHeaders = headers;
        return this;
    }

    /**
     * Returns the HTTP headers of the last response or {@code null} before request has been executed.
     */
    public final HttpHeaders getLastResponseHeaders() {
        return lastResponseHeaders;
    }

    /**
     * Returns the status code of the last response or {@code -1} before request has been executed.
     */
    public final int getLastStatusCode() {
        return lastStatusCode;
    }

    /**
     * Returns the status message of the last response or {@code null} before request has been
     * executed.
     */
    public final String getLastStatusMessage() {
        return lastStatusMessage;
    }

    /** Returns the response class to parse into. */
    public final Class<T> getResponseClass() {
        return responseClass;
    }

    /** Returns the media HTTP Uploader or {@code null} for none. */
    public final MediaHttpUploader getMediaHttpUploader() {
        return uploader;
    }

    /**
     * Initializes the media HTTP uploader based on the media content.
     *
     * @param mediaContent media content
     */
    protected final void initializeMediaUpload(AbstractInputStreamContent mediaContent) {
        HttpRequestFactory requestFactory = abstractGoogleClient.getRequestFactory();
        this.uploader = new MediaHttpUploader(mediaContent, requestFactory.getTransport(),
                requestFactory.getInitializer());
        this.uploader.setInitiationRequestMethod(requestMethod);
        if (httpContent != null) {
            this.uploader.setMetadata(httpContent);
        }
    }

    /** Returns the media HTTP downloader or {@code null} for none. */
    public final MediaHttpDownloader getMediaHttpDownloader() {
        return downloader;
    }

    /** Initializes the media HTTP downloader. */
    protected final void initializeMediaDownload() {
        HttpRequestFactory requestFactory = abstractGoogleClient.getRequestFactory();
        this.downloader = new MediaHttpDownloader(requestFactory.getTransport(), requestFactory.getInitializer());
    }

    /**
     * Creates a new instance of {@link GenericUrl} suitable for use against this service.
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return newly created {@link GenericUrl}
     */
    public GenericUrl buildHttpRequestUrl() {
        return new GenericUrl(UriTemplate.expand(abstractGoogleClient.getBaseUrl(), uriTemplate, this, true));
    }

    /**
     * Create a request suitable for use against this service.
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     */
    public HttpRequest buildHttpRequest() throws IOException {
        return buildHttpRequest(false);
    }

    /**
     * Create a request suitable for use against this service, but using HEAD instead of GET.
     *
     * <p>
     * Only supported when the original request method is GET.
     * </p>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     */
    protected HttpRequest buildHttpRequestUsingHead() throws IOException {
        return buildHttpRequest(true);
    }

    /** Create a request suitable for use against this service. */
    private HttpRequest buildHttpRequest(boolean usingHead) throws IOException {
        Preconditions.checkArgument(uploader == null);
        Preconditions.checkArgument(!usingHead || requestMethod.equals(HttpMethods.GET));
        String requestMethodToUse = usingHead ? HttpMethods.HEAD : requestMethod;
        final HttpRequest httpRequest = getAbstractGoogleClient().getRequestFactory()
                .buildRequest(requestMethodToUse, buildHttpRequestUrl(), httpContent);
        new MethodOverride().intercept(httpRequest);
        httpRequest.setParser(getAbstractGoogleClient().getObjectParser());
        // custom methods may use POST with no content but require a Content-Length header
        if (httpContent == null && (requestMethod.equals(HttpMethods.POST) || requestMethod.equals(HttpMethods.PUT)
                || requestMethod.equals(HttpMethods.PATCH))) {
            httpRequest.setContent(new EmptyContent());
        }
        httpRequest.getHeaders().putAll(requestHeaders);
        if (!disableGZipContent) {
            httpRequest.setEncoding(new GZipEncoding());
        }
        httpRequest.setResponseReturnRawInputStream(returnRawInputStream);
        final HttpResponseInterceptor responseInterceptor = httpRequest.getResponseInterceptor();
        httpRequest.setResponseInterceptor(new HttpResponseInterceptor() {

            public void interceptResponse(HttpResponse response) throws IOException {
                if (responseInterceptor != null) {
                    responseInterceptor.interceptResponse(response);
                }
                if (!response.isSuccessStatusCode() && httpRequest.getThrowExceptionOnExecuteError()) {
                    throw newExceptionOnError(response);
                }
            }
        });
        return httpRequest;
    }

    /**
     * Sends the metadata request to the server and returns the raw metadata {@link HttpResponse}.
     *
     * <p>
     * Callers are responsible for disconnecting the HTTP response by calling
     * {@link HttpResponse#disconnect}. Example usage:
     * </p>
     *
     * <pre>
       HttpResponse response = request.executeUnparsed();
       try {
         // process response..
       } finally {
         response.disconnect();
       }
     * </pre>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return the {@link HttpResponse}
     */
    public HttpResponse executeUnparsed() throws IOException {
        return executeUnparsed(false);
    }

    /**
     * Sends the media request to the server and returns the raw media {@link HttpResponse}.
     *
     * <p>
     * Callers are responsible for disconnecting the HTTP response by calling
     * {@link HttpResponse#disconnect}. Example usage:
     * </p>
     *
     * <pre>
       HttpResponse response = request.executeMedia();
       try {
         // process response..
       } finally {
         response.disconnect();
       }
     * </pre>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return the {@link HttpResponse}
     */
    protected HttpResponse executeMedia() throws IOException {
        set("alt", "media");
        return executeUnparsed();
    }

    /**
     * Sends the metadata request using HEAD to the server and returns the raw metadata
     * {@link HttpResponse} for the response headers.
     *
     * <p>
     * Only supported when the original request method is GET. The response content is assumed to be
     * empty and ignored. Calls {@link HttpResponse#ignore()} so there is no need to disconnect the
     * response. Example usage:
     * </p>
     *
     * <pre>
       HttpResponse response = request.executeUsingHead();
       // look at response.getHeaders()
     * </pre>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return the {@link HttpResponse}
     */
    protected HttpResponse executeUsingHead() throws IOException {
        Preconditions.checkArgument(uploader == null);
        HttpResponse response = executeUnparsed(true);
        response.ignore();
        return response;
    }

    /**
     * Sends the metadata request using the given request method to the server and returns the raw
     * metadata {@link HttpResponse}.
     */
    private HttpResponse executeUnparsed(boolean usingHead) throws IOException {
        HttpResponse response;
        if (uploader == null) {
            // normal request (not upload)
            response = buildHttpRequest(usingHead).execute();
        } else {
            // upload request
            GenericUrl httpRequestUrl = buildHttpRequestUrl();
            HttpRequest httpRequest = getAbstractGoogleClient().getRequestFactory().buildRequest(requestMethod,
                    httpRequestUrl, httpContent);
            boolean throwExceptionOnExecuteError = httpRequest.getThrowExceptionOnExecuteError();

            response = uploader.setInitiationHeaders(requestHeaders).setDisableGZipContent(disableGZipContent)
                    .upload(httpRequestUrl);
            response.getRequest().setParser(getAbstractGoogleClient().getObjectParser());
            // process any error
            if (throwExceptionOnExecuteError && !response.isSuccessStatusCode()) {
                throw newExceptionOnError(response);
            }
        }
        // process response
        lastResponseHeaders = response.getHeaders();
        lastStatusCode = response.getStatusCode();
        lastStatusMessage = response.getStatusMessage();
        return response;
    }

    /**
     * Returns the exception to throw on an HTTP error response as defined by
     * {@link HttpResponse#isSuccessStatusCode()}.
     *
     * <p>
     * It is guaranteed that {@link HttpResponse#isSuccessStatusCode()} is {@code false}. Default
     * implementation is to call {@link HttpResponseException#HttpResponseException(HttpResponse)},
     * but subclasses may override.
     * </p>
     *
     * @param response HTTP response
     * @return exception to throw
     */
    protected IOException newExceptionOnError(HttpResponse response) {
        return new HttpResponseException(response);
    }

    /**
     * Sends the metadata request to the server and returns the parsed metadata response.
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return parsed HTTP response
     */
    public T execute() throws IOException {
        return executeUnparsed().parseAs(responseClass);
    }

    /**
     * Sends the metadata request to the server and returns the metadata content input stream of
     * {@link HttpResponse}.
     *
     * <p>
     * Callers are responsible for closing the input stream after it is processed. Example sample:
     * </p>
     *
     * <pre>
       InputStream is = request.executeAsInputStream();
       try {
         // Process input stream..
       } finally {
         is.close();
       }
     * </pre>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return input stream of the response content
     */
    public InputStream executeAsInputStream() throws IOException {
        return executeUnparsed().getContent();
    }

    /**
     * Sends the media request to the server and returns the media content input stream of
     * {@link HttpResponse}.
     *
     * <p>
     * Callers are responsible for closing the input stream after it is processed. Example sample:
     * </p>
     *
     * <pre>
       InputStream is = request.executeMediaAsInputStream();
       try {
         // Process input stream..
       } finally {
         is.close();
       }
     * </pre>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @return input stream of the response content
     */
    protected InputStream executeMediaAsInputStream() throws IOException {
        return executeMedia().getContent();
    }

    /**
     * Sends the metadata request to the server and writes the metadata content input stream of
     * {@link HttpResponse} into the given destination output stream.
     *
     * <p>
     * This method closes the content of the HTTP response from {@link HttpResponse#getContent()}.
     * </p>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @param outputStream destination output stream
     */
    public void executeAndDownloadTo(OutputStream outputStream) throws IOException {
        executeUnparsed().download(outputStream);
    }

    /**
     * Sends the media request to the server and writes the media content input stream of
     * {@link HttpResponse} into the given destination output stream.
     *
     * <p>
     * This method closes the content of the HTTP response from {@link HttpResponse#getContent()}.
     * </p>
     *
     * <p>
     * Subclasses may override by calling the super implementation.
     * </p>
     *
     * @param outputStream destination output stream
     */
    protected void executeMediaAndDownloadTo(OutputStream outputStream) throws IOException {
        if (downloader == null) {
            executeMedia().download(outputStream);
        } else {
            downloader.download(buildHttpRequestUrl(), requestHeaders, outputStream);
        }
    }

    /**
     * Queues the request into the specified batch request container using the specified error class.
     *
     * <p>
     * Batched requests are then executed when {@link BatchRequest#execute()} is called.
     * </p>
     *
     * @param batchRequest batch request container
     * @param errorClass data class the unsuccessful response will be parsed into or
     *        {@code Void.class} to ignore the content
     * @param callback batch callback
     */
    public final <E> void queue(BatchRequest batchRequest, Class<E> errorClass, BatchCallback<T, E> callback)
            throws IOException {
        Preconditions.checkArgument(uploader == null, "Batching media requests is not supported");
        batchRequest.queue(buildHttpRequest(), getResponseClass(), errorClass, callback);
    }

    // @SuppressWarnings was added here because this is generic class.
    // see: http://stackoverflow.com/questions/4169806/java-casting-object-to-a-generic-type and
    // http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Type%20Erasure
    // for more details
    @SuppressWarnings("unchecked")
    @Override
    public AbstractGoogleClientRequest<T> set(String fieldName, Object value) {
        return (AbstractGoogleClientRequest<T>) super.set(fieldName, value);
    }

    /**
     * Ensures that the specified required parameter is not null or
     * {@link AbstractGoogleClient#getSuppressRequiredParameterChecks()} is true.
     *
     * @param value the value of the required parameter
     * @param name the name of the required parameter
     * @throws IllegalArgumentException if the specified required parameter is null and
     *         {@link AbstractGoogleClient#getSuppressRequiredParameterChecks()} is false
     * @since 1.14
     */
    protected final void checkRequiredParameter(Object value, String name) {
        Preconditions.checkArgument(abstractGoogleClient.getSuppressRequiredParameterChecks() || value != null,
                "Required parameter %s must be specified", name);
    }
}