How to connect to multiple servers in spring data elasticsearch - spring-boot

I use the following configuration for elastic and I want to use multiple addresses instead of one
#Configuration
#EnableElasticsearchRepositories
class Config {
#Bean
fun client(): RestHighLevelClient {
val clientConfiguration = ClientConfiguration.builder()
.connectedTo("127.0.0.1:9200")
.build()
return RestClients.create(clientConfiguration).rest()
}
#Bean
fun elasticsearchTemplate(): ElasticsearchOperations {
return ElasticsearchRestTemplate(client())
}
}

You can pass multiple host:port Strings to the connectedTo() method:
ClientConfiguration.builder()
.connectedTo("IP_1:PORT_1", "IP_2:PORT_2")
.build()

Related

Best Way to connect from spring boot to Elasticsearch without deprecations

#Bean
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration
= ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
#Bean
public Client client() throws Exception {
Settings settings = Settings.builder().put("cluster.name", esClusterName).build();
TransportClient client = new TransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName(esHost), esPort));
return client;
}
I have been trying to connect to elasticsearch from spring boot using following techniques:
RestHighLevelClient & TransportClient
Custom binary port : 9300
as these techniques are deprecated.
Please help me with best recommended solution to this.
Thanks,
Dilip
You can probably allow the auto-configuration to create the client for you, so it isn't necessary to create the bean. Set the configuration properties if needed:
spring.elasticsearch.rest.uris=http://localhost:9300
All properties can be found here.

Multiple Redis For Spring Cache And JWT

I have actually an application that is using Redis for Cache and keeping the JWT token into redis.
My goal is to have one redis for cache, and another one for jwt token.
I dont understand how i can achieve this.
How can i say to spring to use a specific redis (here "redis-cache") for caching ?
Actually i only put #EnableCaching and it is working properly
Thanks for any help
spring:
redis:
port: 7000
password: password123
host: 127.0.0.1
redis-cache: # New One
port: 7001
password: password123
host: 127.0.0.1
I'm using redisTemplate to keep jwt token into redis
#Configuration
public class GenericBeanConfig {
#Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
Jackson2JsonRedisSerializer<String> jrs = new Jackson2JsonRedisSerializer<String>(String.class);
template.setKeySerializer(jrs);
template.setConnectionFactory(connectionFactory);
return template;
}
}
...
#EnableCaching
public class ProjectsApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectsApplication .class, args);
}
}
Caching some endpoint
#Cacheable(value = "users-rbac")
public UserResponseDTO search(#PathVariable String email) {
return userService.search(email);
}
You can define your custom CacheManager as well, you define your cache manager as follows. Customize this as you see, for now host, port and password are fixed but you can read them from application config file using #Value .
#Bean
public CacheManager cacheManager() {
// create redis configuration
RedisStandaloneConfiguration configuration =
new RedisStandaloneConfiguration("127.0.0.1", 7001);
configuration.setPassword(RedisPassword.of("password"));
// create a connection factory, use other connection factory if you want
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
factory.afterPropertiesSet();
RedisCacheWriter writer = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
// define cache cnfiguration
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
// use Jackson serdes do not use JDK one
cacheConfiguration.serializeValuesWith(
SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// create cache manager
return new RedisCacheManager(writer, cacheConfiguration);
}

Conditionally override default datasource in springboot

I am trying to configure the spring data source programmatically. The idea is to override the default data source if tenant information is specified or else use the default data source for backward compatibility. I tried coming up with something like :
#Component
#AllArgsConstructor
public class TenancyDataSource {
private TenantConfigs tenantConfigs;
private DataSource dataSource;
#Bean
public DataSource dataSource() {
String tenant = System.getenv(AppConstants.TENANT);
if (!ObjectUtils.isEmpty(tenant)) {
TenantConfigs config =
tenantConfigs
.getDataSources()
.stream()
.filter(TenantConfig -> tenantConfig.getTenantName().equals(tenant))
.findFirst()
.orElse(null);
if (!ObjectUtils.isEmpty(config)) {
return DataSourceBuilder.create()
.url(config.getUrl())
.password(config.getPassword())
.username(config.getUsername())
.build();
}
}
return dataSource;
}
}
But this does not work due to circular dependency.
What would be the best way to implement it ?
SpringBoot treats environment variables as properties by default. So using #ConditionalOnProperty with the "tenant" property worked
#Bean
#ConditionalOnProperty(name = AppConstants.TENANT)
public DataSource dataSource() {
return DataSourceBuilder.create()
.url(config.getUrl())
.password(config.getPassword())
.username(config.getUsername())
.build();
}

Spring Boot RSocketRequester deal with server restart

I have a question about Springs RSocketRequester. I have a rsocket server and client. Client connects to this server and requests #MessageMapping endpoint. It works as expected.
But what if I restart the server. How to do automatic reconnect to rsocket server from client? Thanks
Server:
#Controller
class RSC {
#MessageMapping("pong")
public Mono<String> pong(String m) {
return Mono.just("PONG " + m);
}
}
Client:
#Bean
public RSocketRequester rSocketRequester() {
return RSocketRequester
.builder()
.connectTcp("localhost", 7000)
.block();
}
#RestController
class RST {
#Autowired
private RSocketRequester requester;
#GetMapping(path = "/ping")
public Mono<String> ping(){
return this.requester
.route("pong")
.data("TEST")
.retrieveMono(String.class)
.doOnNext(System.out::println);
}
}
Updated for Spring Framework 5.2.6+
You could achieve it with io.rsocket.core.RSocketConnector#reconnect.
#Bean
Mono<RSocketRequester> rSocketRequester(RSocketRequester.Builder rSocketRequesterBuilder) {
return rSocketRequesterBuilder
.rsocketConnector(connector -> connector
.reconnect(Retry.fixedDelay(Integer.MAX_VALUE, Duration.ofSeconds(1))))
.connectTcp("localhost", 7000);
}
#RestController
public class RST {
#Autowired
private Mono<RSocketRequester> rSocketRequesterMono;
#GetMapping(path = "/ping")
public Mono<String> ping() {
return rSocketRequesterMono.flatMap(rSocketRequester ->
rSocketRequester.route("pong")
.data("TEST")
.retrieveMono(String.class)
.doOnNext(System.out::println));
}
}
I don't think I would create a RSocketRequester bean in an application. Unlike WebClient (which has a pool of reusable connections), the RSocket requester wraps a single RSocket, i.e. a single network connection.
I think it's best to store a Mono<RSocketRequester> and subscribe to that to get an actual requester when needed. Because you don't want to create a new connection for each call, you can cache the result. Thanks to Mono retryXYZ operators, there are many ways you can refine the reconnection behavior.
You could try something like the following:
#Service
public class RSocketPingService {
private final Mono<RSocketRequester> requesterMono;
// Spring Boot is creating an auto-configured RSocketRequester.Builder bean
public RSocketPingService(RSocketRequester.Builder builder) {
this.requesterMono = builder
.dataMimeType(MediaType.APPLICATION_CBOR)
.connectTcp("localhost", 7000).retry(5).cache();
}
public Mono<String> ping() {
return this.requesterMono.flatMap(requester -> requester.route("pong")
.data("TEST")
.retrieveMono(String.class));
}
}
the answer here https://stackoverflow.com/a/58890649/2852528 is the right one. The only thing I would like to add is that reactor.util.retry.Retry has many options for configuring the logic of your retry including even logging.
So I would slightly improve the original answer, so we'd be increasing the time between the retry till riching the max value (16 sec) and before each retry log the failure - so we could monitor the activity of the connector:
#Bean
Mono<RSocketRequester> rSocketRequester(RSocketRequester.Builder builder) {
return builder.rsocketConnector(connector -> connector.reconnect(Retry.backoff(Integer.MAX_VALUE, Duration.ofSeconds(1L))
.maxBackoff(Duration.ofSeconds(16L))
.jitter(1.0D)
.doBeforeRetry((signal) -> log.error("connection error", signal.failure()))))
.connectTcp("localhost", 7000);
}

Configured ObjectMapper not used in spring-boot-webflux

I have mixins configured in my objectmapperbuilder config, using the regular spring web controller, the data outputted according to the mixins.
However using webflux, a controller with a method returning a Flow or Mono have the data serialized like if the objectmapper a default one.
How to get webflux to enforce an objectmapper configuration to be used ?
sample config:
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
I actually found my solution by stepping through the init code:
#Configuration
public class Config {
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
#Bean
Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper){
return new Jackson2JsonEncoder(mapper);
}
#Bean
Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
return new Jackson2JsonDecoder(mapper);
}
#Bean
WebFluxConfigurer webFluxConfigurer(Jackson2JsonEncoder encoder, Jackson2JsonDecoder decoder){
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(encoder);
configurer.defaultCodecs().jackson2JsonDecoder(decoder);
}
};
}
}
I translated the solution of #Alberto Galiana to Java and injected the configured Objectmapper for convenience, so you avoid having to do multiple configurations:
#Configuration
#RequiredArgsConstructor
public class WebFluxConfig implements WebFluxConfigurer {
private final ObjectMapper objectMapper;
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(
new Jackson2JsonEncoder(objectMapper)
);
configurer.defaultCodecs().jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper)
);
}
}
Just implement WebFluxConfigurer and override method configureHttpMessageCodecs
Sample code for Spring Boot 2 + Kotlin
#Configuration
#EnableWebFlux
class WebConfiguration : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)))
}
}
Make sure all your data classes to be encoded/decoded have all its properties annotated with #JsonProperty even if property name is equal in class and json data
data class MyClass(
#NotNull
#JsonProperty("id")
val id: String,
#NotNull
#JsonProperty("my_name")
val name: String)
In my case, I was trying to use a customized ObjectMapper while inheriting all of the behavior from my app's default WebClient.
I found that I had to use WebClient.Builder.codecs. When I used WebClient.Builder.exchangeStrategies, the provided overrides were ignored. Not sure if this behavior is something specific to using WebClient.mutate, but this is the only solution I found that worked.
WebClient customizedWebClient = webClient.mutate()
.codecs(clientCodecConfigurer ->
clientCodecConfigurer.defaultCodecs()
.jackson2JsonDecoder(new Jackson2JsonDecoder(customObjectMapper)))
.build();
I have tried all the different solutions (#Primary #Bean for ObjectMapper, configureHttpMessageCodecs(), etc.). What worked for me at the end was specifying a MIME type. Here's an example:
#Configuration
class WebConfig: WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
val encoder = Jackson2JsonEncoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
val decoder = Jackson2JsonDecoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
configurer.defaultCodecs().jackson2JsonEncoder(encoder)
configurer.defaultCodecs().jackson2JsonDecoder(decoder)
}
}

Resources