![JAR search and dependency download from the Maven repository](/logo.png)
com.microsoft.rest.v2.RestProxy Maven / Gradle / Ivy
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/
package com.microsoft.rest.v2;
import com.microsoft.rest.v2.annotations.ResumeOperation;
import com.microsoft.rest.v2.credentials.ServiceClientCredentials;
import com.microsoft.rest.v2.http.ContentType;
import com.microsoft.rest.v2.http.HttpHeader;
import com.microsoft.rest.v2.http.HttpHeaders;
import com.microsoft.rest.v2.http.HttpMethod;
import com.microsoft.rest.v2.http.HttpPipeline;
import com.microsoft.rest.v2.http.HttpPipelineBuilder;
import com.microsoft.rest.v2.http.HttpRequest;
import com.microsoft.rest.v2.http.HttpResponse;
import com.microsoft.rest.v2.http.UrlBuilder;
import com.microsoft.rest.v2.policy.CookiePolicyFactory;
import com.microsoft.rest.v2.policy.CredentialsPolicyFactory;
import com.microsoft.rest.v2.policy.DecodingPolicyFactory;
import com.microsoft.rest.v2.policy.RequestPolicyFactory;
import com.microsoft.rest.v2.policy.RetryPolicyFactory;
import com.microsoft.rest.v2.policy.UserAgentPolicyFactory;
import com.microsoft.rest.v2.protocol.HttpResponseDecoder;
import com.microsoft.rest.v2.protocol.SerializerAdapter;
import com.microsoft.rest.v2.protocol.SerializerEncoding;
import com.microsoft.rest.v2.serializer.JacksonAdapter;
import com.microsoft.rest.v2.util.FlowableUtil;
import com.microsoft.rest.v2.util.TypeUtil;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.exceptions.Exceptions;
import io.reactivex.functions.Function;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Map;
/**
* This class can be used to create a proxy implementation for a provided Swagger generated
* interface. RestProxy can create proxy implementations for interfaces with methods that return
* deserialized Java objects as well as asynchronous Single objects that resolve to a deserialized
* Java object.
*/
public class RestProxy implements InvocationHandler {
private final HttpPipeline httpPipeline;
private final SerializerAdapter> serializer;
private final SwaggerInterfaceParser interfaceParser;
/**
* Create a new instance of RestProxy.
* @param httpPipeline The RequestPolicy and HttpClient httpPipeline that will be used to send HTTP
* requests.
* @param serializer The serializer that will be used to convert response bodies to POJOs.
* @param interfaceParser The parser that contains information about the swagger interface that
* this RestProxy "implements".
*/
public RestProxy(HttpPipeline httpPipeline, SerializerAdapter> serializer, SwaggerInterfaceParser interfaceParser) {
this.httpPipeline = httpPipeline;
this.serializer = serializer;
this.interfaceParser = interfaceParser;
}
/**
* Get the SwaggerMethodParser for the provided method. The Method must exist on the Swagger
* interface that this RestProxy was created to "implement".
* @param method The method to get a SwaggerMethodParser for.
* @return The SwaggerMethodParser for the provided method.
*/
private SwaggerMethodParser methodParser(Method method) {
return interfaceParser.methodParser(method);
}
/**
* Get the SerializerAdapter used by this RestProxy.
* @return The SerializerAdapter used by this RestProxy.
*/
public SerializerAdapter> serializer() {
return serializer;
}
/**
* Send the provided request asynchronously, applying any request policies provided to the HttpClient instance.
* @param request The HTTP request to send.
* @return A {@link Single} representing the HTTP response that will arrive asynchronously.
*/
public Single sendHttpRequestAsync(HttpRequest request) {
return httpPipeline.sendRequestAsync(request);
}
@Override
public Object invoke(Object proxy, final Method method, Object[] args) {
try {
SwaggerMethodParser methodParser = null;
HttpRequest request = null;
if (method.isAnnotationPresent(ResumeOperation.class)) {
OperationDescription opDesc = (OperationDescription) args[0];
Method resumeMethod = null;
Method[] methods = method.getDeclaringClass().getMethods();
for (Method origMethod : methods) {
if (origMethod.getName().equals(opDesc.methodName())) {
resumeMethod = origMethod;
break;
}
}
methodParser = methodParser(resumeMethod);
request = createHttpRequest(opDesc, methodParser, args);
final Type returnType = methodParser.returnType();
return handleResumeOperation(request, opDesc, methodParser, returnType);
} else {
methodParser = methodParser(method);
request = createHttpRequest(methodParser, args);
final Single asyncResponse = sendHttpRequestAsync(request);
final Type returnType = methodParser.returnType();
return handleAsyncHttpResponse(request, asyncResponse, methodParser, returnType);
}
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
/**
* Create a HttpRequest for the provided Swagger method using the provided arguments.
* @param methodParser The Swagger method parser to use.
* @param args The arguments to use to populate the method's annotation values.
* @return A HttpRequest.
* @throws IOException Thrown if the body contents cannot be serialized.
*/
@SuppressWarnings("unchecked")
private HttpRequest createHttpRequest(SwaggerMethodParser methodParser, Object[] args) throws IOException {
UrlBuilder urlBuilder;
// Sometimes people pass in a full URL for the value of their PathParam annotated argument.
// This definitely happens in paging scenarios. In that case, just use the full URL and
// ignore the Host annotation.
final String path = methodParser.path(args);
final UrlBuilder pathUrlBuilder = UrlBuilder.parse(path);
if (pathUrlBuilder.scheme() != null) {
urlBuilder = pathUrlBuilder;
}
else {
urlBuilder = new UrlBuilder();
// We add path to the UrlBuilder first because this is what is
// provided to the HTTP Method annotation. Any path substitutions
// from other substitution annotations will overwrite this.
urlBuilder.withPath(path);
final String scheme = methodParser.scheme(args);
urlBuilder.withScheme(scheme);
final String host = methodParser.host(args);
urlBuilder.withHost(host);
}
for (final EncodedParameter queryParameter : methodParser.encodedQueryParameters(args)) {
urlBuilder.setQueryParameter(queryParameter.name(), queryParameter.encodedValue());
}
final URL url = urlBuilder.toURL();
final HttpRequest request = new HttpRequest(methodParser.fullyQualifiedMethodName(), methodParser.httpMethod(), url, new HttpResponseDecoder(methodParser, serializer));
request.withContext(methodParser.context(args));
final Object bodyContentObject = methodParser.body(args);
if (bodyContentObject == null) {
request.headers().set("Content-Length", "0");
} else {
String contentType = methodParser.bodyContentType();
if (contentType == null || contentType.isEmpty()) {
if (bodyContentObject instanceof byte[] || bodyContentObject instanceof String) {
contentType = ContentType.APPLICATION_OCTET_STREAM;
}
else {
contentType = ContentType.APPLICATION_JSON;
}
}
request.headers().set("Content-Type", contentType);
boolean isJson = false;
final String[] contentTypeParts = contentType.split(";");
for (String contentTypePart : contentTypeParts) {
if (contentTypePart.trim().equalsIgnoreCase(ContentType.APPLICATION_JSON)) {
isJson = true;
break;
}
}
if (isJson) {
final String bodyContentString = serializer.serialize(bodyContentObject, SerializerEncoding.JSON);
request.withBody(bodyContentString);
}
else if (FlowableUtil.isFlowableByteBuffer(methodParser.bodyJavaType())) {
// Content-Length or Transfer-Encoding: chunked must be provided by a user-specified header when a Flowable is given for the body.
//noinspection ConstantConditions
request.withBody((Flowable) bodyContentObject);
}
else if (bodyContentObject instanceof byte[]) {
request.withBody((byte[]) bodyContentObject);
}
else if (bodyContentObject instanceof String) {
final String bodyContentString = (String) bodyContentObject;
if (!bodyContentString.isEmpty()) {
request.withBody(bodyContentString);
}
}
else {
final String bodyContentString = serializer.serialize(bodyContentObject, SerializerEncoding.fromHeaders(request.headers()));
request.withBody(bodyContentString);
}
}
// Headers from Swagger method arguments always take precedence over inferred headers from body types
for (final HttpHeader header : methodParser.headers(args)) {
request.withHeader(header.name(), header.value());
}
return request;
}
/**
* Create a HttpRequest for the provided Swagger method using the provided arguments.
* @param methodParser The Swagger method parser to use.
* @param args The arguments to use to populate the method's annotation values.
* @return A HttpRequest.
* @throws IOException Thrown if the body contents cannot be serialized.
*/
@SuppressWarnings("unchecked")
private HttpRequest createHttpRequest(OperationDescription operationDescription, SwaggerMethodParser methodParser, Object[] args) throws IOException {
final HttpRequest request = new HttpRequest(
methodParser.fullyQualifiedMethodName(),
methodParser.httpMethod(),
operationDescription.url(),
new HttpResponseDecoder(methodParser, serializer));
final Object bodyContentObject = methodParser.body(args);
if (bodyContentObject == null) {
request.headers().set("Content-Length", "0");
} else {
String contentType = methodParser.bodyContentType();
if (contentType == null || contentType.isEmpty()) {
if (bodyContentObject instanceof byte[] || bodyContentObject instanceof String) {
contentType = ContentType.APPLICATION_OCTET_STREAM;
}
else {
contentType = ContentType.APPLICATION_JSON;
}
}
request.headers().set("Content-Type", contentType);
boolean isJson = false;
final String[] contentTypeParts = contentType.split(";");
for (String contentTypePart : contentTypeParts) {
if (contentTypePart.trim().equalsIgnoreCase(ContentType.APPLICATION_JSON)) {
isJson = true;
break;
}
}
if (isJson) {
final String bodyContentString = serializer.serialize(bodyContentObject, SerializerEncoding.JSON);
request.withBody(bodyContentString);
}
else if (FlowableUtil.isFlowableByteBuffer(methodParser.bodyJavaType())) {
// Content-Length or Transfer-Encoding: chunked must be provided by a user-specified header when a Flowable is given for the body.
//noinspection ConstantConditions
request.withBody((Flowable) bodyContentObject);
}
else if (bodyContentObject instanceof byte[]) {
request.withBody((byte[]) bodyContentObject);
}
else if (bodyContentObject instanceof String) {
final String bodyContentString = (String) bodyContentObject;
if (!bodyContentString.isEmpty()) {
request.withBody(bodyContentString);
}
}
else {
final String bodyContentString = serializer.serialize(bodyContentObject, SerializerEncoding.fromHeaders(request.headers()));
request.withBody(bodyContentString);
}
}
// Headers from Swagger method arguments always take precedence over inferred headers from body types
for (final String headerName : operationDescription.headers().keySet()) {
request.withHeader(headerName, operationDescription.headers().get(headerName));
}
return request;
}
private Exception instantiateUnexpectedException(SwaggerMethodParser methodParser, HttpResponse response, String responseContent) {
final int responseStatusCode = response.statusCode();
final Class extends RestException> exceptionType = methodParser.exceptionType();
final Class> exceptionBodyType = methodParser.exceptionBodyType();
String contentType = response.headerValue("Content-Type");
String bodyRepresentation;
if ("application/octet-stream".equalsIgnoreCase(contentType)) {
bodyRepresentation = "(" + response.headerValue("Content-Length") + "-byte body)";
} else {
bodyRepresentation = responseContent.isEmpty() ? "(empty body)" : "\"" + responseContent + "\"";
}
Exception result;
try {
final Constructor extends RestException> exceptionConstructor = exceptionType.getConstructor(String.class, HttpResponse.class, exceptionBodyType);
result = exceptionConstructor.newInstance("Status code " + responseStatusCode + ", " + bodyRepresentation, response, response.deserializedBody());
} catch (ReflectiveOperationException e) {
String message = "Status code " + responseStatusCode + ", but an instance of "
+ exceptionType.getCanonicalName() + " cannot be created."
+ " Response body: " + bodyRepresentation;
result = new IOException(message, e);
}
return result;
}
private Single ensureExpectedStatus(Single asyncResponse, final SwaggerMethodParser methodParser) {
return asyncResponse
.flatMap(new Function>() {
@Override
public Single extends HttpResponse> apply(HttpResponse httpResponse) {
return ensureExpectedStatus(httpResponse, methodParser);
}
});
}
private Single ensureExpectedStatus(final HttpResponse response, final SwaggerMethodParser methodParser) {
return ensureExpectedStatus(response, methodParser, null);
}
/**
* Ensure that the provided HttpResponse has a status code that is defined in the provided
* SwaggerMethodParser or is in the int[] of additional allowed status codes. If the
* HttpResponse's status code is not allowed, then an exception will be thrown.
* @param response The HttpResponse to check.
* @param methodParser The method parser that contains information about the service interface
* method that initiated the HTTP request.
* @param additionalAllowedStatusCodes Additional allowed status codes that are permitted based
* on the context of the HTTP request.
* @return An async-version of the provided HttpResponse.
*/
public Single ensureExpectedStatus(final HttpResponse response, final SwaggerMethodParser methodParser, int[] additionalAllowedStatusCodes) {
final int responseStatusCode = response.statusCode();
final Single asyncResult;
if (!methodParser.isExpectedResponseStatusCode(responseStatusCode, additionalAllowedStatusCodes)) {
asyncResult = response.bodyAsString().flatMap(new Function>() {
@Override
public Single apply(String responseBody) throws Exception {
return Single.error(instantiateUnexpectedException(methodParser, response, responseBody));
}
});
} else {
asyncResult = Single.just(response);
}
return asyncResult;
}
/**
* @param entityType the RestResponse subtype to get a constructor for.
* @return a Constructor which produces an instance of a RestResponse subtype.
*/
@SuppressWarnings("unchecked")
public Constructor extends RestResponse, ?>> getRestResponseConstructor(Type entityType) {
Class extends RestResponse, ?>> rawEntityType = (Class extends RestResponse, ?>>) TypeUtil.getRawClass(entityType);
try {
Constructor extends RestResponse, ?>> ctor = null;
for (Constructor> c : rawEntityType.getDeclaredConstructors()) {
// Generic constructor arguments turn into Object.
// Because some child class constructors have a more specific concrete type,
// there's not a single type we can check for the headers or body parameters.
if (c.getParameterTypes().length == 5
&& c.getParameterTypes()[0].equals(HttpRequest.class)
&& c.getParameterTypes()[1].equals(Integer.TYPE)
&& c.getParameterTypes()[3].equals(Map.class)) {
ctor = (Constructor extends RestResponse, ?>>) c;
}
}
if (ctor == null) {
throw new NoSuchMethodException("No appropriate constructor found for type " + rawEntityType.getName());
}
return ctor;
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private Single> handleRestResponseReturnTypeAsync(HttpResponse response, SwaggerMethodParser methodParser, Type entityType) {
final int responseStatusCode = response.statusCode();
try {
Single> asyncResult;
if (TypeUtil.isTypeOrSubTypeOf(entityType, RestResponse.class)) {
Constructor extends RestResponse, ?>> responseConstructor = getRestResponseConstructor(entityType);
Type[] deserializedTypes = TypeUtil.getTypeArguments(TypeUtil.getSuperType(entityType, RestResponse.class));
HttpHeaders responseHeaders = response.headers();
Object deserializedHeaders = response.deserializedHeaders();
Type bodyType = deserializedTypes[1];
if (TypeUtil.isTypeOrSubTypeOf(bodyType, Void.class)) {
asyncResult = response.body().lastElement().ignoreElement()
.andThen(Single.just(responseConstructor.newInstance(response.request(), responseStatusCode, deserializedHeaders, responseHeaders.toMap(), null)));
} else {
final Map rawHeaders = responseHeaders.toMap();
asyncResult = handleBodyReturnTypeAsync(response, methodParser, bodyType)
.map((Function
© 2015 - 2025 Weber Informatics LLC | Privacy Policy