001
002package io.vrap.rmf.base.client;
003
004import static java.util.Objects.requireNonNull;
005
006import java.net.URI;
007import java.time.Duration;
008import java.util.*;
009import java.util.concurrent.ExecutorService;
010import java.util.concurrent.ForkJoinPool;
011import java.util.concurrent.ScheduledExecutorService;
012import java.util.function.Function;
013import java.util.function.Predicate;
014import java.util.function.Supplier;
015
016import javax.annotation.Nullable;
017
018import io.vrap.rmf.base.client.error.HttpExceptionFactory;
019import io.vrap.rmf.base.client.http.*;
020import io.vrap.rmf.base.client.oauth2.*;
021
022import org.slf4j.event.Level;
023
024import dev.failsafe.spi.Scheduler;
025
026/**
027    <p>The ClientBuilder is used to configure and create an {@link ApiHttpClient}. As the ApiHttpClient uses a {@link HandlerStack stack}
028    of middlewares the Builder comes with methods to configure and attach new middlewares. Also it ensures that some default
029    used middlewares are instantiated at the correct location in the middleware stack.</p>
030
031    <p>The default middlewares and services are added as Supplier to be able to override the ones provided by e.g.: {@link #defaultClient(URI)}</p>
032 */
033public class ClientBuilder implements Builder<ApiHttpClient> {
034    public static final String COMMERCETOOLS = "commercetools";
035    static final String userAgent = "commercetools-sdk-java-v2/";
036
037    private URI apiBaseUrl;
038    private boolean useAuthCircuitBreaker;
039    private int authRetries;
040    private Supplier<ErrorMiddleware> errorMiddleware;
041    private Supplier<OAuthMiddleware> oAuthMiddleware;
042    private Supplier<RetryRequestMiddleware> retryMiddleware;
043    private Supplier<QueueRequestMiddleware> queueMiddleware;
044    private Supplier<Middleware> correlationIdMiddleware;
045    private InternalLoggerMiddleware internalLoggerMiddleware;
046    private UserAgentMiddleware userAgentMiddleware;
047
048    private Supplier<TelemetryMiddleware> telemetryMiddleware;
049    private List<Middleware> middlewares = new ArrayList<>();
050    private Supplier<HandlerStack> stack;
051    private VrapHttpClient httpClient;
052    private VrapHttpClient oauthHttpClient;
053    private Supplier<ResponseSerializer> serializer;
054    private Supplier<HttpExceptionFactory> httpExceptionFactory;
055
056    private Supplier<ExecutorService> oauthExecutorService = ForkJoinPool::new;
057    /**
058     * <p>Creates a default client builder</p>
059     * @return ClientBuilder instance
060     */
061    public static ClientBuilder of() {
062        return new ClientBuilder();
063    }
064
065    public static ClientBuilder of(ExecutorService httpClientExecutorService) {
066        return new ClientBuilder(httpClientExecutorService);
067    }
068
069    /**
070     * <p>Creates a client builder with a specific or preconfigured {@link VrapHttpClient} instance. Uses defaults for
071     * the {@link HandlerStack}</p>
072     * @param httpClient the HTTP client to be used
073     * @return ClientBuilder instance
074     */
075    public static ClientBuilder of(final VrapHttpClient httpClient) {
076        return new ClientBuilder(httpClient);
077    }
078
079    /**
080     * <p>Creates a client builder with a specific or preconfigured {@link ApiHttpClient} instance. Uses defaults for
081     * the {@link HandlerStack}</p>
082     * @param httpClient the HTTP client to be used
083     * @return ClientBuilder instance
084     */
085    public static ClientBuilder of(final ApiHttpClient httpClient) {
086        return new ClientBuilder(httpClient);
087    }
088
089    /**
090     * <p>Creates a client builder with a specifig {@link HandlerStack}</p>
091     * @param stack the HandlerStack to be used
092     * @return ClientBuilder instance
093     */
094    public static ClientBuilder of(final HandlerStack stack) {
095        return new ClientBuilder(stack);
096    }
097
098    private ClientBuilder(final HandlerStack stack) {
099        this.stack = () -> stack;
100        ResponseSerializer serializer = ResponseSerializer.of();
101        this.serializer = () -> serializer;
102        this.httpExceptionFactory = () -> HttpExceptionFactory.of(this.serializer.get());
103        this.useAuthCircuitBreaker = false;
104        this.authRetries = 1;
105    }
106
107    private ClientBuilder() {
108        this.httpClient = HttpClientSupplier.of(new ForkJoinPool()).get();
109        this.oauthHttpClient = httpClient;
110        this.stack = stackSupplier();
111        ResponseSerializer serializer = ResponseSerializer.of();
112        this.serializer = () -> serializer;
113        this.httpExceptionFactory = () -> HttpExceptionFactory.of(this.serializer.get());
114        this.useAuthCircuitBreaker = false;
115        this.authRetries = 1;
116    }
117
118    private ClientBuilder(ExecutorService httpClientExecutorService) {
119        this.httpClient = HttpClientSupplier.of(httpClientExecutorService).get();
120        this.oauthHttpClient = httpClient;
121        this.stack = stackSupplier();
122        ResponseSerializer serializer = ResponseSerializer.of();
123        this.serializer = () -> serializer;
124        this.httpExceptionFactory = () -> HttpExceptionFactory.of(this.serializer.get());
125        this.useAuthCircuitBreaker = false;
126        this.authRetries = 1;
127    }
128
129    private ClientBuilder(final ApiHttpClient httpClient) {
130        this.httpClient = httpClient;
131        this.oauthHttpClient = HttpClientSupplier.of(new ForkJoinPool()).get();
132        this.stack = stackSupplier();
133        ResponseSerializer serializer = ResponseSerializer.of();
134        this.serializer = () -> serializer;
135        this.httpExceptionFactory = () -> HttpExceptionFactory.of(this.serializer.get());
136        this.authRetries = 1;
137    }
138
139    private ClientBuilder(final VrapHttpClient httpClient) {
140        this.httpClient = httpClient;
141        this.oauthHttpClient = httpClient;
142        this.stack = stackSupplier();
143        ResponseSerializer serializer = ResponseSerializer.of();
144        this.serializer = () -> serializer;
145        this.httpExceptionFactory = () -> HttpExceptionFactory.of(this.serializer.get());
146        this.authRetries = 1;
147    }
148
149    /**
150     * Ensures the order of default middlewares to create a {@link HandlerStack}
151     * @return HandlerStack supplier method
152     */
153    private Supplier<HandlerStack> stackSupplier() {
154        return () -> {
155            final List<Middleware> middlewareStack = new ArrayList<>();
156            Optional.ofNullable(telemetryMiddleware).map(m -> middlewareStack.add(m.get()));
157            Optional.ofNullable(errorMiddleware).map(m -> middlewareStack.add(m.get()));
158            Optional.ofNullable(internalLoggerMiddleware).map(middlewareStack::add);
159            Optional.ofNullable(userAgentMiddleware).map(middlewareStack::add);
160            Optional.ofNullable(oAuthMiddleware).map(m -> middlewareStack.add(m.get()));
161            Optional.ofNullable(retryMiddleware).map(m -> middlewareStack.add(m.get()));
162            Optional.ofNullable(queueMiddleware).map(m -> middlewareStack.add(m.get()));
163            Optional.ofNullable(correlationIdMiddleware).map(m -> middlewareStack.add(m.get()));
164            middlewareStack.addAll(middlewares);
165            return HandlerStack.create(HttpHandler.create(requireNonNull(httpClient)), middlewareStack);
166        };
167    }
168
169    /**
170     * Ensures the order of default middlewares to create a {@link HandlerStack}
171     * @return HandlerStack supplier method
172     */
173    private Supplier<HandlerStack> oauthHandlerSupplier() {
174        return () -> {
175            final List<Middleware> middlewareStack = new ArrayList<>();
176            Optional.ofNullable(userAgentMiddleware).map(middlewareStack::add);
177            Optional.ofNullable(correlationIdMiddleware).map(m -> middlewareStack.add(m.get()));
178            return HandlerStack.create(HttpHandler.create(requireNonNull(oauthHttpClient)), middlewareStack);
179        };
180    }
181
182    /**
183     * deactivates the circuit breaker for authentication
184     * @return ClientBuilder instance
185     */
186    public ClientBuilder withoutAuthCircuitBreaker() {
187        this.useAuthCircuitBreaker = false;
188        return this;
189    }
190
191    /**
192     * activates the circuit breaker for authentication. Upon erroneous authentication e.g. the authentication
193     * middleware will open the circuit breaker and retry regularly.
194     * @return ClientBuilder instance
195     */
196    public ClientBuilder withAuthCircuitBreaker() {
197        this.useAuthCircuitBreaker = true;
198        return this;
199    }
200
201    /**
202     * @param authRetries number of retries for authentication before giving up.
203     * @return ClientBuilder instance
204     */
205    public ClientBuilder withAuthRetries(final int authRetries) {
206        this.authRetries = authRetries;
207        return this;
208    }
209
210    /**
211     * @param stack {@link HandlerStack} to be used for the HTTP requests
212     * @return ClientBuilder instance
213     */
214    public ClientBuilder withHandlerStack(final HandlerStack stack) {
215        this.stack = () -> stack;
216        return this;
217    }
218
219    /**
220     * @param httpClient {@link VrapHttpClient} to be used for the HTTP requests
221     * @return ClientBuilder instance
222     */
223    public ClientBuilder withHttpClient(final VrapHttpClient httpClient) {
224        this.httpClient = httpClient;
225        return this;
226    }
227
228    /**
229     * @param httpClient {@link VrapHttpClient} to be used for the OAuth requests
230     * @return ClientBuilder instance
231     */
232    public ClientBuilder withOAuthHttpClient(final VrapHttpClient httpClient) {
233        this.oauthHttpClient = httpClient;
234        return this;
235    }
236
237    /**
238     * @param serializer {@link ResponseSerializer} to be used for de-/serialization
239     * @return ClientBuilder instance
240     */
241    public ClientBuilder withSerializer(final ResponseSerializer serializer) {
242        this.serializer = () -> serializer;
243        return this;
244    }
245
246    /**
247     * @param serializer {@link ResponseSerializer} to be used for de-/serialization
248     * @return ClientBuilder instance
249     */
250    public ClientBuilder withSerializer(final Supplier<ResponseSerializer> serializer) {
251        this.serializer = serializer;
252        return this;
253    }
254
255    /**
256     * @param factory {@link HttpExceptionFactory} to be used for creating Exceptions based on response status code
257     * @return ClientBuilder instance
258     */
259    public ClientBuilder withHttpExceptionFactory(final HttpExceptionFactory factory) {
260        this.httpExceptionFactory = () -> factory;
261        return this;
262    }
263
264    /**
265     * @param factory function to create {@link HttpExceptionFactory} to be used for creating Exceptions based on response
266     * status code with the configured {@link ResponseSerializer}
267     * @return ClientBuilder instance
268     */
269    public ClientBuilder withHttpExceptionFactory(final Function<ResponseSerializer, HttpExceptionFactory> factory) {
270        this.httpExceptionFactory = () -> factory.apply(serializer.get());
271        return this;
272    }
273
274    /**
275     * configures the Factory for HTTP exception in case.
276     * @param factory {@link HttpExceptionFactory} to be used for creating Exceptions based on response status code
277     * @return ClientBuilder instance
278     */
279    public ClientBuilder withHttpExceptionFactory(final Supplier<HttpExceptionFactory> factory) {
280        this.httpExceptionFactory = factory;
281        return this;
282    }
283
284    /**
285     * configures an ExecutorService to be used for the Middlewares
286     * @param executorService supplier of the executor service to be used
287     * @return ClientBuilder instance
288     */
289    public ClientBuilder withOAuthExecutorService(final Supplier<ExecutorService> executorService) {
290        this.oauthExecutorService = executorService;
291        return this;
292    }
293
294    /**
295     * configures an ExecutorService to be used for the Middlewares
296     * @param executorService executor service to be used
297     * @return ClientBuilder instance
298     */
299    public ClientBuilder withOAuthExecutorService(final ExecutorService executorService) {
300        this.oauthExecutorService = () -> executorService;
301        return this;
302    }
303
304    /**
305     * <p>Configures a client with the default middlewares and the given baseUrl</p>
306     * <p>The following middlewares and services are configured:
307     * <ul>
308     *     <li>{@link ErrorMiddleware}</li>
309     *     <li>{@link ResponseSerializer}</li>
310     *     <li>{@link InternalLoggerFactory}</li>
311     *     <li>{@link UserAgentMiddleware}</li>
312     *     <li>{@link AcceptGZipMiddleware}</li>
313     * </ul>
314     * </p>
315     * @param apiBaseUrl base URI for the API
316     * @return ClientBuilder instance
317     */
318    public ClientBuilder defaultClient(final URI apiBaseUrl) {
319        return withApiBaseUrl(apiBaseUrl).withErrorMiddleware()
320                .withSerializer(ResponseSerializer.of())
321                .withInternalLoggerFactory((request, topic) -> InternalLogger.getLogger(COMMERCETOOLS + "." + topic))
322                .withUserAgentSupplier(ClientBuilder::buildDefaultUserAgent)
323                .addAcceptGZipMiddleware();
324    }
325
326    /**
327     * <p>Configures a client with the default middlewares and the given baseUrl</p>
328     * <p>The following middlewares and services are configured:
329     * <ul>
330     *     <li>{@link ErrorMiddleware}</li>
331     *     <li>{@link ResponseSerializer}</li>
332     *     <li>{@link InternalLoggerFactory}</li>
333     *     <li>{@link UserAgentMiddleware}</li>
334     *     <li>{@link AcceptGZipMiddleware}</li>
335     * </ul>
336     * </p>
337     * @param apiBaseUrl base URI for the API
338     * @return ClientBuilder instance
339     */
340    public ClientBuilder defaultClient(final String apiBaseUrl) {
341        return defaultClient(URI.create(apiBaseUrl));
342    }
343
344    /**
345     * <p>Configures a client with the default middlewares and the given baseUrl</p>
346     * <p>The following middlewares and services are configured:
347     * <ul>
348     *     <li>{@link ErrorMiddleware}</li>
349     *     <li>{@link ResponseSerializer}</li>
350     *     <li>{@link InternalLoggerFactory}</li>
351     *     <li>{@link UserAgentMiddleware}</li>
352     *     <li>{@link AcceptGZipMiddleware}</li>
353     * </ul>
354     * </p>
355     * @param apiBaseUrl base URI for the API
356     * @param credentials {@link ClientCredentials} to be used
357     * @param tokenEndpoint token endpoint URI to be used for authentication
358     * @return ClientBuilder instance
359     */
360    public ClientBuilder defaultClient(final String apiBaseUrl, final ClientCredentials credentials,
361            final String tokenEndpoint) {
362        return defaultClient(apiBaseUrl).withClientCredentialsFlow(credentials, tokenEndpoint);
363    }
364
365    /**
366     * <p>Configures a client with the default middlewares and the given baseUrl</p>
367     * <p>The following middlewares and services are configured:
368     * <ul>
369     *     <li>{@link ErrorMiddleware}</li>
370     *     <li>{@link ResponseSerializer}</li>
371     *     <li>{@link InternalLoggerFactory}</li>
372     *     <li>{@link UserAgentMiddleware}</li>
373     *     <li>{@link AcceptGZipMiddleware}</li>
374     * </ul>
375     * </p>
376     * @param credentials {@link ClientCredentials} to be used
377     * @param serviceRegionConfig {@link ServiceRegionConfig} to be used
378     * @return ClientBuilder instance
379     */
380    public ClientBuilder defaultClient(final ClientCredentials credentials,
381            final ServiceRegionConfig serviceRegionConfig) {
382        return defaultClient(serviceRegionConfig.getApiUrl()).withClientCredentialsFlow(credentials,
383            serviceRegionConfig.getOAuthTokenUrl());
384    }
385
386    /**
387     * @deprecated use {@link #withClientCredentialsFlow(ClientCredentials, String)} instead
388     * @param credentials OAuth credentials
389     * @param tokenEndpoint OAuth endpoint
390     * @return client builder
391     */
392    @Deprecated
393    public ClientBuilder withClientCredentials(final ClientCredentials credentials, final String tokenEndpoint) {
394        return withClientCredentialsFlow(credentials, tokenEndpoint);
395    }
396
397    /**
398     * @deprecated use {@link #withClientCredentialsFlow(ClientCredentials, String, VrapHttpClient)} instead
399     * @param credentials OAuth credentials
400     * @param tokenEndpoint OAuth endpoint
401     * @param httpClient HTTP client to be used
402     * @return client builder
403     */
404    @Deprecated
405    public ClientBuilder withClientCredentials(final ClientCredentials credentials, final String tokenEndpoint,
406            VrapHttpClient httpClient) {
407        return withClientCredentialsFlow(credentials, tokenEndpoint, httpClient);
408    }
409
410    /**
411     * configure the client to use client credentials flow
412     * @param credentials {@link ClientCredentials} to be used for authentication
413     * @param tokenEndpoint URI to be used for authentication
414     * @return ClientBuilder instance
415     */
416    public ClientBuilder withClientCredentialsFlow(final ClientCredentials credentials, final URI tokenEndpoint) {
417        return withClientCredentialsFlow(credentials, tokenEndpoint.toString());
418    }
419
420    /**
421     * configure the client to use client credentials flow
422     * @param credentials {@link ClientCredentials} to be used for authentication
423     * @param tokenEndpoint URI to be used for authentication
424     * @param httpClientSupplier {@link HandlerStack} to use for authentication
425     * @return ClientBuilder instance
426     */
427    public ClientBuilder withClientCredentialsFlow(final ClientCredentials credentials, final URI tokenEndpoint,
428            Supplier<HandlerStack> httpClientSupplier) {
429        return withClientCredentialsFlow(credentials, tokenEndpoint.toString(), httpClientSupplier);
430    }
431
432    /**
433     * configure the client to use client credentials flow
434     * @param credentials {@link ClientCredentials} to be used for authentication
435     * @param tokenEndpoint URI to be used for authentication
436     * @param httpClient {@link VrapHttpClient} to use for authentication
437     * @return ClientBuilder instance
438     */
439    public ClientBuilder withClientCredentialsFlow(final ClientCredentials credentials, final URI tokenEndpoint,
440            VrapHttpClient httpClient) {
441        return withClientCredentialsFlow(credentials, tokenEndpoint.toString(), httpClient);
442    }
443
444    private TokenSupplier createClientCredentialsTokenSupplier(final ClientCredentials credentials,
445            final String tokenEndpoint, final VrapHttpClient httpClient) {
446        return new ClientCredentialsTokenSupplier(credentials.getClientId(), credentials.getClientSecret(),
447            credentials.getScopes(), tokenEndpoint, httpClient);
448    }
449
450    /**
451     * configure the client to use client credentials flow
452     * @param credentials {@link ClientCredentials} to be used for authentication
453     * @param tokenEndpoint URI to be used for authentication
454     * @return ClientBuilder instance
455     */
456    public ClientBuilder withClientCredentialsFlow(final ClientCredentials credentials, final String tokenEndpoint) {
457        return withClientCredentialsFlow(credentials, tokenEndpoint, oauthHandlerSupplier());
458    }
459
460    /**
461     * configure the client to use client credentials flow
462     * @param credentials {@link ClientCredentials} to be used for authentication
463     * @param tokenEndpoint URI to be used for authentication
464     * @param httpClientSupplier {@link VrapHttpClient} to use for authentication
465     * @return ClientBuilder instance
466     */
467    public ClientBuilder withClientCredentialsFlow(final ClientCredentials credentials, final String tokenEndpoint,
468            Supplier<HandlerStack> httpClientSupplier) {
469        return withTokenSupplier(() -> createInMemoryTokenSupplier(
470            createClientCredentialsTokenSupplier(credentials, tokenEndpoint, httpClientSupplier.get())));
471    }
472
473    /**
474     * configure the client to use client credentials flow
475     * @param credentials {@link ClientCredentials} to be used for authentication
476     * @param tokenEndpoint URI to be used for authentication
477     * @param httpClient {@link VrapHttpClient} to use for authentication
478     * @return ClientBuilder instance
479     */
480    public ClientBuilder withClientCredentialsFlow(final ClientCredentials credentials, final String tokenEndpoint,
481            VrapHttpClient httpClient) {
482        return withTokenSupplier(() -> createInMemoryTokenSupplier(
483            createClientCredentialsTokenSupplier(credentials, tokenEndpoint, httpClient)));
484    }
485
486    /**
487     * configure the client to use client credentials flow
488     * @param token {@link AuthenticationToken} to be used requests
489     * @return ClientBuilder instance
490     */
491    public ClientBuilder withStaticTokenFlow(final AuthenticationToken token) {
492        return withTokenSupplier(new StaticTokenSupplier(token));
493    }
494
495    /**
496     * configure the client to use anonymous session flow
497     * @param credentials {@link ClientCredentials} to be used for authentication
498     * @param tokenEndpoint URI to be used for authentication
499     * @return ClientBuilder instance
500     */
501    public ClientBuilder withAnonymousSessionFlow(final ClientCredentials credentials, final String tokenEndpoint) {
502        return withAnonymousSessionFlow(credentials, tokenEndpoint, oauthHandlerSupplier());
503    }
504
505    /**
506     * configure the client to use anonymous session flow
507     * @param credentials {@link ClientCredentials} to be used for authentication
508     * @param tokenEndpoint URI to be used for authentication
509     * @param httpClientSupplier {@link HandlerStack} to use for authentication
510     * @return ClientBuilder instance
511     */
512    public ClientBuilder withAnonymousSessionFlow(final ClientCredentials credentials, final String tokenEndpoint,
513            Supplier<HandlerStack> httpClientSupplier) {
514        return withTokenSupplier(() -> createInMemoryTokenSupplier(
515            createAnonymousSessionTokenSupplier(credentials, tokenEndpoint, httpClientSupplier.get())));
516    }
517
518    /**
519     * configure the client to use anonymous session flow
520     * @param credentials {@link ClientCredentials} to be used for authentication
521     * @param tokenEndpoint URI to be used for authentication
522     * @param httpClient {@link VrapHttpClient} to use for authentication
523     * @return ClientBuilder instance
524     */
525    public ClientBuilder withAnonymousSessionFlow(final ClientCredentials credentials, final String tokenEndpoint,
526            VrapHttpClient httpClient) {
527        return withTokenSupplier(() -> createInMemoryTokenSupplier(
528            createAnonymousSessionTokenSupplier(credentials, tokenEndpoint, httpClient)));
529    }
530
531    private TokenSupplier createAnonymousSessionTokenSupplier(final ClientCredentials credentials,
532            final String tokenEndpoint, final VrapHttpClient httpClient) {
533        return new AnonymousSessionTokenSupplier(credentials.getClientId(), credentials.getClientSecret(),
534            credentials.getScopes(), tokenEndpoint, httpClient);
535    }
536
537    /**
538     * configure the client to use anonymous & refresh token flow
539     * @param credentials {@link ClientCredentials} to be used for authentication
540     * @param anonTokenEndpoint URI to be used for anonymous token authentication
541     * @param refreshTokenEndpoint URI to be used for refresh token authentication
542     * @param storage {@link TokenStorage} for the authentication tokens
543     * @return ClientBuilder instance
544     */
545    public ClientBuilder withAnonymousRefreshFlow(final ClientCredentials credentials, final String anonTokenEndpoint,
546            final String refreshTokenEndpoint, final TokenStorage storage) {
547        return withAnonymousRefreshFlow(credentials, anonTokenEndpoint, refreshTokenEndpoint, storage,
548            oauthHandlerSupplier());
549    }
550
551    /**
552     * configure the client to use anonymous & refresh token flow
553     * @param credentials {@link ClientCredentials} to be used for authentication
554     * @param anonTokenEndpoint URI to be used for anonymous token authentication
555     * @param refreshTokenEndpoint URI to be used for refresh token authentication
556     * @param storage {@link TokenStorage} for the authentication tokens
557     * @param httpClientSupplier {@link HandlerStack} to be used for authentication
558     * @return ClientBuilder instance
559     */
560    public ClientBuilder withAnonymousRefreshFlow(final ClientCredentials credentials, final String anonTokenEndpoint,
561            final String refreshTokenEndpoint, final TokenStorage storage, Supplier<HandlerStack> httpClientSupplier) {
562        return withTokenSupplier(() -> createAnonymousRefreshFlowSupplier(credentials, anonTokenEndpoint,
563            refreshTokenEndpoint, storage, httpClientSupplier.get()));
564    }
565
566    /**
567     * configure the client to use anonymous & refresh token flow
568     * @param credentials {@link ClientCredentials} to be used for authentication
569     * @param anonTokenEndpoint URI to be used for anonymous token authentication
570     * @param refreshTokenEndpoint URI to be used for refresh token authentication
571     * @param storage {@link TokenStorage} for the authentication tokens
572     * @param httpClient {@link VrapHttpClient} to be used for authentication
573     * @return ClientBuilder instance
574     */
575    public ClientBuilder withAnonymousRefreshFlow(final ClientCredentials credentials, final String anonTokenEndpoint,
576            final String refreshTokenEndpoint, final TokenStorage storage, VrapHttpClient httpClient) {
577        return withTokenSupplier(() -> createAnonymousRefreshFlowSupplier(credentials, anonTokenEndpoint,
578            refreshTokenEndpoint, storage, httpClient));
579    }
580
581    private TokenSupplier createInMemoryTokenSupplier(TokenSupplier tokenSupplier) {
582        return Optional.ofNullable(oauthExecutorService)
583                .map(executorService -> new InMemoryTokenSupplier(executorService.get(), tokenSupplier))
584                .orElse(new InMemoryTokenSupplier(tokenSupplier));
585    }
586
587    private TokenSupplier createAnonymousRefreshFlowSupplier(final ClientCredentials credentials,
588            final String anonTokenEndpoint, final String refreshTokenEndpoint, final TokenStorage tokenStorage,
589            final VrapHttpClient httpClient) {
590        final RefreshFlowTokenSupplier refreshFlowTokenSupplier = createRefreshFlowSupplier(credentials,
591            refreshTokenEndpoint, tokenStorage, httpClient);
592
593        final AnonymousFlowTokenSupplier anonymousFlowTokenSupplier = new AnonymousFlowTokenSupplier(
594            credentials.getClientId(), credentials.getClientSecret(), credentials.getScopes(), anonTokenEndpoint,
595            refreshFlowTokenSupplier, httpClient);
596
597        return new TokenStorageSupplier(tokenStorage, anonymousFlowTokenSupplier);
598    }
599
600    private RefreshFlowTokenSupplier createRefreshFlowSupplier(final ClientCredentials credentials,
601            final String tokenEndpoint, final TokenStorage tokenStorage, final VrapHttpClient httpClient) {
602        return new RefreshFlowTokenSupplier(credentials.getClientId(), credentials.getClientSecret(), tokenEndpoint,
603            tokenStorage, httpClient);
604    }
605
606    /**
607     * configure the client to use password flow
608     * @param credentials {@link ClientCredentials} to be used for authentication
609     * @param tokenEndpoint URI to be used for password flow authentication
610     * @param email customer email
611     * @param password customer password
612     * @return ClientBuilder instance
613     */
614    public ClientBuilder withGlobalCustomerPasswordFlow(final ClientCredentials credentials, final String email,
615            final String password, final String tokenEndpoint) {
616        return withGlobalCustomerPasswordFlow(credentials, email, password, tokenEndpoint, oauthHandlerSupplier());
617    }
618
619    /**
620     * configure the client to use password flow
621     * @param credentials {@link ClientCredentials} to be used for authentication
622     * @param tokenEndpoint URI to be used for password flow authentication
623     * @param email customer email
624     * @param password customer password
625     * @param httpClientSupplier {@link HandlerStack} to use for authentication
626     * @return ClientBuilder instance
627     */
628    public ClientBuilder withGlobalCustomerPasswordFlow(final ClientCredentials credentials, final String email,
629            final String password, final String tokenEndpoint, Supplier<HandlerStack> httpClientSupplier) {
630        return withTokenSupplier(
631            () -> createInMemoryTokenSupplier(createGlobalCustomerPasswordTokenSupplier(credentials, email, password,
632                tokenEndpoint, httpClientSupplier.get())));
633    }
634
635    /**
636     * configure the client to use password flow
637     * @param credentials {@link ClientCredentials} to be used for authentication
638     * @param tokenEndpoint URI to be used for password flow authentication
639     * @param email customer email
640     * @param password customer password
641     * @param httpClient {@link VrapHttpClient} to use for authentication
642     * @return ClientBuilder instance
643     */
644    public ClientBuilder withGlobalCustomerPasswordFlow(final ClientCredentials credentials, final String email,
645            final String password, final String tokenEndpoint, VrapHttpClient httpClient) {
646        return withTokenSupplier(() -> createInMemoryTokenSupplier(
647            createGlobalCustomerPasswordTokenSupplier(credentials, email, password, tokenEndpoint, httpClient)));
648    }
649
650    private TokenSupplier createGlobalCustomerPasswordTokenSupplier(final ClientCredentials credentials,
651            final String email, final String password, final String tokenEndpoint, final VrapHttpClient httpClient) {
652        return new GlobalCustomerPasswordTokenSupplier(credentials.getClientId(), credentials.getClientSecret(), email,
653            password, credentials.getScopes(), tokenEndpoint, httpClient);
654    }
655
656    /**
657     * add middleware to inject an <i>Accept: gzip</i> header
658     * @return ClientBuilder instance
659     */
660    public ClientBuilder addAcceptGZipMiddleware() {
661        return addMiddleware(AcceptGZipMiddleware.of());
662    }
663
664    /**
665     * add middleware to create Exceptions for responses with error status code
666     * @param errorMiddleware {@link ErrorMiddleware} to be used
667     * @return ClientBuilder instance
668     */
669    public ClientBuilder withErrorMiddleware(final Supplier<ErrorMiddleware> errorMiddleware) {
670        this.errorMiddleware = errorMiddleware;
671        return this;
672    }
673
674    /**
675     * add middleware to create Exceptions for responses with error status code
676     * @return ClientBuilder instance
677     */
678    public ClientBuilder withErrorMiddleware() {
679        return withErrorMiddleware(() -> ErrorMiddleware.of(httpExceptionFactory.get()));
680    }
681
682    /**
683     * add middleware to create Exceptions for responses with error status code
684     * @param errorMiddleware {@link ErrorMiddleware} to be used
685     * @return ClientBuilder instance
686     */
687    public ClientBuilder withErrorMiddleware(final ErrorMiddleware errorMiddleware) {
688        return withErrorMiddleware(() -> errorMiddleware);
689    }
690
691    /**
692     * add middleware to create Exceptions for responses with error status code
693     * @param exceptionMode either use CompletionExceptions or unwrap the Exception. See {@link ErrorMiddleware.ExceptionMode}
694     * @return ClientBuilder instance
695     */
696    public ClientBuilder withErrorMiddleware(ErrorMiddleware.ExceptionMode exceptionMode) {
697        return withErrorMiddleware(() -> ErrorMiddleware.of(httpExceptionFactory.get(), exceptionMode));
698    }
699
700    /**
701     * add middleware to collect and report telemetry data
702     * @param telemetryMiddleware {@link TelemetryMiddleware} to be used
703     * @return ClientBuilder instance
704     */
705    public ClientBuilder withTelemetryMiddleware(final Supplier<TelemetryMiddleware> telemetryMiddleware) {
706        this.telemetryMiddleware = telemetryMiddleware;
707        return this;
708    }
709
710    /**
711     * add middleware to collect and report telemetry data
712     * @param telemetryMiddleware {@link TelemetryMiddleware} to be used
713     * @return ClientBuilder instance
714     */
715    public ClientBuilder withTelemetryMiddleware(final TelemetryMiddleware telemetryMiddleware) {
716        return withTelemetryMiddleware(() -> telemetryMiddleware);
717    }
718
719    /**
720     * add Middleware to convert a {@link io.vrap.rmf.base.client.error.NotFoundException} to a response with a null body value
721     * @return ClientBuilder instance
722     */
723    public ClientBuilder addNotFoundExceptionMiddleware() {
724        return addNotFoundExceptionMiddleware(NotFoundExceptionMiddleware.of());
725    }
726
727    /**
728     * add Middleware to convert a {@link io.vrap.rmf.base.client.error.NotFoundException} to a response with a null body value
729     * @param methods HTTP methods to convert on {@link io.vrap.rmf.base.client.error.NotFoundException}
730     * @return ClientBuilder instance
731     */
732    public ClientBuilder addNotFoundExceptionMiddleware(final Set<ApiHttpMethod> methods) {
733        return addNotFoundExceptionMiddleware(NotFoundExceptionMiddleware.of(methods));
734    }
735
736    /**
737     * add Middleware to convert a {@link io.vrap.rmf.base.client.error.NotFoundException} to a response with a null body value
738     * @param requestPredicate predicate to match for converting {@link io.vrap.rmf.base.client.error.NotFoundException}
739     * @return ClientBuilder instance
740     */
741    public ClientBuilder addNotFoundExceptionMiddleware(final Predicate<ApiHttpRequest> requestPredicate) {
742        return addNotFoundExceptionMiddleware(NotFoundExceptionMiddleware.of(requestPredicate));
743    }
744
745    /**
746     * add Middleware to convert a {@link io.vrap.rmf.base.client.error.NotFoundException} to a response with a null body value
747     * @param exceptionMiddleware middleware to be used
748     * @return ClientBuilder instance
749     */
750    public ClientBuilder addNotFoundExceptionMiddleware(final NotFoundExceptionMiddleware exceptionMiddleware) {
751        return addMiddleware(exceptionMiddleware);
752    }
753
754    /**
755     * add middleware to retry failed requests
756     * @param retryMiddleware {@link RetryMiddleware} to be used
757     * @return ClientBuilder instance
758     */
759    public ClientBuilder withRetryMiddleware(final Supplier<RetryRequestMiddleware> retryMiddleware) {
760        this.retryMiddleware = retryMiddleware;
761        return this;
762    }
763
764    /**
765     * add middleware to retry failed requests
766     * @param retryMiddleware {@link RetryMiddleware} to be used
767     * @return ClientBuilder instance
768     */
769    public ClientBuilder withRetryMiddleware(final RetryRequestMiddleware retryMiddleware) {
770        return withRetryMiddleware(() -> retryMiddleware);
771    }
772
773    /**
774     * add middleware to retry failed requests. By default, status code 500 & 503 will be retried. Between each retry
775     * an incremental backoff strategy is applied
776     * @param maxRetries number of retries before giving uo
777     * @return ClientBuilder instance
778     */
779    public ClientBuilder withRetryMiddleware(final int maxRetries) {
780        return withRetryMiddleware(RetryRequestMiddleware.of(maxRetries));
781    }
782
783    /**
784     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
785     * @param maxRetries number of retries before giving uo
786     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
787     * @return ClientBuilder instance
788     */
789    public ClientBuilder withRetryMiddleware(final int maxRetries, List<Integer> statusCodes) {
790        return withRetryMiddleware(RetryRequestMiddleware.of(maxRetries, statusCodes));
791    }
792
793    /**
794     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
795     * @param maxRetries number of retries before giving uo
796     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
797     * @param failures {@link Throwable}s to be retried
798     * @return ClientBuilder instance
799     */
800    public ClientBuilder withRetryMiddleware(final int maxRetries, List<Integer> statusCodes,
801            final List<Class<? extends Throwable>> failures) {
802        return withRetryMiddleware(RetryRequestMiddleware.of(maxRetries, statusCodes, failures));
803    }
804
805    /**
806     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
807     * @param maxRetries number of retries before giving uo
808     * @param delay the initial delay for a retry
809     * @param maxDelay the maximum delay between each retry
810     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
811     * @param failures {@link Throwable}s to be retried
812     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
813     * @return ClientBuilder instance
814     */
815    public ClientBuilder withRetryMiddleware(final int maxRetries, final long delay, final long maxDelay,
816            List<Integer> statusCodes, final List<Class<? extends Throwable>> failures,
817            final FailsafeRetryPolicyBuilderOptions fn) {
818        return withRetryMiddleware(
819            RetryRequestMiddleware.of(maxRetries, delay, maxDelay, RetryRequestMiddleware.handleFailures(failures)
820                    .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes).andThen(fn))));
821    }
822
823    /**
824     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
825     * @param maxRetries number of retries before giving uo
826     * @param delay the initial delay for a retry
827     * @param maxDelay the maximum delay between each retry
828     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
829     * @return ClientBuilder instance
830     */
831    public ClientBuilder withRetryMiddleware(final int maxRetries, final long delay, final long maxDelay,
832            final FailsafeRetryPolicyBuilderOptions fn) {
833        return withRetryMiddleware(RetryRequestMiddleware.of(maxRetries, delay, maxDelay, fn));
834    }
835
836    /**
837     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
838     * @param executorService {@link ExecutorService} to be used for the retry handler
839     * @param maxRetries number of retries before giving uo
840     * @return ClientBuilder instance
841     */
842    public ClientBuilder withRetryMiddleware(final ExecutorService executorService, final int maxRetries) {
843        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries));
844    }
845
846    /**
847     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
848     * @param executorService {@link ExecutorService} to be used for the retry handler
849     * @param maxRetries number of retries before giving uo
850     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
851     * @return ClientBuilder instance
852     */
853    public ClientBuilder withRetryMiddleware(final ExecutorService executorService, final int maxRetries,
854            List<Integer> statusCodes) {
855        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, statusCodes));
856    }
857
858    /**
859     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
860     * @param executorService {@link ExecutorService} to be used for the retry handler
861     * @param maxRetries number of retries before giving uo
862     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
863     * @param failures {@link Throwable}s to be retried
864     * @return ClientBuilder instance
865     */
866    public ClientBuilder withRetryMiddleware(final ExecutorService executorService, final int maxRetries,
867            List<Integer> statusCodes, final List<Class<? extends Throwable>> failures) {
868        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, statusCodes, failures));
869    }
870
871    /**
872     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
873     * @param executorService {@link ExecutorService} to be used for the retry handler
874     * @param maxRetries number of retries before giving uo
875     * @param delay the initial delay for a retry
876     * @param maxDelay the maximum delay between each retry
877     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
878     * @param failures {@link Throwable}s to be retried
879     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
880     * @return ClientBuilder instance
881     */
882    public ClientBuilder withRetryMiddleware(final ExecutorService executorService, final int maxRetries,
883            final long delay, final long maxDelay, List<Integer> statusCodes,
884            final List<Class<? extends Throwable>> failures, final FailsafeRetryPolicyBuilderOptions fn) {
885        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, delay, maxDelay,
886            RetryRequestMiddleware.handleFailures(failures)
887                    .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes).andThen(fn))));
888    }
889
890    /**
891     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
892     * @param executorService {@link ExecutorService} to be used for the retry handler
893     * @param maxRetries number of retries before giving uo
894     * @param delay the initial delay for a retry
895     * @param maxDelay the maximum delay between each retry
896     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
897     * @return ClientBuilder instance
898     */
899    public ClientBuilder withRetryMiddleware(final ExecutorService executorService, final int maxRetries,
900            final long delay, final long maxDelay, final FailsafeRetryPolicyBuilderOptions fn) {
901        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, delay, maxDelay, fn));
902    }
903
904    /**
905     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
906     * @param executorService {@link ScheduledExecutorService} to be used for the retry handler
907     * @param maxRetries number of retries before giving uo
908     * @return ClientBuilder instance
909     */
910    public ClientBuilder withRetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries) {
911        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries));
912    }
913
914    /**
915     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
916     * @param executorService {@link ScheduledExecutorService} to be used for the retry handler
917     * @param maxRetries number of retries before giving uo
918     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
919     * @return ClientBuilder instance
920     */
921    public ClientBuilder withRetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries,
922            List<Integer> statusCodes) {
923        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, statusCodes));
924    }
925
926    /**
927     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
928     * @param executorService {@link ScheduledExecutorService} to be used for the retry handler
929     * @param maxRetries number of retries before giving uo
930     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
931     * @param failures {@link Throwable}s to be retried
932     * @return ClientBuilder instance
933     */
934    public ClientBuilder withRetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries,
935            List<Integer> statusCodes, final List<Class<? extends Throwable>> failures) {
936        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, statusCodes, failures));
937    }
938
939    /**
940     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
941     * @param executorService {@link ScheduledExecutorService} to be used for the retry handler
942     * @param maxRetries number of retries before giving uo
943     * @param delay the initial delay for a retry
944     * @param maxDelay the maximum delay between each retry
945     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
946     * @param failures {@link Throwable}s to be retried
947     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
948     * @return ClientBuilder instance
949     */
950    public ClientBuilder withRetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries,
951            final long delay, final long maxDelay, List<Integer> statusCodes,
952            final List<Class<? extends Throwable>> failures, final FailsafeRetryPolicyBuilderOptions fn) {
953        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, delay, maxDelay,
954            RetryRequestMiddleware.handleFailures(failures)
955                    .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes).andThen(fn))));
956    }
957
958    /**
959     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
960     * @param executorService {@link ScheduledExecutorService} to be used for the retry handler
961     * @param maxRetries number of retries before giving uo
962     * @param delay the initial delay for a retry
963     * @param maxDelay the maximum delay between each retry
964     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
965     * @return ClientBuilder instance
966     */
967    public ClientBuilder withRetryMiddleware(final ScheduledExecutorService executorService, final int maxRetries,
968            final long delay, final long maxDelay, final FailsafeRetryPolicyBuilderOptions fn) {
969        return withRetryMiddleware(RetryRequestMiddleware.of(executorService, maxRetries, delay, maxDelay, fn));
970    }
971
972    /**
973     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
974     * @param scheduler {@link Scheduler} to be used for the retry handler
975     * @param maxRetries number of retries before giving uo
976     * @return ClientBuilder instance
977     */
978    public ClientBuilder withRetryMiddleware(final Scheduler scheduler, final int maxRetries) {
979        return withRetryMiddleware(RetryRequestMiddleware.of(scheduler, maxRetries));
980    }
981
982    /**
983     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
984     * @param scheduler {@link Scheduler} to be used for the retry handler
985     * @param maxRetries number of retries before giving uo
986     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
987     * @return ClientBuilder instance
988     */
989    public ClientBuilder withRetryMiddleware(final Scheduler scheduler, final int maxRetries,
990            List<Integer> statusCodes) {
991        return withRetryMiddleware(RetryRequestMiddleware.of(scheduler, maxRetries, statusCodes));
992    }
993
994    /**
995     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
996     * @param scheduler {@link Scheduler} to be used for the retry handler
997     * @param maxRetries number of retries before giving uo
998     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
999     * @param failures {@link Throwable}s to be retried
1000     * @return ClientBuilder instance
1001     */
1002    public ClientBuilder withRetryMiddleware(final Scheduler scheduler, final int maxRetries, List<Integer> statusCodes,
1003            final List<Class<? extends Throwable>> failures) {
1004        return withRetryMiddleware(RetryRequestMiddleware.of(scheduler, maxRetries, statusCodes, failures));
1005    }
1006
1007    /**
1008     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
1009     * @param scheduler {@link Scheduler} to be used for the retry handler
1010     * @param maxRetries number of retries before giving uo
1011     * @param delay the initial delay for a retry
1012     * @param maxDelay the maximum delay between each retry
1013     * @param statusCodes HTTP status codes to retry a failed request e.g. 500 & 503
1014     * @param failures {@link Throwable}s to be retried
1015     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
1016     * @return ClientBuilder instance
1017     */
1018    public ClientBuilder withRetryMiddleware(final Scheduler scheduler, final int maxRetries, final long delay,
1019            final long maxDelay, List<Integer> statusCodes, final List<Class<? extends Throwable>> failures,
1020            final FailsafeRetryPolicyBuilderOptions fn) {
1021        return withRetryMiddleware(RetryRequestMiddleware.of(scheduler, maxRetries, delay, maxDelay,
1022            RetryRequestMiddleware.handleFailures(failures)
1023                    .andThen(RetryRequestMiddleware.handleStatusCodes(statusCodes).andThen(fn))));
1024    }
1025
1026    /**
1027     * add middleware to retry failed requests. Between each retry an incremental backoff strategy is applied
1028     * @param scheduler {@link Scheduler} to be used for the retry handler
1029     * @param maxRetries number of retries before giving uo
1030     * @param delay the initial delay for a retry
1031     * @param maxDelay the maximum delay between each retry
1032     * @param fn additional configuration for the {@link dev.failsafe.RetryPolicy} to be applied
1033     * @return ClientBuilder instance
1034     */
1035    public ClientBuilder withRetryMiddleware(final Scheduler scheduler, final int maxRetries, final long delay,
1036            final long maxDelay, final FailsafeRetryPolicyBuilderOptions fn) {
1037        return withRetryMiddleware(RetryRequestMiddleware.of(scheduler, maxRetries, delay, maxDelay, fn));
1038    }
1039
1040    /**
1041     * add middleware to limit the concurrent requests to be executed
1042     * @param queueMiddleware {@link QueueRequestMiddleware} to be used
1043     * @return ClientBuilder instance
1044     */
1045    public ClientBuilder withQueueMiddleware(final Supplier<QueueRequestMiddleware> queueMiddleware) {
1046        this.queueMiddleware = queueMiddleware;
1047        return this;
1048    }
1049
1050    /**
1051     * add middleware to limit the concurrent requests to be executed
1052     * @param queueMiddleware {@link QueueRequestMiddleware} to be used
1053     * @return ClientBuilder instance
1054     */
1055    public ClientBuilder withQueueMiddleware(final QueueRequestMiddleware queueMiddleware) {
1056        return withQueueMiddleware(() -> queueMiddleware);
1057    }
1058
1059    /**
1060     * add middleware to limit the concurrent requests to be executed
1061     * @param maxRequests maximum number of concurrent requests
1062     * @param maxWaitTime maximum time to wait before giving up
1063     * @return ClientBuilder instance
1064     */
1065    public ClientBuilder withQueueMiddleware(final int maxRequests, final Duration maxWaitTime) {
1066        return withQueueMiddleware(() -> QueueRequestMiddleware.of(maxRequests, maxWaitTime));
1067    }
1068
1069    /**
1070     * add middleware to limit the concurrent requests to be executed
1071     * @param scheduler {@link Scheduler} to be used for handling the queue
1072     * @param maxRequests maximum number of concurrent requests
1073     * @param maxWaitTime maximum time to wait before giving up
1074     * @return ClientBuilder instance
1075     */
1076    public ClientBuilder withQueueMiddleware(final Scheduler scheduler, final int maxRequests,
1077            final Duration maxWaitTime) {
1078        return withQueueMiddleware(() -> QueueRequestMiddleware.of(scheduler, maxRequests, maxWaitTime));
1079    }
1080
1081    /**
1082     * add middleware to limit the concurrent requests to be executed
1083     * @param executorService {@link ScheduledExecutorService} to be used for handling the queue
1084     * @param maxRequests maximum number of concurrent requests
1085     * @param maxWaitTime maximum time to wait before giving up
1086     * @return ClientBuilder instance
1087     */
1088    public ClientBuilder withQueueMiddleware(final ScheduledExecutorService executorService, final int maxRequests,
1089            final Duration maxWaitTime) {
1090        return withQueueMiddleware(() -> QueueRequestMiddleware.of(executorService, maxRequests, maxWaitTime));
1091    }
1092
1093    /**
1094     * add middleware to limit the concurrent requests to be executed
1095     * @param executorService {@link ExecutorService} to be used for handling the queue
1096     * @param maxRequests maximum number of concurrent requests
1097     * @param maxWaitTime maximum time to wait before giving up
1098     * @return ClientBuilder instance
1099     */
1100    public ClientBuilder withQueueMiddleware(final ExecutorService executorService, final int maxRequests,
1101            final Duration maxWaitTime) {
1102        return withQueueMiddleware(() -> QueueRequestMiddleware.of(executorService, maxRequests, maxWaitTime));
1103    }
1104
1105    /**
1106     * add authenticator middleware
1107     * @param oAuthMiddleware {@link OAuthMiddleware} to be used for authentication
1108     * @return ClientBuilder instance
1109     */
1110    public ClientBuilder withOAuthMiddleware(final Supplier<OAuthMiddleware> oAuthMiddleware) {
1111        this.oAuthMiddleware = oAuthMiddleware;
1112        return this;
1113    }
1114
1115    /**
1116     * add authenticator middleware
1117     * @param oAuthMiddleware {@link OAuthMiddleware} to be used for authentication
1118     * @return ClientBuilder instance
1119     */
1120    public ClientBuilder withOAuthMiddleware(final OAuthMiddleware oAuthMiddleware) {
1121        return withOAuthMiddleware(() -> oAuthMiddleware);
1122    }
1123
1124    /**
1125     * use supplier for authentication tokens
1126     * @param tokenSupplier {@link TokenSupplier} for retrieving authentication tokens
1127     * @return ClientBuilder instance
1128     */
1129    public ClientBuilder withTokenSupplier(final Supplier<TokenSupplier> tokenSupplier) {
1130        return withOAuthMiddleware(() -> {
1131            final OAuthHandler oAuthHandler = new OAuthHandler(tokenSupplier.get());
1132            return Optional.ofNullable(oauthExecutorService)
1133                    .map(executorService -> OAuthMiddleware.of(executorService.get(), oAuthHandler, authRetries,
1134                        useAuthCircuitBreaker))
1135                    .orElseGet(() -> OAuthMiddleware.of(oAuthHandler, authRetries, useAuthCircuitBreaker));
1136        });
1137    }
1138
1139    /**
1140     * use supplier for authentication tokens
1141     * @param tokenSupplier {@link TokenSupplier} for retrieving authentication tokens
1142     * @return ClientBuilder instance
1143     */
1144    public ClientBuilder withTokenSupplier(final TokenSupplier tokenSupplier) {
1145        return withTokenSupplier(() -> tokenSupplier);
1146    }
1147
1148    /**
1149     * @param internalLoggerMiddleware {@link InternalLoggerMiddleware} used for logging requests and responses
1150     * @return ClientBuilder instance
1151     */
1152    public ClientBuilder withInternalLoggerMiddleware(final InternalLoggerMiddleware internalLoggerMiddleware) {
1153        this.internalLoggerMiddleware = internalLoggerMiddleware;
1154        return this;
1155    }
1156
1157    /**
1158     * @param internalLoggerFactory {@link InternalLoggerFactory} creates the logger for request & responses
1159     * @return ClientBuilder instance
1160     */
1161    public ClientBuilder withInternalLoggerFactory(final InternalLoggerFactory internalLoggerFactory) {
1162        return withInternalLoggerMiddleware(InternalLoggerMiddleware.of(internalLoggerFactory));
1163    }
1164
1165    /**
1166     * @param internalLoggerFactory {@link InternalLoggerFactory} creates the logger for request & responses
1167     * @param responseLogEvent {@link Level} for logging responses.
1168     * @param deprecationLogEvent {@link Level} for logging {@link ApiHttpHeaders#X_DEPRECATION_NOTICE}
1169     * @return ClientBuilder instance
1170     */
1171    public ClientBuilder withInternalLoggerFactory(final InternalLoggerFactory internalLoggerFactory,
1172            final Level responseLogEvent, final Level deprecationLogEvent) {
1173        return withInternalLoggerMiddleware(
1174            InternalLoggerMiddleware.of(internalLoggerFactory, responseLogEvent, deprecationLogEvent));
1175    }
1176
1177    /**
1178     * @param internalLoggerFactory {@link InternalLoggerFactory} creates the logger for request & responses
1179     * @param responseLogEvent {@link Level} for logging responses.
1180     * @param deprecationLogEvent {@link Level} for logging {@link ApiHttpHeaders#X_DEPRECATION_NOTICE}
1181     * @param defaultExceptionLogEvent {@link Level} for logging errors
1182     * @param exceptionLogEvents {@link Level} for logging by exception class
1183     * @return ClientBuilder instance
1184     */
1185    public ClientBuilder withInternalLoggerFactory(final InternalLoggerFactory internalLoggerFactory,
1186            final Level responseLogEvent, final Level deprecationLogEvent, final Level defaultExceptionLogEvent,
1187            final Map<Class<? extends Throwable>, Level> exceptionLogEvents) {
1188        return withInternalLoggerMiddleware(InternalLoggerMiddleware.of(internalLoggerFactory, responseLogEvent,
1189            deprecationLogEvent, defaultExceptionLogEvent, exceptionLogEvents));
1190    }
1191
1192    /**
1193     * @param apiBaseUrl base URI for calling the API
1194     * @return ClientBuilder instance
1195     */
1196    public ClientBuilder withApiBaseUrl(String apiBaseUrl) {
1197        return withApiBaseUrl(URI.create(apiBaseUrl));
1198    }
1199
1200    /**
1201     * @param apiBaseUrl base URI for calling the API
1202     * @return ClientBuilder instance
1203     */
1204    public ClientBuilder withApiBaseUrl(final URI apiBaseUrl) {
1205        if (!apiBaseUrl.getPath().endsWith("/")) {
1206            this.apiBaseUrl = URI.create(apiBaseUrl + "/");
1207            return this;
1208        }
1209        this.apiBaseUrl = apiBaseUrl;
1210        return this;
1211    }
1212
1213    /**
1214     * @param userAgentSupplier user agent to be send with the requests
1215     * @return ClientBuilder instance
1216     */
1217    public ClientBuilder withUserAgentSupplier(final Supplier<String> userAgentSupplier) {
1218        return withUserAgentMiddleware(new UserAgentMiddleware(userAgentSupplier.get()));
1219    }
1220
1221    private ClientBuilder withUserAgentMiddleware(final UserAgentMiddleware userAgentMiddleware) {
1222        this.userAgentMiddleware = userAgentMiddleware;
1223        return this;
1224    }
1225
1226    /**
1227     * @param correlationIdProvider provider to create correlation IDs for each request
1228     * @param replace replace any existing correlation id provider
1229     * @return ClientBuilder instance
1230     */
1231    public ClientBuilder addCorrelationIdProvider(final @Nullable CorrelationIdProvider correlationIdProvider,
1232            final boolean replace) {
1233        if (!replace && correlationIdMiddleware != null) {
1234            return this;
1235        }
1236        if (correlationIdProvider != null) {
1237            correlationIdMiddleware = () -> (request, next) -> {
1238                if (request.getHeaders().getFirst(ApiHttpHeaders.X_CORRELATION_ID) != null) {
1239                    return next.apply(request);
1240                }
1241                return next.apply(
1242                    request.withHeader(ApiHttpHeaders.X_CORRELATION_ID, correlationIdProvider.getCorrelationId()));
1243            };
1244        }
1245        return this;
1246    }
1247
1248    /**
1249     * @param correlationIdProvider provider to create correlation IDs for each request
1250     * @return ClientBuilder instance
1251     */
1252    public ClientBuilder addCorrelationIdProvider(final @Nullable CorrelationIdProvider correlationIdProvider) {
1253        return addCorrelationIdProvider(correlationIdProvider, true);
1254    }
1255
1256    /**
1257     * sets the middlewares to be configured for the client.
1258     * @param middlewares {@link Middleware} instances
1259     * @return ClientBuilder instance
1260     */
1261    public ClientBuilder withMiddlewares(final List<Middleware> middlewares) {
1262        this.middlewares = new ArrayList<>(middlewares);
1263        return this;
1264    }
1265
1266    /**
1267     * sets the middlewares to be configured for the client.
1268     * @param middleware {@link Middleware} instance
1269     * @param middlewares {@link Middleware} instances
1270     * @return ClientBuilder instance
1271     */
1272    public ClientBuilder withMiddleware(final Middleware middleware, final Middleware... middlewares) {
1273        this.middlewares = new ArrayList<>(Collections.singletonList(middleware));
1274        if (middlewares.length > 0) {
1275            this.middlewares.addAll(Arrays.asList(middlewares));
1276        }
1277        return this;
1278    }
1279
1280    /**
1281     * adds the middlewares to be configured for the client.
1282     * @param middlewares {@link Middleware} instances
1283     * @return ClientBuilder instance
1284     */
1285    public ClientBuilder addMiddlewares(final List<Middleware> middlewares) {
1286        this.middlewares.addAll(middlewares);
1287        return this;
1288    }
1289
1290    /**
1291     * adds the middlewares to be configured for the client.
1292     * @param middleware {@link Middleware} instance
1293     * @param middlewares {@link Middleware} instances
1294     * @return ClientBuilder instance
1295     */
1296    public ClientBuilder addMiddleware(final Middleware middleware, final Middleware... middlewares) {
1297        this.middlewares.add(middleware);
1298        if (middlewares.length > 0) {
1299            this.middlewares.addAll(Arrays.asList(middlewares));
1300        }
1301        return this;
1302    }
1303
1304    /**
1305     * build the {@link ApiHttpClient} with the configured values
1306     * @return {@link ApiHttpClient}
1307     */
1308    public ApiHttpClient build() {
1309        return ApiHttpClient.of(requireNonNull(apiBaseUrl), requireNonNull(stack.get()),
1310            requireNonNull(serializer.get()));
1311    }
1312
1313    /**
1314     * default user agent provider
1315     * @return user agent string
1316     */
1317    public static String buildDefaultUserAgent() {
1318        return UserAgentUtils.obtainUserAgent();
1319    }
1320}