com.c4_soft.springaddons.rest.SpringAddonsRestProperties Maven / Gradle / Ivy
package com.c4_soft.springaddons.rest;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.service.annotation.HttpExchange;
import lombok.Data;
/**
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
*/
@Data
@AutoConfiguration
@ConfigurationProperties(prefix = "com.c4-soft.springaddons.rest")
public class SpringAddonsRestProperties {
/**
* Expose {@link RestClient} or {@link WebClient} instances as named beans
*/
private Map client = new HashMap<>();
// FIXME: enable when a way is found to generate and register service proxies as beans.
// For instance, have the HttpExchangeProxyFactoryBean definitions registered with a
// BeanDefinitionRegistryPostProcessor
// /**
// * Expose {@link HttpExchange @HttpExchange} proxies as named beans (generated using
// * {@link HttpServiceProxyFactory})
// */
// private Map service = new HashMap<>();
public String getClientBeanName(String clientId) {
if (!client.containsKey(clientId)) {
return null;
}
final var clientProperties = client.get(clientId);
return clientProperties.getBeanName()
.orElse(clientProperties.isExposeBuilder() ? toCamelCase(clientId) + "Builder"
: toCamelCase(clientId));
}
private static String toCamelCase(String in) {
if (in == null) {
return null;
}
if (!StringUtils.hasText(in)) {
return "";
}
String[] words = in.split("[\\W_]+");
StringBuilder builder = new StringBuilder();
for (int i = 0; i < words.length; i++) {
String word = words[i];
if (i == 0) {
word = word.isEmpty() ? word : word.toLowerCase();
} else {
word = word.isEmpty() ? word
: Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase();
}
builder.append(word);
}
return builder.toString();
}
@Data
public static class RestClientProperties {
/**
* Base URI used to build the REST client ({@link RestClient} or {@link WebClient})
*/
private Optional baseUrl = Optional.empty();
/**
* Configure a {@link ClientHttpRequestInterceptor} or {@link ExchangeFilterFunction} to
* authorize requests (add a Basic or Bearer header to each request)
*/
private AuthorizationProperties authorization = new AuthorizationProperties();
/**
* Configure the internal {@link SimpleClientHttpRequestFactory} with timeouts and HTTP or SOCKS
* proxy
*/
private ClientHttpRequestFactoryProperties http = new ClientHttpRequestFactoryProperties();
/**
* Defines the type of the REST client. Default is {@link RestClient} in servlet applications
* and {@link WebClient} in reactive ones.
*/
private ClientType type = ClientType.DEFAULT;
/**
* If true, what is exposed as a bean is the pre-configured {@link RestClient.Builder} or
* {@link WebClient.Builder}. This allows to add some more configuration. Don't forget to expose
* the resulting {@link RestClient} or {@link WebClient} as a named bean if you intend to use it
* as the REST client in an auto-configured {@link HttpExchange @HttpExchange} proxy.
*/
private boolean exposeBuilder = false;
/**
*
* Override the auto-configured bean name which defaults to the camelCase version of the
* client-id, with the "Builder" suffix if expose-builder is true.
*
*
* For instance, "com.c4-soft.springaddons.rest.client.machin-client" will create a bean named
* machinClient or machinClientBuilder depending on
* "com.c4-soft.springaddons.rest.client.machin-client.expose-builder" value.
*
*/
private Optional beanName = Optional.empty();
/**
* Some static headers to add to all requests sent by this client (for instance an API key). The
* key is the header name. If the value contains several entries, the header is set several
* times.
*/
private Map> headers = new HashMap<>();
public Optional getBaseUrl() {
return baseUrl.map(t -> {
try {
return new URL(t);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
});
}
@Data
public static class AuthorizationProperties {
private OAuth2Properties oauth2 = new OAuth2Properties();
private BasicAuthProperties basic = new BasicAuthProperties();
boolean isConfigured() {
return oauth2.isConfigured() || basic.isConfigured();
}
boolean isConfValid() {
return oauth2.isConfValid() && basic.isConfValid()
&& (!oauth2.isConfigured() || !basic.isConfigured());
}
@Data
public static class OAuth2Properties {
/**
*
* If provided, it is used to get an access token from the
* {@link OAuth2AuthorizedClientManager}.
*
*
* Must reference a valid entry under spring.security.oauth2.client.registration
*
*
* Mutually exclusive with forward-bearer property.
*
*/
private Optional oauth2RegistrationId = Optional.empty();
/**
*
* If true, the access token is taken from the {@link Authentication} in the security
* context.
*
*
* Mutually exclusive with auth2-registration-id property.
*
*/
private boolean forwardBearer = false;
boolean isConfigured() {
return forwardBearer || oauth2RegistrationId.isPresent();
}
boolean isConfValid() {
return !forwardBearer || oauth2RegistrationId.isEmpty();
}
}
@Data
public static class BasicAuthProperties {
private Optional username = Optional.empty();
private Optional password = Optional.empty();
private Optional charset = Optional.empty();
private Optional encodedCredentials = Optional.empty();
boolean isConfigured() {
return encodedCredentials.isPresent() || username.isPresent();
}
boolean isConfValid() {
return encodedCredentials.isEmpty() || (username.isEmpty() && password.isEmpty());
}
}
}
@Data
public static class ClientHttpRequestFactoryProperties {
/**
*
* Configure Proxy-Authorization header for authentication on a HTTP or SOCKS proxy. This
* header auto-configuration can be disable on each client.
*
*
* HTTP_PROXY and NO_PROXY standard environment variable are used only if
* "com.c4-soft.springaddons.rest.proxy.hostname" is left empty and
* "com.c4-soft.springaddons.rest.proxy.enabled" is TRUE or null. In other words, if the
* standard environment variables are correctly set, leaving "proxy" properties empty here is
* probably the best option.
*
*/
private ProxyProperties proxy = new ProxyProperties();
/**
* Connection timeout in milliseconds.
*/
private Optional connectTimeoutMillis = Optional.empty();
/**
* Read timeout in milliseconds.
*/
private Optional readTimeoutMillis = Optional.empty();
@Data
public static class ProxyProperties {
private boolean enabled = true;
private String protocol = "http";
private int port = 8080;
private String username;
private String password;
private int connectTimeoutMillis = 10000;
private Optional host = Optional.empty();
private String nonProxyHostsPattern;
}
}
public static enum ClientType {
DEFAULT, REST_CLIENT, WEB_CLIENT;
}
}
@Data
public static class RestServiceProperties {
/**
*
* Name of a {@link RestClient} or {@link WebClient} bean.
*
* Note that:
*
* - This bean does not have to be one of the auto-generated REST clients.
* - The value is a REST client bean name, not a "com.c4-soft.springaddons.rest.client"
* key, which is the ID of for an auto-generated REST client (or builder) bean.
* - As a reminder, auto-generated REST client beans hare named with a camel-case version of
* their ID. For instance "com.c4-soft.springaddons.rest.client.machin-client" properties would
* create a bean named "machinClient"
*
*/
private String clientBeanName;
/**
* Fully qualified class name of the {@link HttpExchange} to implement
*/
private String httpExchangeClass;
/**
*
* Override the auto-configured bean name which defaults to the camelCase version of the
* client-id.
*
*/
private Optional beanName = Optional.empty();
}
}