Spring RefreshScope with SpEL deferred Evaluation - spring

I'm having trouble knowing how this is possible or a potential work around. I am looking at configuring variables that are in the form of "foo,bar,baz" into a List as separate elements. Currently the code looks like
#RefreshScope
#Configuration
#Getter
public class fakeConfiguration {
#Value("#{PropertySplitter.toSet('${properties:}')}")
private final Set<String> field = new HashSet<>();
}
#Component("PropertySplitter")
#SuppressWarnings("unused")
public class PropertySplitter {
public Set<String> toSet(String property) {
Set<String> set = new HashSet<>();
if(!property.trim().isEmpty()){
Collections.addAll(set, property.split("\\s*,\\s*"));
}
return set;
}
}
This properly evaluates the String list into a Set however the refreshScope never gets triggered properly. If I use
#Value("${properties:}")
private final String fieldAsString;
I can see the field properly refresh, but I'd like to actively convert the value to a list as it is changed. Is this possible?

In newer version of spring-boot below works for application.properties and Application.yml
#Value("#{${props.list}}")
private List<String> fieldList;
If you use Application.yml you can arrange the objects
props:
list:
- val1
- val2
and then use in code
#Value("${props.list}")
private List<String> ymlList
Last, You can try the below as well
#Value("#{'${props.list}'.split(',')}")
private List<String> myList;

Related

Spring data mongo - unique random generated field

I'm using spring data mongo. I have a collection within a document that when I add an item to it I would like to assign a new automatically generated unique identifier to it e.g. (someGeneratedId)
#Document(collection = "questionnaire")
public class Questionnaire {
#Id
private String id;
#Field("answers")
private List<Answer> answers;
}
public class Answer {
private String someGeneratedId;
private String text;
}
I am aware I could use UUID.randomUUID() (wrapped in some kind of service) and set the value, I was just wondering if there was anything out of the box that can handle this? From here #Id seems to be specific to _id field in mongo:
The #Id annotation tells the mapper which property you want to use for
the MongoDB _id property
TIA
No there is no out of the box solution for generating ids for properties on embedded documents.
If you want to keep this away from your business-logic you could implement a BeforeConvertCallback which generates the id's for your embedded objects.
#Component
class BeforeConvertQuestionnaireCallback implements BeforeConvertCallback<Questionnaire> {
#Override
public Questionnaire onBeforeConvert(#NonNull Questionnaire entity, #NonNull String collection) {
for (var answer : entity.getAnswers()) {
if (answer.getId() == null) {
answer.setId(new ObjectId().toString());
}
}
return entity;
}
}
You could also implement this in a more generic manner:
Create a new annotation: #AutogeneratedId.
Then listen to all BeforeConvertCallback's of all entities and iterate through the properties with reflection. Each property annotated with the new annotation gets a unique id if null.

List of custom object data retrieval in Javers

I have an object with attributes like below:
public class Model1 {
private String aa;
private List<String> bb;
private List<CC> list;
}
public class CC{
private String cc1;
private List<String> cc2;
}
When I use javers, I am able to get changes made to aa and bb. But for List<CC> it just says, list/2 was added or list/0 was removed but it does not give exact data that was added or removed.
How do I get the complete data of CC object which was added, removed or changed from list?

Spring Boot Controller endpoint and ModelAttribute deep access

I would like to know how to access a deep collection class attribute in a GET request. My endpoint maps my query strings through #ModelAttribute annotation:
Given that:
public class MyEntity
{
Set<Item> items;
Integer status;
// getters setters
}
public class Item
{
String name;
// getters setters
}
And my GET request: localhost/entities/?status=0&items[0].name=Garry
Produces bellow behavior?
#RequestMapping(path = "/entities", method = RequestMethod.GET)
public List<MyEntity> findBy(#ModelAttribute MyEntity entity) {
// entity.getItems() is empty and an error is thrown: "Property referenced in indexed property path 'items[0]' is neither an array nor a List nor a Map."
}
Should my "items" be an array, List or Map? If so, thereĀ“s alternatives to keep using as Set?
Looks like there is some problem with the Set<Item>.
If you want to use Set for the items collection you have to initialize it and add some items:
e.g. like this:
public class MyEntity {
private Integer status;
private Set<Item> items;
public MyEntity() {
this.status = 0;
this.items = new HashSet<>();
this.items.add(new Item());
this.items.add(new Item());
}
//getters setters
}
but then you will be able to set only the values of this 2 items:
This will work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa
This will not work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa&items[2].name=aaa
it will say: Invalid property 'items[2]' of bean class MyEntity.
However if you switch to List:
public class MyEntity {
private Integer status;
private List<Item> items;
}
both urls map without the need to initialize anything and for various number of items.
note that I didn't use #ModelAttribute, just set the class as paramter
#GetMapping("map")//GetMapping is just a shortcut for RequestMapping
public MyEntity map(MyEntity myEntity) {
return myEntity;
}
Offtopic
Mapping a complex object in Get request sounds like a code smell to me.
Usually Get methods are used to get/read data and the url parameters are used to specify the values that should be used to filter the data that has to be read.
if you want to insert or update some data use POST, PATCH or PUT and put the complex object that you want to insert/update in the request body as JSON(you can map that in the Spring Controller with #RequestBody).

preprocess configuration values in Spring

In a Spring bean, I need to process a configuration property before using is, e.g.:
#Component
class UsersController {
#Value("${roles}")
private String rolesAsString;
private List<String> roles;
#PostConstruct
public void initRoles() {
// just an example, not necessarily string splitting
roles = rolesAsString.split(",");
}
This works, but I am left with an unneeded member variable 'rolesString'. What would be a clean concise way to only keep the processed value?
Properties is
roles=role1,role2,role3
Code is :
#Value("#{'${roles}'.split(',')}")
private List<String> roles;

Validator error ordering

What I want to achieve is ordered error messages. Give the following bean class
public class DummyBean{
#NotNull
public String firstName;
#NotNull
public String lastName;
}
And using LocalValidatorFactoryBean like this:
private Validator validator;
#Autowired
public DummyController(Validator validator) {
this.validator = validator;
}
public void validate(DummyBean bean){
Map<String, String> failureMessages = new LinkedHashMap<String, String>();
for (ConstraintViolation<DummyBean> failure : validator.validate(bean)) {
failureMessages.put(failure.getPropertyPath().toString(), failure.getMessage());
}
}
How can I make sure that errors messages related to property "firstName" always come first related to "lastName"?
Afaik only group sequences can be ordered but I'm hoping that maybe someone came across this problem already
You are right, if you want to enforce an order you need to use a group sequence. However, in this case validation will stop as soon as a constraint violation occurs. This means in this case you would never get constraint violation for both firstName and lastName.
Also consider that you are getting a Set of violations and that there is no guaranteed iteration order over a set.

Resources