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

com.google.api.client.googleapis.services.AbstractGoogleClientRequest Maven / Gradle / Ivy

/*
 * 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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
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}.
 *
 * 

Implementation is not thread-safe. * * @param type of the response * @since 1.12 * @author Yaniv Inbar */ public abstract class AbstractGoogleClientRequest 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_CLIENT_HEADER = "X-Goog-Api-Client"; /** * The generated request class will pass this constant as part of the header if the RPC supports * ApiVersion. */ protected static final String API_VERSION_HEADER = "X-Goog-Api-Version"; /** 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 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 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_CLIENT_HEADER, ApiClientVersion.DEFAULT_VERSION); } /** * Internal class to help build the X-Goog-Api-Client header. This header identifies the API * Client version and environment. * *

See */ 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() { // When running the application as a native image, append `-graalvm` to the // version. String imageCode = System.getProperty("org.graalvm.nativeimage.imagecode"); if (imageCode != null && imageCode.equals("runtime")) { String[] tokens = versionString.split(" "); if (tokens.length > 0 && tokens[0].startsWith("gl-java")) { tokens[0] += "-graalvm"; return Joiner.on(" ").join(tokens); } } 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. * *

By default it is {@code false}. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. */ public AbstractGoogleClientRequest setDisableGZipContent(boolean disableGZipContent) { this.disableGZipContent = disableGZipContent; return this; } /** * Sets whether the response should return raw input stream or not. * *

By default it is {@code false}. * *

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 #1009 ). * Setting this to true will make the response return the raw input stream. * * @since 1.30 */ public AbstractGoogleClientRequest 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. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. */ 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. * *

These headers are set on the request after {@link #buildHttpRequest} is called, this means * that {@link HttpRequestInitializer#initialize} is called first. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. */ public AbstractGoogleClientRequest setRequestHeaders(HttpHeaders headers) { this.requestHeaders = headers; return this; } /** Returns the ApiVersion set in the headers. If ApiVersion is not set, null is returned. */ @VisibleForTesting String getApiVersionHeader() { return (String) requestHeaders.get(API_VERSION_HEADER); } /** * 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 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(); String applicationName = abstractGoogleClient.getApplicationName(); HttpRequestInitializer requestInitializer = mediaUploadRequestUserAgentInitializer(applicationName, requestFactory.getInitializer()); this.uploader = new MediaHttpUploader(mediaContent, requestFactory.getTransport(), requestInitializer); this.uploader.setInitiationRequestMethod(requestMethod); if (httpContent != null) { this.uploader.setMetadata(httpContent); } } private static HttpRequestInitializer mediaUploadRequestUserAgentInitializer( final String applicationName, final HttpRequestInitializer originalInitializer) { if (applicationName == null) { return originalInitializer; } if (originalInitializer == null) { return new HttpRequestInitializer() { @Override public void initialize(HttpRequest request) { HttpHeaders headers = request.getHeaders(); headers.setUserAgent(applicationName); } }; } else { return new HttpRequestInitializer() { @Override public void initialize(HttpRequest request) throws IOException { originalInitializer.initialize(request); HttpHeaders headers = request.getHeaders(); headers.setUserAgent(applicationName); } }; } } /** 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. * *

Subclasses may override by calling the super implementation. * * @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. * *

Subclasses may override by calling the super implementation. */ public HttpRequest buildHttpRequest() throws IOException { return buildHttpRequest(false); } /** * Create a request suitable for use against this service, but using HEAD instead of GET. * *

Only supported when the original request method is GET. * *

Subclasses may override by calling the super implementation. */ 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}. * *

Callers are responsible for disconnecting the HTTP response by calling {@link * HttpResponse#disconnect}. Example usage: * *

{@code
   * HttpResponse response = request.executeUnparsed();
   * try {
   *   // process response..
   * } finally {
   *   response.disconnect();
   * }
   * }
* *

Subclasses may override by calling the super implementation. * * @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}. * *

Callers are responsible for disconnecting the HTTP response by calling {@link * HttpResponse#disconnect}. Example usage: * *

{@code
   * HttpResponse response = request.executeMedia();
   * try {
   *   // process response..
   * } finally {
   *   response.disconnect();
   * }
   * }
* *

Subclasses may override by calling the super implementation. * * @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. * *

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: * *

{@code
   * HttpResponse response = request.executeUsingHead();
   * // look at response.getHeaders()
   * }
* *

Subclasses may override by calling the super implementation. * * @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()}. * *

It is guaranteed that {@link HttpResponse#isSuccessStatusCode()} is {@code false}. Default * implementation is to call {@link HttpResponseException#HttpResponseException(HttpResponse)}, * but subclasses may override. * * @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. * *

Subclasses may override by calling the super implementation. * * @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}. * *

Callers are responsible for closing the input stream after it is processed. Example sample: * *

{@code
   * InputStream is = request.executeAsInputStream();
   * try {
   *   // Process input stream..
   * } finally {
   *   is.close();
   * }
   * }
* *

Subclasses may override by calling the super implementation. * * @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}. * *

Callers are responsible for closing the input stream after it is processed. Example sample: * *

{@code
   * InputStream is = request.executeMediaAsInputStream();
   * try {
   *   // Process input stream..
   * } finally {
   *   is.close();
   * }
   * }
* *

Subclasses may override by calling the super implementation. * * @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. * *

This method closes the content of the HTTP response from {@link HttpResponse#getContent()}. * *

Subclasses may override by calling the super implementation. * * @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. * *

This method closes the content of the HTTP response from {@link HttpResponse#getContent()}. * *

Subclasses may override by calling the super implementation. * * @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. * *

Batched requests are then executed when {@link BatchRequest#execute()} is called. * * @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 void queue( BatchRequest batchRequest, Class errorClass, BatchCallback 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 set(String fieldName, Object value) { return (AbstractGoogleClientRequest) 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy