Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected Client(Builder<?, ?> builder) {
ClientConfig.Builder configBuilder = builder.configBuilder();
this.config = configBuilder.build();
this.pipeline = ClientPipeline.of(config.protocol(), config.transport());
this.interceptor = ClientInterceptor.chain(config.interceptors());
this.interceptor = config.interceptorChain();
this.identityResolvers = IdentityResolvers.of(config.identityResolvers());
this.typeRegistry = typeRegistry();

Expand Down Expand Up @@ -99,7 +99,7 @@ protected <I extends SerializableStruct, O extends SerializableStruct> O call(
// Rebuild the pipeline, resolvers, etc if the config changed.
if (callConfig != config) {
callPipeline = ClientPipeline.of(callConfig.protocol(), callConfig.transport());
callInterceptor = ClientInterceptor.chain(callConfig.interceptors());
callInterceptor = callConfig.interceptorChain();
callIdentityResolvers = IdentityResolvers.of(callConfig.identityResolvers());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public final class ClientConfig {
private final ClientProtocol<?, ?> protocol;
private final EndpointResolver endpointResolver;
private final List<ClientInterceptor> interceptors;
private final ClientInterceptor interceptorChain;
private final List<AuthScheme<?, ?>> supportedAuthSchemes;
private final AuthSchemeResolver authSchemeResolver;
private final List<IdentityResolver<?>> identityResolvers;
Expand Down Expand Up @@ -78,6 +79,7 @@ private ClientConfig(Builder builder) {
}

this.interceptors = List.copyOf(builder.interceptors);
this.interceptorChain = ClientInterceptor.chain(this.interceptors);

// By default, support NoAuthAuthScheme
List<AuthScheme<?, ?>> supportedAuthSchemes = new ArrayList<>();
Expand Down Expand Up @@ -178,6 +180,13 @@ public List<ClientInterceptor> interceptors() {
return interceptors;
}

/**
* @return A pre-built interceptor chain for the configured interceptors.
*/
public ClientInterceptor interceptorChain() {
return interceptorChain;
}

/**
* @return Authentication schemes supported by the client.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ public interface ClientInterceptor {
* @return the combined interceptors.
*/
static ClientInterceptor chain(List<ClientInterceptor> interceptors) {
return interceptors.isEmpty() ? NOOP : new ClientInterceptorChain(interceptors);
return switch (interceptors.size()) {
case 0 -> NOOP;
case 1 -> interceptors.get(0);
default -> new ClientInterceptorChain(interceptors);
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,30 @@
package software.amazon.smithy.java.client.core.interceptors;

import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import software.amazon.smithy.java.client.core.ClientConfig;
import software.amazon.smithy.java.core.schema.SerializableStruct;
import software.amazon.smithy.java.logging.InternalLogger;

final class ClientInterceptorChain implements ClientInterceptor {

private static final InternalLogger LOGGER = InternalLogger.getLogger(ClientInterceptorChain.class);
private final List<ClientInterceptor> interceptors;
private final ClientInterceptor[] interceptors;

public ClientInterceptorChain(List<ClientInterceptor> interceptors) {
if (interceptors.isEmpty()) {
throw new IllegalArgumentException("Interceptors cannot be empty");
}
this.interceptors = interceptors;
this.interceptors = interceptors.toArray(ClientInterceptor[]::new);
}

@Override
public void readBeforeExecution(InputHook<?, ?> hook) {
applyToEachThrowLastError("readBeforeExecution", ClientInterceptor::readBeforeExecution, hook);
}

// Many interceptors require running each hook, logging errors, and throwing the last.
private <T> void applyToEachThrowLastError(String hookName, BiConsumer<ClientInterceptor, T> consumer, T hook) {
RuntimeException error = null;
for (var interceptor : interceptors) {
try {
consumer.accept(interceptor, hook);
interceptor.readBeforeExecution(hook);
} catch (RuntimeException e) {
error = swapError(hookName, error, e);
error = swapError("readBeforeExecution", error, e);
}
}

Expand Down Expand Up @@ -79,28 +72,36 @@ public void readAfterSerialization(RequestHook<?, ?, ?> hook) {

@Override
public <RequestT> RequestT modifyBeforeRetryLoop(RequestHook<?, ?, RequestT> hook) {
return modifyRequestHook(ClientInterceptor::modifyBeforeRetryLoop, hook);
}

private <I extends SerializableStruct, RequestT> RequestT modifyRequestHook(
BiFunction<ClientInterceptor, RequestHook<I, ?, RequestT>, RequestT> mapper,
RequestHook<I, ?, RequestT> hook
) {
var request = hook.request();
for (var interceptor : interceptors) {
request = mapper.apply(interceptor, hook.withRequest(request));
request = interceptor.modifyBeforeRetryLoop(hook.withRequest(request));
}
return request;
}

@Override
public void readBeforeAttempt(RequestHook<?, ?, ?> hook) {
applyToEachThrowLastError("readBeforeAttempt", ClientInterceptor::readBeforeAttempt, hook);
RuntimeException error = null;
for (var interceptor : interceptors) {
try {
interceptor.readBeforeAttempt(hook);
} catch (RuntimeException e) {
error = swapError("readBeforeAttempt", error, e);
}
}

if (error != null) {
throw error;
}
}

@Override
public <RequestT> RequestT modifyBeforeSigning(RequestHook<?, ?, RequestT> hook) {
return modifyRequestHook(ClientInterceptor::modifyBeforeSigning, hook);
var request = hook.request();
for (var interceptor : interceptors) {
request = interceptor.modifyBeforeSigning(hook.withRequest(request));
}
return request;
}

@Override
Expand All @@ -119,7 +120,11 @@ public void readAfterSigning(RequestHook<?, ?, ?> hook) {

@Override
public <RequestT> RequestT modifyBeforeTransmit(RequestHook<?, ?, RequestT> hook) {
return modifyRequestHook(ClientInterceptor::modifyBeforeTransmit, hook);
var request = hook.request();
for (var interceptor : interceptors) {
request = interceptor.modifyBeforeTransmit(hook.withRequest(request));
}
return request;
}

@Override
Expand Down
Loading