com.google.api.gax.httpjson.HttpJsonCallContext Maven / Gradle / Ivy
Show all versions of gax-httpjson Show documentation
/*
* Copyright 2017 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.api.gax.httpjson;
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.rpc.ApiCallContext;
import com.google.api.gax.rpc.ApiExceptionFactory;
import com.google.api.gax.rpc.EndpointContext;
import com.google.api.gax.rpc.StatusCode;
import com.google.api.gax.rpc.TransportChannel;
import com.google.api.gax.rpc.UnauthenticatedException;
import com.google.api.gax.rpc.internal.ApiCallContextOptions;
import com.google.api.gax.rpc.internal.Headers;
import com.google.api.gax.tracing.ApiTracer;
import com.google.api.gax.tracing.BaseApiTracer;
import com.google.auth.Credentials;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.threeten.bp.Duration;
import org.threeten.bp.Instant;
/**
* HttpJsonCallContext encapsulates context data used to make an http-json call.
*
* HttpJsonCallContext is immutable in the sense that none of its methods modifies the
* HttpJsonCallContext itself or the underlying data. Methods of the form {@code withX} return
* copies of the object, but with one field changed. The immutability and thread safety of the
* arguments solely depends on the arguments themselves.
*/
public final class HttpJsonCallContext implements ApiCallContext {
private static final HttpJsonStatusCode UNAUTHENTICATED_STATUS_CODE =
HttpJsonStatusCode.of(StatusCode.Code.UNAUTHENTICATED);
private final HttpJsonChannel channel;
private final HttpJsonCallOptions callOptions;
@Nullable private final Duration timeout;
@Nullable private final Duration streamWaitTimeout;
@Nullable private final Duration streamIdleTimeout;
private final ImmutableMap> extraHeaders;
private final ApiCallContextOptions options;
private final ApiTracer tracer;
@Nullable private final RetrySettings retrySettings;
@Nullable private final ImmutableSet retryableCodes;
private final EndpointContext endpointContext;
/** Returns an empty instance. */
public static HttpJsonCallContext createDefault() {
return new HttpJsonCallContext(
null,
HttpJsonCallOptions.newBuilder().build(),
null,
null,
null,
ImmutableMap.of(),
ApiCallContextOptions.getDefaultOptions(),
null,
null,
null,
null);
}
public static HttpJsonCallContext of(HttpJsonChannel channel, HttpJsonCallOptions options) {
return new HttpJsonCallContext(
channel,
options,
null,
null,
null,
ImmutableMap.of(),
ApiCallContextOptions.getDefaultOptions(),
null,
null,
null,
null);
}
private HttpJsonCallContext(
HttpJsonChannel channel,
HttpJsonCallOptions callOptions,
Duration timeout,
Duration streamWaitTimeout,
Duration streamIdleTimeout,
ImmutableMap> extraHeaders,
ApiCallContextOptions options,
ApiTracer tracer,
RetrySettings defaultRetrySettings,
Set defaultRetryableCodes,
EndpointContext endpointContext) {
this.channel = channel;
this.callOptions = callOptions;
this.timeout = timeout;
this.streamWaitTimeout = streamWaitTimeout;
this.streamIdleTimeout = streamIdleTimeout;
this.extraHeaders = extraHeaders;
this.options = options;
this.tracer = tracer;
this.retrySettings = defaultRetrySettings;
this.retryableCodes =
defaultRetryableCodes == null ? null : ImmutableSet.copyOf(defaultRetryableCodes);
this.endpointContext = endpointContext;
}
/**
* Returns inputContext cast to {@link HttpJsonCallContext}, or an empty {@link
* HttpJsonCallContext} if inputContext is null.
*
* @param inputContext the {@link ApiCallContext} to cast if it is not null
*/
@Override
public HttpJsonCallContext nullToSelf(ApiCallContext inputContext) {
HttpJsonCallContext httpJsonCallContext;
if (inputContext == null) {
httpJsonCallContext = this;
} else {
if (!(inputContext instanceof HttpJsonCallContext)) {
throw new IllegalArgumentException(
"context must be an instance of HttpJsonCallContext, but found "
+ inputContext.getClass().getName());
}
httpJsonCallContext = (HttpJsonCallContext) inputContext;
}
return httpJsonCallContext;
}
@Override
public HttpJsonCallContext merge(ApiCallContext inputCallContext) {
if (inputCallContext == null) {
return this;
}
if (!(inputCallContext instanceof HttpJsonCallContext)) {
throw new IllegalArgumentException(
"context must be an instance of HttpJsonCallContext, but found "
+ inputCallContext.getClass().getName());
}
HttpJsonCallContext httpJsonCallContext = (HttpJsonCallContext) inputCallContext;
HttpJsonChannel newChannel = httpJsonCallContext.channel;
if (newChannel == null) {
newChannel = this.channel;
}
// Do deep merge of callOptions
HttpJsonCallOptions newCallOptions = callOptions.merge(httpJsonCallContext.callOptions);
Duration newTimeout = httpJsonCallContext.timeout;
if (newTimeout == null) {
newTimeout = this.timeout;
}
Duration newStreamWaitTimeout = httpJsonCallContext.streamWaitTimeout;
if (newStreamWaitTimeout == null) {
newStreamWaitTimeout = streamWaitTimeout;
}
Duration newStreamIdleTimeout = httpJsonCallContext.streamIdleTimeout;
if (newStreamIdleTimeout == null) {
newStreamIdleTimeout = streamIdleTimeout;
}
ImmutableMap> newExtraHeaders =
Headers.mergeHeaders(extraHeaders, httpJsonCallContext.extraHeaders);
ApiCallContextOptions newOptions = options.merge(httpJsonCallContext.options);
ApiTracer newTracer = httpJsonCallContext.tracer;
if (newTracer == null) {
newTracer = this.tracer;
}
RetrySettings newRetrySettings = httpJsonCallContext.retrySettings;
if (newRetrySettings == null) {
newRetrySettings = this.retrySettings;
}
Set newRetryableCodes = httpJsonCallContext.retryableCodes;
if (newRetryableCodes == null) {
newRetryableCodes = this.retryableCodes;
}
// The EndpointContext is not updated as there should be no reason for a user
// to update this.
return new HttpJsonCallContext(
newChannel,
newCallOptions,
newTimeout,
newStreamWaitTimeout,
newStreamIdleTimeout,
newExtraHeaders,
newOptions,
newTracer,
newRetrySettings,
newRetryableCodes,
endpointContext);
}
@Override
public HttpJsonCallContext withCredentials(Credentials newCredentials) {
HttpJsonCallOptions.Builder builder =
callOptions != null ? callOptions.toBuilder() : HttpJsonCallOptions.newBuilder();
return withCallOptions(builder.setCredentials(newCredentials).build());
}
@Override
public HttpJsonCallContext withTransportChannel(TransportChannel inputChannel) {
Preconditions.checkNotNull(inputChannel);
if (!(inputChannel instanceof HttpJsonTransportChannel)) {
throw new IllegalArgumentException(
"Expected HttpJsonTransportChannel, got " + inputChannel.getClass().getName());
}
HttpJsonTransportChannel transportChannel = (HttpJsonTransportChannel) inputChannel;
return withChannel(transportChannel.getChannel());
}
@Override
public HttpJsonCallContext withEndpointContext(EndpointContext endpointContext) {
Preconditions.checkNotNull(endpointContext);
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
endpointContext);
}
@Override
public HttpJsonCallContext withTimeout(Duration timeout) {
// Default RetrySettings use 0 for RPC timeout. Treat that as disabled timeouts.
if (timeout != null && (timeout.isZero() || timeout.isNegative())) {
timeout = null;
}
// Prevent expanding deadlines
if (timeout != null && this.timeout != null && this.timeout.compareTo(timeout) <= 0) {
return this;
}
return new HttpJsonCallContext(
this.channel,
this.callOptions,
timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
@Nullable
@Override
public Duration getTimeout() {
return timeout;
}
@Override
public HttpJsonCallContext withStreamWaitTimeout(@Nullable Duration streamWaitTimeout) {
if (streamWaitTimeout != null) {
Preconditions.checkArgument(
streamWaitTimeout.compareTo(Duration.ZERO) >= 0, "Invalid timeout: < 0 s");
}
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
/**
* The stream wait timeout set for this context.
*
* @see ApiCallContext#withStreamWaitTimeout(Duration)
*/
@Override
@Nullable
public Duration getStreamWaitTimeout() {
return streamWaitTimeout;
}
@Override
public HttpJsonCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeout) {
if (streamIdleTimeout != null) {
Preconditions.checkArgument(
streamIdleTimeout.compareTo(Duration.ZERO) >= 0, "Invalid timeout: < 0 s");
}
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
/**
* The stream idle timeout set for this context.
*
* @see ApiCallContext#withStreamIdleTimeout(Duration)
*/
@Override
@Nullable
public Duration getStreamIdleTimeout() {
return streamIdleTimeout;
}
@BetaApi("The surface for extra headers is not stable yet and may change in the future.")
@Override
public ApiCallContext withExtraHeaders(Map> extraHeaders) {
Preconditions.checkNotNull(extraHeaders);
ImmutableMap> newExtraHeaders =
Headers.mergeHeaders(this.extraHeaders, extraHeaders);
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
newExtraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
@BetaApi("The surface for extra headers is not stable yet and may change in the future.")
@Override
public Map> getExtraHeaders() {
return extraHeaders;
}
/** {@inheritDoc} */
@Override
public ApiCallContext withOption(Key key, T value) {
ApiCallContextOptions newOptions = options.withOption(key, value);
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
newOptions,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
/** {@inheritDoc} */
@Override
public T getOption(Key key) {
return options.getOption(key);
}
/**
* Validate the Universe Domain to ensure that the user configured Universe Domain and the
* Credentials' Universe Domain match. An exception will be raised if there are any issues when
* trying to validate (i.e. unable to access the universe domain).
*
* @throws UnauthenticatedException Thrown if the universe domain that the user configured does
* not match the Credential's universe domain or if the client library is unable to retrieve
* the Universe Domain from the Credentials.
*/
@InternalApi
public void validateUniverseDomain() {
try {
endpointContext.validateUniverseDomain(
getCallOptions().getCredentials(), UNAUTHENTICATED_STATUS_CODE);
} catch (IOException e) {
// All instances of IOException from endpointContext's `validateUniverseDomain()`
// call should be an Auth Exception
throw ApiExceptionFactory.createException(
EndpointContext.UNABLE_TO_RETRIEVE_CREDENTIALS_ERROR_MESSAGE,
e,
UNAUTHENTICATED_STATUS_CODE,
false);
}
}
public HttpJsonChannel getChannel() {
return channel;
}
public HttpJsonCallOptions getCallOptions() {
return callOptions;
}
@Deprecated
@Nullable
public Instant getDeadline() {
return getCallOptions() != null ? getCallOptions().getDeadline() : null;
}
@Deprecated
@Nullable
public Credentials getCredentials() {
return getCallOptions() != null ? getCallOptions().getCredentials() : null;
}
@Override
public RetrySettings getRetrySettings() {
return retrySettings;
}
@Override
public HttpJsonCallContext withRetrySettings(RetrySettings retrySettings) {
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
retrySettings,
this.retryableCodes,
this.endpointContext);
}
@Override
public Set getRetryableCodes() {
return retryableCodes;
}
@Override
public HttpJsonCallContext withRetryableCodes(Set retryableCodes) {
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
retryableCodes,
this.endpointContext);
}
public HttpJsonCallContext withChannel(HttpJsonChannel newChannel) {
return new HttpJsonCallContext(
newChannel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
public HttpJsonCallContext withCallOptions(HttpJsonCallOptions newCallOptions) {
return new HttpJsonCallContext(
this.channel,
newCallOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
this.tracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
@Deprecated
public HttpJsonCallContext withDeadline(Instant newDeadline) {
HttpJsonCallOptions.Builder builder =
callOptions != null ? callOptions.toBuilder() : HttpJsonCallOptions.newBuilder();
return withCallOptions(builder.setDeadline(newDeadline).build());
}
@Nonnull
@Override
public ApiTracer getTracer() {
if (tracer == null) {
return BaseApiTracer.getInstance();
}
return tracer;
}
/** {@inheritDoc} */
@Override
public HttpJsonCallContext withTracer(@Nonnull ApiTracer newTracer) {
Preconditions.checkNotNull(newTracer);
return new HttpJsonCallContext(
this.channel,
this.callOptions,
this.timeout,
this.streamWaitTimeout,
this.streamIdleTimeout,
this.extraHeaders,
this.options,
newTracer,
this.retrySettings,
this.retryableCodes,
this.endpointContext);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HttpJsonCallContext that = (HttpJsonCallContext) o;
return Objects.equals(this.channel, that.channel)
&& Objects.equals(this.callOptions, that.callOptions)
&& Objects.equals(this.timeout, that.timeout)
&& Objects.equals(this.extraHeaders, that.extraHeaders)
&& Objects.equals(this.options, that.options)
&& Objects.equals(this.tracer, that.tracer)
&& Objects.equals(this.retrySettings, that.retrySettings)
&& Objects.equals(this.retryableCodes, that.retryableCodes)
&& Objects.equals(this.endpointContext, that.endpointContext);
}
@Override
public int hashCode() {
return Objects.hash(
channel,
callOptions,
timeout,
extraHeaders,
options,
tracer,
retrySettings,
retryableCodes,
endpointContext);
}
}