ResponseConstructorsCache.java
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.core.http.rest;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpResponse;
import com.azure.core.implementation.serializer.HttpResponseDecoder;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A concurrent cache of {@link Response} constructors.
*/
final class ResponseConstructorsCache {
private final ClientLogger logger = new ClientLogger(ResponseConstructorsCache.class);
private final Map<Class<?>, Constructor<? extends Response<?>>> cache = new ConcurrentHashMap<>();
/**
* Identify the suitable constructor for the given response class.
*
* @param responseClass the response class
* @return identified constructor, null if there is no match
*/
Constructor<? extends Response<?>> get(Class<? extends Response<?>> responseClass) {
return this.cache.computeIfAbsent(responseClass, this::locateResponseConstructor);
}
/**
* Identify the most specific constructor for the given response class.
*
* The most specific constructor is looked up following order:
* 1. (httpRequest, statusCode, headers, body, decodedHeaders)
* 2. (httpRequest, statusCode, headers, body)
* 3. (httpRequest, statusCode, headers)
*
* Developer Note: This method logic can be easily replaced with Java.Stream
* and associated operators but we're using basic sort and loop constructs
* here as this method is in hot path and Stream route is consuming a fair
* amount of resources.
*
* @param responseClass the response class
* @return identified constructor, null if there is no match
*/
@SuppressWarnings("unchecked")
private Constructor<? extends Response<?>> locateResponseConstructor(Class<?> responseClass) {
Constructor<?>[] constructors = responseClass.getDeclaredConstructors();
// Sort constructors in the "descending order" of parameter count.
Arrays.sort(constructors, Comparator.comparing(Constructor::getParameterCount, (a, b) -> b - a));
for (Constructor<?> constructor : constructors) {
final int paramCount = constructor.getParameterCount();
if (paramCount >= 3 && paramCount <= 5) {
try {
return (Constructor<? extends Response<?>>) constructor;
} catch (Throwable t) {
throw logger.logExceptionAsError(new RuntimeException(t));
}
}
}
return null;
}
/**
* Invoke the constructor this type represents.
*
* @param constructor the constructor type
* @param decodedResponse the decoded http response
* @param bodyAsObject the http response content
* @return an instance of a {@link Response} implementation
*/
Mono<Response<?>> invoke(final Constructor<? extends Response<?>> constructor,
final HttpResponseDecoder.HttpDecodedResponse decodedResponse,
final Object bodyAsObject) {
final HttpResponse httpResponse = decodedResponse.getSourceResponse();
final HttpRequest httpRequest = httpResponse.getRequest();
final int responseStatusCode = httpResponse.getStatusCode();
final HttpHeaders responseHeaders = httpResponse.getHeaders();
final int paramCount = constructor.getParameterCount();
switch (paramCount) {
case 3:
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(new RuntimeException("Failed to deserialize 3-parameter"
+ " response. ", e));
}
case 4:
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(new RuntimeException("Failed to deserialize 4-parameter"
+ " response. ", e));
}
case 5:
return decodedResponse.getDecodedHeaders()
.map((Function<Object, Response<?>>) decodedHeaders -> {
try {
return constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject,
decodedHeaders);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(new RuntimeException("Failed to deserialize 5-parameter"
+ " response with decoded headers. ", e));
}
})
.switchIfEmpty(Mono.defer((Supplier<Mono<Response<?>>>) () -> {
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject,
null));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(new RuntimeException(
"Failed to deserialize 5-parameter response without decoded headers.", e));
}
}));
default:
throw logger.logExceptionAsError(
new IllegalStateException("Response constructor with expected parameters not found."));
}
}
}