How to get child properties from spring config - java

I have the following configuration:
external:
shop-service:
url: localhost:8080
timeout: 5000
pet-service:
url: localhost:8081
timeout: 10000
user-service:
url: localhost:8082
timeout: 15000
I want to create some config library that will read these properties in every my service. In every service I can have different clients with different values of properties, but all of them have the same structure.
Any ways how I can get a map that will contain client name as a key and object that has url and timeout values, if I know only external property at the beginning and don't know exact client names?

You can create a configuration class which maps your properties into a map.
#Getter
#Configuration
#ConfigurationProperties
public class Properties {
private Map<String, String[]> external = new HashMap<>();
}
In my opinion, you should avoid using String array as value type. Instead of, you should create a POJO to map the url and timeout properties.
#Getter
#Configuration
#ConfigurationProperties
public class Properties {
private Map<String, Data> external = new HashMap<>();
#Getter
#Setter
#ToString
public static class Data {
private long timeout;
private String url;
}
}
Note: the Properties class must have a getter for the map, and the Data properties must have a getter and setter for each attribute.

this is my solution
private static final String BASE_PROPERTY = "clients.";
private static final String DELIMITER = ".";
private final org.springframework.core.env.Environment environment;
public Set<String> getClientNames() {
return Arrays
.stream(((EnumerablePropertySource<?>) ((ConfigurableEnvironment) environment)
.getPropertySources()
.stream()
.filter(OriginTrackedMapPropertySource.class::isInstance)
.findFirst()
.orElseThrow(RuntimeException::new)).getPropertyNames())
.filter(currentValue -> StringUtils.startsWith(currentValue, BASE_PROPERTY))
.map(key -> StringUtils.substringBetween(key, DELIMITER, DELIMITER))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}

Related

Merge property file into application.properties

I have two properties files. Let's say a.properties and b.properties. these file values has been stored in maps created, let say aMap and bMap.
#PropertySource(value={ "classpath:a.properties", "classpath:b.properties"})
Class propFile{
Private Map<String, String> aMap;
Private Map<String, String> bMap;
}
I have to merge these property file into application.properties such that it works same way. Please provide me solution for this.
You will be able to retrieve your properties by annotating your properties class with #Configuration and #ConfigurationProperties:
#Configuration
#ConfigurationProperties(prefix="maps")
public class ConfigProperties {
private Map<String, String> a;
private Map<String, String> b;
// getters and setters
}
The corresponding application.yml would look as follows:
maps:
a:
key:
test1
b:
key:
test2
Or alternatively with an application.properties file:
maps.a.key=test1
maps.b.key=test2

Sharing field constraints between server and client side

I am working on a project, where server side is based on Spring Boot 2, and front-end is based on Angular.
On the server side, in the data model classes I have declarations like that:
#Column(length = 256, nullable = false)
#Size(max = 256)
private String subject;
I would like to implement field length validation also on the front-end (angular side).
What is the best approach to share field length constraints between server and client side?
I do not like the idea that I need to repeat myself and hard-code field length on the both sides (server and client side).
Is it the best approach in my case if I declare set of constants like this:
private static final int maxSubjectLength = 256;
And use them as follows:
#Column(length = maxSubjectLength, nullable = false)
#Size(max = maxSubjectLength)
private String subject;
Then make a configuration class with these constants, which instance is accessible via GET http-request?
Or there is a better approach?
I decided to use the following approach. Assume we have a model class Question, that has a property body.
#Entity
#Table(name = "Questions")
public final class Question {
// other properties & code
private String body;
// other properties & code
}
And we want to limit the body length to 1024 symbols and define this limit only once, on the server and use this limit on the back-end and on the front-end of the application.
On the server side, in our model class Question we define static map, that contains size limits for all class properties.
#Entity
#Table(name = "Questions")
public final class Question {
private static class ModelConstraints {
static final int MAX_BODY_LENGTH = 1024;
// limits for other fields are going here
}
private static final Map<String, Integer> modelConstraintsMap;
static
{
final Map<String, Integer> localConstraintsMap = new HashMap<>();
localConstraintsMap.put("MAX_BODY_LENGTH", ModelConstraints.MAX_BODY_LENGTH);
// .... putting all constants from ModelConstraints to the map here
// composing unmodifable map
modelConstraintsMap = Collections.unmodifiableMap(localConstraintsMap);
}
#Column(length = ModelConstraints.MAX_BODY_LENGTH, nullable = false)
#Size(max = ModelConstraints.MAX_BODY_LENGTH)
private String body;
// other properties and code
public static Map<String, Integer> getModelConstraintsMap() {
return modelConstraintsMap;
}
// other properties and code
}
Internal class ModelConstraints contains definitions of max length values for all relevant model properties.
In the static block, I create an unmodifiable map, that contains these constraints and I return this map via public method.
In the controller, related to the model class I add a rest-endpoint that returns property length constraints.
#RequestMapping(path = "/questions/model-constraints", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<Map<String, Integer>> getModelConstraints() {
return new ResponseEntity<>(Question.getModelConstraintsMap(), HttpStatus.OK);
}
This method returns json-representation of the map with the property length constraints.
On the (angular) fron-tend I call this end-point and set maxlength property of form fields, related to the model class properties.
In the component typescript file I add and call this method:
loadConstraints() {
var url: string = "/questions/model-constraints";
this.http
.get(url)
.subscribe((data: Map<string, number>) => (this.modelConstraints = data));
}
And after call of this method the component property modelConstraints will contain map with field length constraints.
I set these constraints in the component template (html) file.
<textarea
matInput
rows="7"
placeholder="Question body"
maxlength="{{ modelConstraints['MAX_BODY_LENGTH'] }}"
[(ngModel)]="questionBody"
></textarea>
That's it. Using this approach you can define field length only once, on the server and use this definition on the server and on the client.

Relaxed binding doesn't work for composed #ConfigurationProperties names with a custom system environment property source

I have an external property source (let's say a .properties file) with environment variables like:
MY_PROP1=1A
MY_PROPS_PROP1=1B
MY_PROPOBJ_PROP1=1C
And I want to init a #ConfigurationProperties with those:
#ConfigurationProperties("my")
#Setter
#Getter
class MyProperties {
private String prop1;
private Props props = new Props();
private PropsObj propsObj = new PropsObj();
#Setter
#Getter
class Props {
private String prop1;
}
#Setter
#Getter
class PropsObj {
private String prop1;
}
}
Everything is working fine when I set those as environment variables at the start of the application. But when I process those with a custom SystemEnvironmentPropertySource in a EnvironmentPostProcessor, the property prop1 of the compose object propsObj is not resolved (the value is null):
class MyEnvPostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
env.getPropertySources().addLast(
new SystemEnvironmentPropertySource(
"myPropertySource1",
// simplified, the map will be filled from a properties file etc:
Map.of("MY_PROP1", "1A", "MY_PROPS_PROP1", "1B", "MY_PROPOBJ_PROP1", "1C")));
}
}
Why relaxed binding doesn't work in this case?
I'm using Spring Boot 2.2.2.RELEASE
The problem is in the mapping Spring uses to resolve the property names. For environment variables the SystemEnvironmentPropertyMapper must be used to work as expected.
This mapper maps the property name into possible candidates: MY_PROPOBJ_PROP1, MY_PROP_OBJ_PROP1, my.prop-obj.prop1, while the default mapper results into only my.prop-obj.prop1. As my.prop-obj.prop1 is not to be found in your custom property source, the value is not resolved.
To hint Spring to use the right mapper for environment properties, your property source name must end with -systemEnvironment, resp. with StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME. Edit your code as follows:
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new SystemEnvironmentPropertySource(
"myPropertySource-" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
Map.of("MY_PROPOBJ_PROP1", "MyValue1")));

Redis still returns null entries even when they are expired

I am using Spring Repositories with Redis and want to store data about user for 10 seconds and expire them (and delete them) from redis.
I know expiring and deleting is different, but is there an easy way to delete them like I am expiring them automatically.
I have the following entity
#RedisHash(value = "User", timeToLive = 10)
public class User {
#Id
private String id;
#Indexed
#ApiModelProperty(notes = "First name of the user")
private String firstName;
#ApiModelProperty(notes = "Last name of the user")
private String lastName;
...
...
}
Repository
#Repository
public interface UserRepository extends CrudRepository<User, String> {
}
Configuration for Redis
#Configuration
public class RedisConfig {
#Value("${redis.hostname}")
private String redisHostname;
#Value("${redis.port}")
private int redisPort;
#Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostname, redisPort);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
When I get all the entities with findAll method from the repository if they are expired I get a bunch of null values, and I can see that they are in the redis with a redis client. I am worried that this will fill the db with a lot of expired data. Is there a way to delete the expired entities.
The short answer is:
Put the following annotation:
#EnableRedisRepositories(enableKeyspaceEvents = EnableKeyspaceEvents.ON_STARTUP)
Above your main class (SpringBootApplication)
Objects will now get removed from the redis store :)
When the expiration is set to a positive value, the corresponding EXPIRE command is executed. In addition to persisting the original, a phantom copy is persisted in Redis and set to expire five minutes after the original one.
for more information, please reference here
Hope this post helps you.

Getting subset of application.properties by prefix

I have (partly) this application.properties in my Spring Boot app:
spring.main.banner-mode = off
app.set.a = 100
app.set.b = abc
app.set.c
# ...
I want to get injected a Properties object with all keys/values with prefix "app.". Something directly like this:
#Value(value="${app.*}")
private Properties appProperties; // this obviously won´t work
I have not tried with Properties. But this is how you do with a Map.
application.yml:
test:
my-map:
key1: value1
key2: value2
Java:
#Service
#ConfigurationProperties(prefix="test")
public class MyService {
private Map<String, String> myMap = new HashMap<>(); // add getter
}

Categories

Resources