All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zalando.riptide.spring.PluginInterceptors Maven / Gradle / Ivy

package org.zalando.riptide.spring;

import com.google.common.collect.ImmutableMultimap;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.AsyncClientHttpRequestExecution;
import org.springframework.http.client.AsyncClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import org.springframework.util.concurrent.ListenableFuture;
import org.zalando.riptide.Plugin;
import org.zalando.riptide.RequestArguments;
import org.zalando.riptide.RequestExecution;
import org.zalando.riptide.capture.Completion;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;

import static org.zalando.riptide.CancelableCompletableFuture.preserveCancelability;

final class PluginInterceptors {

    private PluginInterceptors() {

    }

    @SuppressWarnings("unused") // by Spring
    static Adapter adapt(final Plugin plugin) {
        return new Adapter(plugin);
    }

    @AllArgsConstructor
    static final class Adapter implements ClientHttpRequestInterceptor, AsyncClientHttpRequestInterceptor {

        private final Plugin plugin;

        @Override
        public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                final ClientHttpRequestExecution execution) throws IOException {

            final RequestArguments arguments = toArguments(request, body);
            final RequestExecution requestExecution = () -> {
                final CompletableFuture future = new CompletableFuture<>();

                try {
                    future.complete(execution.execute(request, body));
                } catch (final Exception e) {
                    future.completeExceptionally(e);
                }

                return future;
            };

            // since there is no routing to be done, we just call the plugin twice in succession
            final RequestExecution before = plugin.interceptBeforeRouting(arguments, requestExecution);
            final RequestExecution after = plugin.interceptAfterRouting(arguments, before);

            return Completion.join(after.execute());
        }

        @Override
        public ListenableFuture intercept(final HttpRequest request, final byte[] body,
                final AsyncClientHttpRequestExecution execution) throws IOException {

            final RequestArguments arguments = toArguments(request, body);

            final RequestExecution requestExecution = () -> {
                try {

                    final ListenableFuture original = execution.executeAsync(request, body);
                    final CompletableFuture future = preserveCancelability(original);
                    original.addCallback(future::complete, future::completeExceptionally);
                    return future;
                } catch (final Exception e) {
                    // this branch is just for issues inside of executeAsync, not during the actual execution
                    final CompletableFuture future = new CompletableFuture<>();
                    future.completeExceptionally(e);
                    return future;
                }
            };

            // since there is no routing to be done, we just call the plugin twice in succession
            final RequestExecution before = plugin.interceptBeforeRouting(arguments, requestExecution);
            final RequestExecution after = plugin.interceptAfterRouting(arguments, before);

            return new CompletableToListenableFutureAdapter<>(after.execute());
        }

        /**
         * Takes an existing {@link HttpRequest request} and a body and tries to adapts them to
         * {@link RequestArguments arguments}.
         *
         * @see org.springframework.web.util.AbstractUriTemplateHandler#insertBaseUrl(java.net.URI)
         * @param request original request
         * @param body serialized request body
         * @return derived request arguments
         */
        static RequestArguments toArguments(final HttpRequest request, final byte[] body) {
            return RequestArguments.create()
                    .withMethod(request.getMethod())
                    .withRequestUri(request.getURI())
                    .withHeaders(copy(request.getHeaders()))
                    /*
                     * Plugins and AsyncClientHttpRequestInterceptors are conceptually working on different logical
                     * levels. Plugins are pre-serialization and interceptors are post-serialization. Passing the
                     * serialized body to plugin implementations is therefore totally wrong, but in a best-effort
                     * attempt, we decided to do it anyway.
                     */
                    .withBody(body);
        }

        private static ImmutableMultimap copy(final HttpHeaders headers) {
            final ImmutableMultimap.Builder copy = ImmutableMultimap.builder();
            headers.forEach(copy::putAll);
            return copy.build();
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy