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