Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Prototype] Support composite ObjectSerializers, add XmlSerializer #43433

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions sdk/clientcore/core/checkstyle-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

<suppressions>
<suppress files="io.clientcore.core.implementation.util.InternalContext.java" checks="JavadocMethodCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RangeReplaceSubstitution.java" checks="MissingJavadocMethodCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RequestDataConfiguration.java" checks="MissingJavadocMethodCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RestProxyBase.java" checks="MissingJavadocMethodCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RestProxyImpl.java" checks="MissingJavadocMethodCheck" />
Expand All @@ -15,14 +14,12 @@
<suppress files="io.clientcore.core.implementation.util.ServerSentEventHelper.java" checks="MissingJavadocMethodCheck" />
<suppress files="io.clientcore.core.implementation.util.Slf4jLoggerShim.java" checks="MissingJavadocMethodCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.HeaderSubstitution.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RangeReplaceSubstitution.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RequestDataConfiguration.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RestProxyBase.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RestProxyImpl.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.util.EnvironmentConfiguration.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.util.ImplUtils.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.util.Slf4jLoggerShim.java" checks="MissingJavadocTypeCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.RequestDataConfiguration.java" checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" />
<suppress files="io.clientcore.core.implementation.util.EnvironmentConfiguration.java" checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" />
<suppress files="io.clientcore.core.implementation.util.Slf4jLoggerShim.java" checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" />
<suppress files="io.clientcore.core.implementation.ReflectionSerializable.java" checks="com.azure.tools.checkstyle.checks.JavadocThrowsChecks" />
Expand Down
11 changes: 7 additions & 4 deletions sdk/clientcore/core/spotbugs-exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<Class name="io.clientcore.core.implementation.http.rest.ResponseExceptionConstructorCache" />
<Class name="io.clientcore.core.implementation.http.rest.RestProxyBase" />
<Class name="io.clientcore.core.implementation.http.rest.RestProxyImpl" />
<Class name="io.clientcore.core.implementation.http.serializer.DefaultJsonSerializer" />
<Class name="io.clientcore.core.implementation.http.serializer.CompositeSerializer" />
<Class name="io.clientcore.core.implementation.util.DateTimeRfc1123" />
<Class name="io.clientcore.core.implementation.util.InternalContext" />
<Class name="io.clientcore.core.implementation.util.Providers" />
Expand All @@ -41,6 +41,8 @@
<Class name="io.clientcore.core.util.binarydata.InputStreamBinaryData" />
<Class name="io.clientcore.core.util.binarydata.ListByteBufferBinaryData" />
<Class name="io.clientcore.core.util.configuration.Configuration" />
<Class name="io.clientcore.core.util.serializer.JsonSerializer" />
<Class name="io.clientcore.core.util.serializer.XmlSerializer" />
</Or>
</Match>
<Match>
Expand Down Expand Up @@ -122,7 +124,7 @@
<Class name="io.clientcore.core.shared.HttpClientTestsServer" />
<Class name="io.clientcore.core.util.ClientLoggerTests" />
<Class name="io.clientcore.core.util.binarydata.BinaryDataTest" />
<Class name="io.clientcore.core.util.serializer.DefaultJsonSerializerTests" />
<Class name="io.clientcore.core.util.serializer.JsonSerializerTests" />
</Or>
</Match>
<Match>
Expand Down Expand Up @@ -256,11 +258,12 @@
<Match>
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
<Or>
<Class name="io.clientcore.core.implementation.http.serializer.DefaultJsonSerializer" />
<Class name="io.clientcore.core.implementation.util.Base64Uri" />
<Class name="io.clientcore.core.serialization.json.implementation.DefaultJsonReader" />
<Class name="io.clientcore.core.util.TestUtils" />
<Class name="io.clientcore.core.util.serializer.JsonSerializer" />
<Class name="io.clientcore.core.util.serializer.MockSerializer" />
<Class name="io.clientcore.core.util.serializer.XmlSerializer" />
</Or>
</Match>
<Match>
Expand Down Expand Up @@ -349,7 +352,7 @@
<Class name="io.clientcore.core.serialization.xml.DefaultXmlWriterContractTests" />
<Class name="io.clientcore.core.serialization.xml.implementation.aalto.in.ReaderConfig$EncodingContext" />
<Class name="io.clientcore.core.util.binarydata.BinaryDataTest$BinaryDataAsProperty" />
<Class name="io.clientcore.core.util.serializer.DefaultJsonSerializerTests$DateTimeWrapper" />
<Class name="io.clientcore.core.util.serializer.JsonSerializerTests$DateTimeWrapper" />
</Or>
</Match>
<Match>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import io.clientcore.core.http.models.RequestOptions;
import io.clientcore.core.http.pipeline.HttpPipeline;
import io.clientcore.core.implementation.http.rest.RestProxyImpl;
import io.clientcore.core.implementation.http.rest.RestProxyUtils;
import io.clientcore.core.implementation.http.rest.SwaggerInterfaceParser;
import io.clientcore.core.implementation.http.rest.SwaggerMethodParser;
import io.clientcore.core.util.serializer.JsonSerializer;
import io.clientcore.core.util.serializer.ObjectSerializer;
import io.clientcore.core.util.serializer.XmlSerializer;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
Expand All @@ -29,13 +30,14 @@ public final class RestProxy implements InvocationHandler {
* Create a RestProxy.
*
* @param httpPipeline the HttpPipelinePolicy 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 interface describing REST API methods that
* this RestProxy "implements".
* @param serializers the serializers that will be used to convert response bodies to POJOs.
*/
private RestProxy(HttpPipeline httpPipeline, ObjectSerializer serializer, SwaggerInterfaceParser interfaceParser) {
private RestProxy(HttpPipeline httpPipeline, SwaggerInterfaceParser interfaceParser,
ObjectSerializer... serializers) {
this.interfaceParser = interfaceParser;
this.restProxyImpl = new RestProxyImpl(httpPipeline, serializer, interfaceParser);
this.restProxyImpl = new RestProxyImpl(httpPipeline, interfaceParser, serializers);
}

/**
Expand Down Expand Up @@ -68,24 +70,30 @@ public Object invoke(Object proxy, final Method method, Object[] args) {
* @param <A> the type of the Swagger interface
* @return a proxy implementation of the provided Swagger interface
*/
@SuppressWarnings("unchecked")
public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline) {
return create(swaggerInterface, httpPipeline, RestProxyUtils.createDefaultSerializer());
final SwaggerInterfaceParser interfaceParser = SwaggerInterfaceParser.getInstance(swaggerInterface);
final RestProxy restProxy
= new RestProxy(httpPipeline, interfaceParser, new JsonSerializer(), new XmlSerializer());

return (A) Proxy.newProxyInstance(swaggerInterface.getClassLoader(), new Class<?>[] { swaggerInterface },
restProxy);
}

/**
* Create a proxy implementation of the provided Swagger interface.
*
* @param swaggerInterface the Swagger interface to provide a proxy implementation for
* @param httpPipeline the HttpPipelinePolicy and HttpClient pipline that will be used to send Http requests
* @param serializer the serializer that will be used to convert POJOs to and from request and response bodies
* @param httpPipeline the HttpPipelinePolicy and HttpClient pipeline that will be used to send Http requests
* @param serializers the serializers that will be used to convert POJOs to and from request and response bodies
* @param <A> the type of the Swagger interface.
*
* @return a proxy implementation of the provided Swagger interface
* @throws IllegalArgumentException If {@code serializers} is null or empty.
*/
@SuppressWarnings("unchecked")
public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline, ObjectSerializer serializer) {
public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline, ObjectSerializer... serializers) {
final SwaggerInterfaceParser interfaceParser = SwaggerInterfaceParser.getInstance(swaggerInterface);
final RestProxy restProxy = new RestProxy(httpPipeline, serializer, interfaceParser);
final RestProxy restProxy = new RestProxy(httpPipeline, interfaceParser, serializers);

return (A) Proxy.newProxyInstance(swaggerInterface.getClassLoader(), new Class<?>[] { swaggerInterface },
restProxy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import io.clientcore.core.http.models.HttpRequest;

public class RequestDataConfiguration {
private HttpRequest httpRequest;
private SwaggerMethodParser methodParser;
private boolean isJson;
private Object bodyContent;
private final HttpRequest httpRequest;
private final SwaggerMethodParser methodParser;
private final boolean isJson;
private final Object bodyContent;

public RequestDataConfiguration(HttpRequest httpRequest, SwaggerMethodParser swaggerMethodParser, boolean isJson,
Object requestBodyContent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
import io.clientcore.core.http.models.RequestOptions;
import io.clientcore.core.http.models.Response;
import io.clientcore.core.http.pipeline.HttpPipeline;
import io.clientcore.core.implementation.ReflectionSerializable;
import io.clientcore.core.implementation.ReflectiveInvoker;
import io.clientcore.core.implementation.TypeUtil;
import io.clientcore.core.implementation.http.UnexpectedExceptionInformation;
import io.clientcore.core.implementation.http.serializer.CompositeSerializer;
import io.clientcore.core.implementation.http.serializer.MalformedValueException;
import io.clientcore.core.implementation.util.UriBuilder;
import io.clientcore.core.serialization.json.JsonSerializable;
import io.clientcore.core.util.ClientLogger;
import io.clientcore.core.util.binarydata.BinaryData;
import io.clientcore.core.util.serializer.ObjectSerializer;
Expand All @@ -28,34 +27,32 @@
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public abstract class RestProxyBase {
static final ResponseConstructorsCache RESPONSE_CONSTRUCTORS_CACHE = new ResponseConstructorsCache();
private static final ResponseExceptionConstructorCache RESPONSE_EXCEPTION_CONSTRUCTOR_CACHE
= new ResponseExceptionConstructorCache();

// RestProxy is a commonly used class, use a static logger.
static final ClientLogger LOGGER = new ClientLogger(RestProxyBase.class);

final HttpPipeline httpPipeline;
final ObjectSerializer serializer;
final CompositeSerializer serializer;
final SwaggerInterfaceParser interfaceParser;

/**
* Create a RestProxy.
*
* @param httpPipeline The HttpPipelinePolicy 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 interface describing REST API methods that
* this RestProxy "implements".
* @param serializers The serializers that will be used to convert response bodies to POJOs.
*/
public RestProxyBase(HttpPipeline httpPipeline, ObjectSerializer serializer,
SwaggerInterfaceParser interfaceParser) {
public RestProxyBase(HttpPipeline httpPipeline, SwaggerInterfaceParser interfaceParser,
ObjectSerializer... serializers) {
this.httpPipeline = httpPipeline;
this.serializer = serializer;
this.interfaceParser = interfaceParser;
this.serializer = new CompositeSerializer(Arrays.asList(serializers));
}

public final Object invoke(Object proxy, RequestOptions options, SwaggerMethodParser methodParser, Object[] args) {
Expand All @@ -74,7 +71,7 @@ public final Object invoke(Object proxy, RequestOptions options, SwaggerMethodPa
protected abstract Object invoke(Object proxy, SwaggerMethodParser methodParser, HttpRequest request);

public abstract void updateRequest(RequestDataConfiguration requestDataConfiguration,
ObjectSerializer objectSerializer) throws IOException;
CompositeSerializer serializer) throws IOException;

@SuppressWarnings({ "unchecked" })
public Response<?> createResponseIfNecessary(Response<?> response, Type entityType, Object bodyAsObject) {
Expand Down Expand Up @@ -103,12 +100,10 @@ public Response<?> createResponseIfNecessary(Response<?> response, Type entityTy
*
* @param methodParser The Swagger method parser to use.
* @param args The arguments to use to populate the method's annotation values.
*
* @return An HttpRequest.
*
* @throws IOException If the body contents cannot be serialized.
*/
HttpRequest createHttpRequest(SwaggerMethodParser methodParser, ObjectSerializer objectSerializer, Object[] args)
HttpRequest createHttpRequest(SwaggerMethodParser methodParser, CompositeSerializer serializer, Object[] args)
throws IOException, URISyntaxException {

// Sometimes people pass in a full URI for the value of their PathParam annotated argument.
Expand Down Expand Up @@ -145,7 +140,7 @@ HttpRequest createHttpRequest(SwaggerMethodParser methodParser, ObjectSerializer

final URI uri = uriBuilder.toUri();
final HttpRequest request
= configRequest(new HttpRequest(methodParser.getHttpMethod(), uri), methodParser, objectSerializer, args);
= configRequest(new HttpRequest(methodParser.getHttpMethod(), uri), methodParser, serializer, args);
// Headers from Swagger method arguments always take precedence over inferred headers from body types
HttpHeaders httpHeaders = request.getHeaders();

Expand All @@ -155,7 +150,7 @@ HttpRequest createHttpRequest(SwaggerMethodParser methodParser, ObjectSerializer
}

private HttpRequest configRequest(HttpRequest request, SwaggerMethodParser methodParser,
ObjectSerializer objectSerializer, Object[] args) throws IOException {
CompositeSerializer objectSerializer, Object[] args) throws IOException {
final Object bodyContentObject = methodParser.setBody(args, serializer);

if (bodyContentObject == null) {
Expand Down Expand Up @@ -218,7 +213,6 @@ private HttpRequest configRequest(HttpRequest request, SwaggerMethodParser metho
* @param response The http response to parse when constructing exception
* @param responseBody The response body to use when constructing exception
* @param responseDecodedBody The decoded response content to use when constructing exception
*
* @return The {@link HttpResponseException} created from the provided details.
*/
public static HttpResponseException instantiateUnexpectedException(
Expand Down Expand Up @@ -256,28 +250,4 @@ public static HttpResponseException instantiateUnexpectedException(

return new HttpResponseException(exceptionMessage.toString(), response, exceptionType, responseDecodedBody);
}

/**
* Whether {@code JsonSerializable} is supported and the {@code bodyContentClass} is an instance of it.
*
* @param bodyContentClass The body content class.
*
* @return Whether {@code bodyContentClass} can be used as {@code JsonSerializable}.
*/
static boolean supportsJsonSerializable(Class<?> bodyContentClass) {
return ReflectionSerializable.supportsJsonSerializable(bodyContentClass);
}

/**
* Serializes the {@code jsonSerializable} as an instance of {@code JsonSerializable}.
*
* @param jsonSerializable The {@code JsonSerializable} body content.
*
* @return The {@link ByteBuffer} representing the serialized {@code jsonSerializable}.
*
* @throws IOException If an error occurs during serialization.
*/
static ByteBuffer serializeAsJsonSerializable(JsonSerializable<?> jsonSerializable) throws IOException {
return ReflectionSerializable.serializeJsonSerializableToByteBuffer(jsonSerializable);
}
}
Loading
Loading