Spring Boot Elasticsearch Configuration - java

I've got a working Spring Boot Elasticsearch Application which uses one of two profiles: application.dev.properties or application.prod.properties. That part works fine. I am having issue with getting the external elasticsearch to read from the application.xxx.properties.
This works:
#Configuration
#PropertySource(value = "classpath:config/elasticsearch.properties")
public class ElasticsearchConfiguration {
#Resource
private Environment environment;
#Bean
public Client client() {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(
environment.getProperty("elasticsearch.host"),
Integer.parseInt(environment.getProperty("elasticsearch.port"))
);
client.addTransportAddress(address);
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(client());
}
}
but obviously doesn't solve my multi-environment issue.
I've also tried #Value annotations for host and port variables without success.
How can I convert the above to read its values from the application properties file or choose a different #PropertySource file based on whichever profile I want to run?
spring.data.elasticsearch.properties.host = 10.10.1.10
spring.data.elasticsearch.properties.port = 9300
Thanks

Remove your configuration class and properties.
Add the following dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
Just add the spring.data.elasticsearch properties to an application-prod.properties and application-dev.properties and change for the desired environment. This is described in the ElasticSearch section of the Spring Boot guide.
spring.data.elasticsearch.cluster-nodes=10.10.1.10:9300
The value in either file will of course differ (or put the default in the application.properties and simply override with an application-dev.properties.
Spring Boot will based on the spring.profiles.active load the desired properties file.
There is no need to hack around yourself.

I agree with Deinum, if you are using Spring boot it will get the properties from the active profile active.
I have different profiles in my project and this is my elasticsearch configuration:
#Configuration
public class ElasticSearchConfiguration {
#Value("${spring.data.elasticsearch.cluster-name}")
private String clusterName;
#Value("${spring.data.elasticsearch.cluster-nodes}")
private String clusterNodes;
#Bean
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
String server = clusterNodes.split(":")[0];
Integer port = Integer.parseInt(clusterNodes.split(":")[1]);
Settings settings = Settings.settingsBuilder()
.put("cluster.name", clusterName).build();
client = TransportClient.builder().settings(settings).build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(server), port));
return new ElasticsearchTemplate(client);
}

Related

Is it possible to have a default application.yml in a custom Spring boot starter?

I am facing an issue with my custom spring boot starter and a spring boot app consumer that uses as a dependency. I have in both an application.yml but it seems that the configuration I am looking for it is only pressent if it is defined in the consumer.
My config in the starter is like this:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
private boolean jwtEnabled;
private String[] unsecuredPaths;
private String[] securedPaths;
}
And I have this bean defined in the AutoConfiguration class:
#Bean
public StarterSecurityConfig starterSecurityConfig() {
return new StarterSecurityConfig();
}
It is perfectly retrieved by the consumer which has this application.yml and another variables:
security:
jwt-enabled: true
secured-paths:
- /user/**
unsecured-paths:
- /**
But if I remove that from the consumer and I put it in the application.yml of the starter, the starter beans does not have these properties when creating them.
Maybe am I missing something?
If I understood properly your issue, I have faced such problem just last week ...
I was inspecting this issue and I have some findings (they are not supported by official documentation): if you add dependency and want to use its resources, you have a situation when both application.yml files have the same location - classpath:application.yml, and or they cannot be loaded together, or one of them is overridden by other. In any case, in my application, it did not work.
The straight and simple solution if you just need to load configuration from dependent config file - rename it and load in a possible way (manual loading from YAML, property source's initializer, etc.)
But if this config file should be used anywhere, we can load properties manually in the context. In a dependency (consumer in your case) create another configuration file, e.g. consumer-application.yml and next bean in #configuration class:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
var propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
var yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ClassPathResource("consumer-application.yaml"));
propertySourcesPlaceholderConfigurer.setProperties(yamlPropertiesFactoryBean.getObject());
return propertySourcesPlaceholderConfigurer;
}
And you can use properties from YAML-file in both applications with #Value.
But the simplest way - to use properties configs. In that case, you can just set #PropertySource("classpath:consumer-application.properties") in consumer and #PropertySource(value = {"classpath:application.properties", "classpath:consumer-application.properties"})
In my case both variants work correctly.
You can try initializing the member variables on the starter itself. If consumer wants to override the values they can do it with they're application configuration.
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
private boolean jwtEnabled = true;
private String[] unsecuredPaths = { "/user/**" };
private String[] securedPaths = { "/**" };
}
Fews more ideas:
I would make jwtEnabled as false and would remove the #Configuration and #ConfigurationProperties from the above Class and create an SecurityAutoConfiguration Class with other beans.
#Configuration
public class SecurityAutoConfiguration{
#Bean
#ConfigurationProperties(prefix = "security")
public StarterSecurityConfig starterSecurityConfig(){
return new StarterSecurityConfig();
}
#Bean
#ConditionalOnProperty(value="security.jwtEnabled", havingValue = "true")
public JwtService jwtService(StarterSecurityConfig starterSecurityConfig) {
return new JwtService(starterSecurityConfig);
}
}
the consumers will be able to enable or disable the security-starter with their application configuration using security.jwtEnabled flag.

SpringBoot Redis remote host

I'm making an application that uses Spring Boot, MySQL and Redis on the Back End and Angular on the Front End. I want to deploy it to Heroku so I could use my front end with it but I just can't seem to configure the remote URL for Redis. I have the Redis To Go Add-on on Heroku for this with the remote URL ready. I just don't know how to configure the environment variables to access that instead of localhost and the default port 6379.
I added the following lines to my application.properties but it still did not work :
spring.redis.url= #URL
spring.redis.host= #HOSTNAME
spring.redis.password= #PASSWORD
spring.redis.port = #PORT
I keep getting the following error :
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis on localhost:6379; nested exception is com.lambdaworks.redis.RedisConnectionException: Unable to connect to localhost/127.0.0.1:6379
Is there any way I could configure this to access the remote url instead of localhost?
I'm using Lettuce and not Jedis and my HttpSessionConfig file is :
#EnableRedisHttpSession
public class HttpSessionConfig {
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
I was having a similar issue, this is how I solved it:
If you define the bean this way:
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
You are not allowing Spring to take the Redis values from the application.properties.
To make it work, please do the following:
Remove this bean definition:
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
Define the RedisTemplate bean this way:
#Bean
public RedisTemplate<String, Object> deliveryRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}
Define the following values in application.properties:
spring.redis.database=<replace-me>
spring.redis.host=<replace-me>
spring.redis.port=<replace-me>
spring.redis.timeout=<replace-me>
I was frustrated with the exact same issue you are facing and the spring boot documentation or examples do nothing to address what we are facing. The reason why your config entries are not being used is because you are creating a new instance of LettuceConnectionFactory with a parameter-less constructor. Digging into the source/byte code you can see that constructor is completely ignoring the spring.redis.host and spring.redis.port values and hardcoding them to localhost:6379.
What you should be doing is either:
Use the LettuceConnectionFactory(hostname, port) constructor.
Don't define the connection factory. Get rid of the entire #Bean entry for connectionFactory() and Spring will automatically wire everything up and use your config entries.
Also as a side note; to get this working with AWS Elasticache (locally via VPN) I had to add to that config class:
#Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
I found the Heroku documentation for connecting to their Redis add-on (https://devcenter.heroku.com/articles/heroku-redis#connecting-in-java) contains an example using Jedis and therefore needed a little adaption. The content of the REDIS_URL added by Heroku to the environment of your running app resembles
redis://h:credentials#host:port
I parsed this using RedisURI.create and then set the host, port and password parameters of RedisStandaloneConfiguration
val uri = RedisURI.create(configuration.redisUrl)
val config = RedisStandaloneConfiguration(uri.host, uri.port)
config.setPassword(uri.password)
return LettuceConnectionFactory(config)
The code above is Kotlin rather than Java but perhaps it will help? You can find the full code at https://github.com/DangerousDarlow/springboot-redis
You need to choose between depending on autoconfiguration or defining your custom connection template.
First way is to remove HttpSessionConfig and then your redis properties from application.properties file will be applied. And as you have spring-redis-data-session dependency on your classpath your lettuce connection will be created implicitly.
Second solution is defining your connection properties as host, port, password inside LettuceConnectionFactory.
However it is recommended to use autoconfiguration.
Set the configuration in application.properties and RedisStandaloneConfiguration.
#Configuration
#PropertySource("application.properties")
#EnableRedisHttpSession
public class SpringRedisConfig {
#Autowired
private Environment env;
#Bean
public LettuceConnectionFactory connectionFactory() {
RedisStandaloneConfiguration redisConf = new RedisStandaloneConfiguration();
redisConf.setHostName(env.getProperty("spring.redis.host"));
redisConf.setPort(Integer.parseInt(env.getProperty("spring.redis.port")));
redisConf.setPassword(RedisPassword.of(env.getProperty("spring.redis.password")));
return new LettuceConnectionFactory(redisConf);
}
}

Spring Config server add property at runtime

I want to add some property at runtime in spring config server and it should be available to all client applications with #Value annotation.
I wont have this property predefine because I am going calculate that value in spring config server and add to environment.
Can you please help me understand what is best way to achieve this.
Spring cloud configuration contains a feature named 'RefreshScope' which allows to refresh properties and beans of a running application.
If you read about spring cloud config, it looks like it can only load properties from a git repository, but that is not true.
You can use RefreshScope to reload properties from a local file without any need to connect to an external git repository or HTTP requests.
Create a file bootstrap.properties with this content:
# false: spring cloud config will not try to connect to a git repository
spring.cloud.config.enabled=false
# let the location point to the file with the reloadable properties
reloadable-properties.location=file:/config/defaults/reloadable.properties
Create a file reloadable.properties at the location you defined above.
You can leave it empty, or add some properties. In this file you can later, at runtime, change or add properties.
Add a dependency to
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
All beans, that are using properties, that may be changed during runtime, should be annotated with #RefreshScope like this:
#Bean
#RefreshScope
Controller controller() {
return new Controller();
}
Create a class
public class ReloadablePropertySourceLocator implements PropertySourceLocator
{
private final String location;
public ReloadablePropertySourceLocator(
#Value("${reloadable-properties.location}") String location) {
this.location = location;
}
/**
* must create a new instance of the property source on every call
*/
#Override
public PropertySource<?> locate(Environment environment) {
try {
return new ResourcePropertySource(location);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Configure Spring to bootstrap the configuration using that class.
Create (or extend) the META-INF/spring.factories file in your resource folder:
org.springframework.cloud.bootstrap.BootstrapConfiguration=your.package.ReloadablePropertySourceLocator
This bean will read the properties from the reloadable.properties. Spring Cloud Config will reload it from disk, when you refresh the application.
Add runtime, edit reloadable.properties as you like, then refresh the spring context.
You can do that by sending a POST request to the /refresh endpoint, or in Java by using ContextRefresher:
#Autowired
ContextRefresher contextRefresher;
...
contextRefresher.refresh();
This should also work, if you choose to use it in parallel to properties from a remote git repository.

Turn off embedded Elasticsearch in Spring Boot test

By default, Spring Boot will create an embedded Elasticsearch. It can be turned off by setting spring.data.elasticsearch.cluster-nodes. However, I'm not sure how to do this in a JUnit test. For example, I have:
#Slf4j
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(RemoteElasticsearch.class)
#SpringBootApplication(
scanBasePackageClasses = {
}
)
#EnableElasticsearchRepositories(basePackages = "com.example.me.repo")
public class RemoteElasticsearch {
#Inject
private SomeRepo someRepo;
#Test
public void test(){
someRepo.save(new Something());
}
}
It connects to the remote elasticsearch if I set the appropriate environment variable (eg spring.data.elasticsearch.cluster-node=host:9300). Can I somehow set this value directly on this test?
Just create second application.properties file in src/test/resources with spring.data.elasticsearch.cluster-nodes disabled. Spring Boot will use this file instead PROD configuration from src/main/resources.

Problems connecting to existing ElasticSearch instance in a spring boot application

I have an elasticsearch instance running locally.
I have a spring boot application.
In my application I have a service ServiceX which contains an elasticsearch repository which extends ElasticsearchRepository.
So
Service X contains
YRepository extends ElasticsearchRepository
I have an elasticsearch instance running locally.
My elastic search settings are
ELASTICSEARCH (ElasticsearchProperties)
spring.data.elasticsearch.properties.http.enabled=true
spring.data.elasticsearch.properties.host = localhost
spring.data.elasticsearch.properties.port = 9300
When the application is started an elasticsearch template is created.
The client that is used is a NodeClient.
The settings for the NodeClient are
"http.enabled" -> "true"
"port" -> "9300"
"host" -> "localhost"
"cluster.name" -> "elasticsearch"
"node.local" -> "true"
"name" -> "Human Robot"
"path.logs" -> "C:/dev/git/xxx/logs"
The name of the elasticsearch (Human Robot in this case), does not match the local elasticsearch instance running (Nikki in this case).
It looks like it
1. creates a new instance of logstash
2. creates an embedded instance of logstash.
I have searched through a lot of information but cannot find any documentation to help.
Could people please advise about what settings to use?
Thanks.
I believe that you do not want to use the NodeClient but the TransportClient unless you want your application to become part of the cluster
I believe you have the following dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artificatId>spring-boot-starter-data-elasticsearch</artificatId>
</dependency>
then you need to create some configuration class as follows:
#Configuration
#PropertySource(value = "classpath:config/elasticsearch.properties")
public class ElasticsearchConfiguration {
#Resource
private Environment environment;
#Bean
public Client client() {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(
environment.getProperty("elasticsearch.host"),
Integer.parseInt(environment.getProperty("elasticsearch.port"))
);
client.addTransportAddress(address);
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(client());
}
}
Also check ElasticSearch section of the Spring Boot guide, and especially the section about spring.data.elasticsearch.cluster-nodes if you put multiple comma seperated list of host port it will be generated a TransportClient instead, your choice
Try it, hope it helps
Thanks. Would you believe I actually just started trying to use a configuration file before I saw your post. I added a configuration class
#Configuration
public class ElasticSearchConfig {
#Bean
public Client client() {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(
"localhost",9300);
client.addTransportAddress(address);
return client;
}
}
And the client is now being injected into the elasticsearch template (so don't need the elasticsearchtemplate bean).
I had an error when I tried to connect but that turned out to be due to elasticsearch 2.2.0, have tried it with elasticsearch 1.7.3 and it worked so now onto the next problem!

Categories