Skip to content

Commit

Permalink
[Prototype] Support composite ObjectSerializers, add XmlSerializer (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
alzimmermsft authored Dec 18, 2024
1 parent d3a27eb commit 6fa7d7f
Show file tree
Hide file tree
Showing 33 changed files with 873 additions and 227 deletions.
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.implementation.util.JsonSerializer" />
<Class name="io.clientcore.core.implementation.util.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.implementation.util.JsonSerializer" />
<Class name="io.clientcore.core.util.serializer.MockSerializer" />
<Class name="io.clientcore.core.implementation.util.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.implementation.util.JsonSerializer;
import io.clientcore.core.util.serializer.ObjectSerializer;
import io.clientcore.core.implementation.util.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

0 comments on commit 6fa7d7f

Please sign in to comment.