ReactiveApiClientAutoConfiguration.java
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.apiclient.webflux.spring.boot.autoconfigure;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.bremersee.apiclient.webflux.Invocation;
import org.bremersee.apiclient.webflux.ReactiveApiClient;
import org.bremersee.apiclient.webflux.ReactiveContract;
import org.bremersee.apiclient.webflux.ReactiveErrorHandler;
import org.bremersee.apiclient.webflux.contract.HeadersConsumer;
import org.bremersee.apiclient.webflux.contract.RequestBodyInserter;
import org.bremersee.apiclient.webflux.contract.RequestBodyInserterRegistry;
import org.bremersee.apiclient.webflux.contract.RequestUriFunction;
import org.bremersee.apiclient.webflux.contract.spring.AcceptResolver;
import org.bremersee.apiclient.webflux.contract.spring.ContentTypeResolver;
import org.bremersee.apiclient.webflux.contract.spring.DataBuffersInserter;
import org.bremersee.apiclient.webflux.contract.spring.FormDataInserter;
import org.bremersee.apiclient.webflux.contract.spring.MultipartDataInserter;
import org.bremersee.apiclient.webflux.contract.spring.PageableRequestParameterResolver;
import org.bremersee.apiclient.webflux.contract.spring.PartToHttpEntityConverter;
import org.bremersee.apiclient.webflux.contract.spring.PathVariablesResolver;
import org.bremersee.apiclient.webflux.contract.spring.PublisherInserter;
import org.bremersee.apiclient.webflux.contract.spring.QueryParametersResolver;
import org.bremersee.apiclient.webflux.contract.spring.ReactiveSpringContract;
import org.bremersee.apiclient.webflux.contract.spring.RequestHeadersResolver;
import org.bremersee.apiclient.webflux.contract.spring.RequestParametersResolver;
import org.bremersee.apiclient.webflux.contract.spring.RequestPathResolver;
import org.bremersee.apiclient.webflux.contract.spring.ResourceInserter;
import org.bremersee.apiclient.webflux.contract.spring.SortRequestParameterResolver;
import org.bremersee.apiclient.webflux.contract.spring.ValueInserter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpEntity;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
/**
* The reactive api client autoconfiguration.
*
* @author Christian Bremer
*/
@SuppressWarnings("SameNameButDifferent")
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass({ReactiveApiClient.class, ReactiveSpringContract.class})
@Configuration
@Slf4j
public class ReactiveApiClientAutoConfiguration {
/**
* Init.
*/
@EventListener(ApplicationReadyEvent.class)
public void init() {
log.info("\n"
+ "*********************************************************************************\n"
+ "* {}\n"
+ "*********************************************************************************",
ClassUtils.getUserClass(getClass()).getSimpleName());
}
/**
* Content type resolver.
*
* @return the content type resolver
*/
@ConditionalOnMissingBean
@Bean
public ContentTypeResolver contentTypeResolver() {
return new ContentTypeResolver();
}
/**
* Request parameters resolver.
*
* @return the request parameters resolver
*/
@ConditionalOnMissingBean
@Bean
@Order(-1000)
public RequestParametersResolver requestParametersResolver() {
log.info("Creating {} with order {}", RequestParametersResolver.class.getSimpleName(), -1000);
return new RequestParametersResolver();
}
/**
* Sort request parameter resolver.
*
* @return the sort request parameter resolver
*/
@ConditionalOnClass(name = "org.springframework.data.domain.Sort")
@ConditionalOnMissingBean
@Bean
@Order(-500)
public SortRequestParameterResolver sortRequestParameterResolver() {
log.info("Creating {} with order {}", SortRequestParameterResolver.class.getSimpleName(), -500);
return new SortRequestParameterResolver();
}
/**
* Pageable request parameter resolver.
*
* @return the pageable request parameter resolver
*/
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
@Bean
@Order(-510)
public PageableRequestParameterResolver pageableRequestParameterResolver() {
log.info(
"Creating {} with order {}",
PageableRequestParameterResolver.class.getSimpleName(),
-510);
return new PageableRequestParameterResolver();
}
/**
* Form data inserter.
*
* @param contentTypeResolver the content type resolver
* @return the request body inserter
*/
@Bean
@Order(100)
public RequestBodyInserter formDataInserter(ContentTypeResolver contentTypeResolver) {
log.info("Creating {} with order {}", FormDataInserter.class.getSimpleName(), 100);
return new FormDataInserter()
.withContentTypeResolver(contentTypeResolver);
}
/**
* Multipart data inserter.
*
* @param contentTypeResolver the content type resolver
* @param partConverter the part converter
* @return the request body inserter
*/
@Bean
@Order(200)
public RequestBodyInserter multipartDataInserter(
ContentTypeResolver contentTypeResolver,
ObjectProvider<Converter<Part, HttpEntity<?>>> partConverter) {
log.info("Creating {} with order {}", MultipartDataInserter.class.getSimpleName(), 200);
return new MultipartDataInserter()
.withContentTypeResolver(contentTypeResolver)
.withPartConverter(partConverter.getIfAvailable(PartToHttpEntityConverter::new));
}
/**
* Resource inserter.
*
* @return the request body inserter
*/
@Bean
@Order(300)
public RequestBodyInserter resourceInserter() {
log.info("Creating {} with order {}", ResourceInserter.class.getSimpleName(), 300);
return new ResourceInserter();
}
/**
* Data buffers inserter.
*
* @return the request body inserter
*/
@Bean
@Order(400)
public RequestBodyInserter dataBuffersInserter() {
log.info("Creating {} with order {}", DataBuffersInserter.class.getSimpleName(), 400);
return new DataBuffersInserter();
}
/**
* Publisher inserter.
*
* @return the request body inserter
*/
@Bean
@Order(500)
public RequestBodyInserter publisherInserter() {
log.info("Creating {} with order {}", PublisherInserter.class.getSimpleName(), 500);
return new PublisherInserter();
}
/**
* Value inserter.
*
* @return the request body inserter
*/
@Bean
@Order(600)
public RequestBodyInserter valueInserter() {
log.info("Creating {} with order {}", ValueInserter.class.getSimpleName(), 600);
return new ValueInserter();
}
/**
* Request body inserter registry.
*
* @param requestBodyInserters the request body inserters
* @return the request body inserter registry
*/
@ConditionalOnMissingBean
@Bean
public RequestBodyInserterRegistry requestBodyInserterRegistry(
ObjectProvider<RequestBodyInserter> requestBodyInserters) {
List<RequestBodyInserter> requestBodyInserterList = requestBodyInserters
.orderedStream()
.collect(Collectors.toList());
log.info(
"Creating {} with inserters {}",
RequestBodyInserterRegistry.class.getSimpleName(),
requestBodyInserterList);
return RequestBodyInserterRegistry.builder()
.requestBodyInserters(requestBodyInserterList)
.build();
}
/**
* Reactive spring contract.
*
* @param contentTypeResolver the content type resolver
* @param queryParametersResolvers the query parameters resolvers
* @param requestBodyInserterRegistry the request body inserter registry
* @return the reactive contract
*/
@ConditionalOnMissingBean
@Bean
public ReactiveContract reactiveSpringContract(
ContentTypeResolver contentTypeResolver,
ObjectProvider<QueryParametersResolver> queryParametersResolvers,
RequestBodyInserterRegistry requestBodyInserterRegistry) {
List<Function<Invocation, MultiValueMap<String, Object>>> queryParametersResolverList
= queryParametersResolvers.orderedStream().collect(Collectors.toList());
log.info(
"Creating {} with queryParametersResolvers {}",
ReactiveContract.class.getSimpleName(),
queryParametersResolverList);
ReactiveSpringContract reactiveSpringContract = new ReactiveSpringContract();
return ReactiveContract.builder()
.from(reactiveSpringContract)
.headersConsumer(HeadersConsumer.builder()
.contentTypeResolver(contentTypeResolver)
.acceptResolver(new AcceptResolver())
.headersResolver(new RequestHeadersResolver())
.build())
.requestUriFunction(RequestUriFunction.builder()
.requestPathResolver(new RequestPathResolver())
.pathVariablesResolver(new PathVariablesResolver())
.requestParametersResolvers(queryParametersResolverList)
.build())
.requestBodyInserterFunction(requestBodyInserterRegistry)
.build();
}
/**
* Reactive api client.
*
* @param configurers the configurers
* @param reactiveContract the reactive contract
* @param errorHandler the error handler
* @return the reactive api client
*/
@ConditionalOnMissingBean
@Bean
public ReactiveApiClient reactiveApiClient(
ObjectProvider<ReactiveApiClientWebClientBuilderConfigurer> configurers,
ReactiveContract reactiveContract,
ObjectProvider<ReactiveErrorHandler> errorHandler) {
WebClient.Builder webClientBuilder = WebClient.builder();
configurers.orderedStream().forEach(configurer -> configurer.configure(webClientBuilder));
return new ReactiveApiClient(webClientBuilder, reactiveContract, errorHandler.getIfAvailable());
}
}