How to bind the same prefix with multiple properties class - spring

Assume that there is a yaml configuration:
foo:
a:
b:
c:
d:
Generally, you will create an object like this:
#Data
#Validated
#Component
#ConfigurationProperties(prefix = "foo")
public class FooProperties {
#NotBlank
private String a;
// b c and d
}
But for the consideration of the project structure, I need to divide them into multiple objects in different packages, And they will use the same prefix.
package one:
#Data
#Validated
#Component
#ConfigurationProperties(prefix = "foo")
public class AProperties {
// only a property
#NotBlank
private String a;
}
package two:
#Data
#Validated
#Component
#ConfigurationProperties(prefix = "foo")
public class BProperties {
// only b property
#NotBlank
private String b;
}
...
And now, when my project fetch config from config server, Spring will say:
***************************
APPLICATION FAILED TO START
***************************
Binding to target [Bindable#2b32cd10 type = AProperties, value = 'provided', annotations = array<Annotation>[#org.springframework.boot.context.properties.ConfigurationProperties(ignoreInvalidFields=false, ignoreUnknownFields=false, prefix=foo, value=foo), #org.springframework.validation.annotation.Validated(value={})]] failed:
Property: foo.b
Value: someValue
Origin: "foo.b" from property source "bootstrapProperties-xxx"
Reason: The elements [foo.b] were left unbound.
So, How can I bind the same prefix with multiple properties class? Thx~

Related

Cannot bind Map to Object in Spring Configuration Properties from YAML file

I have the following configuration in my Spring boot's application.yml file:
project:
country-properties:
france:
capital: paris
population: 60
And I have the the properties class : CountryProperties :
#Getter
#AllArgsConstructor
#ConstructorBinding
#ConfigurationProperties(prefix="project.country-properties")
public class CountryProperties {
private Map<String, CountryData> countryProperties;
#Getter
#Setter
public static class CountryData {
private String capital;
private Integer population;
}
However my CountryProperties is always null, and it's because of a failed mapping with the CountryData object.
Any ideas what is wrong with what I wrote?
You have the annotation #ConstructorBinding. This annotation tells Spring to look for a constructor in your class that has parameters corresponding to your configuration properties, and then will bind the properties.
What you are missing is:
public CountryProperties(Map<String, CountryData> countryProperties) {
this.countryProperties = countryProperties;
}
Update:
After inspecting your code again, it looks like you aren't mapping the configuration correctly to the instance field. Please update your #ConfigurationProperties(prefix="project.country-properties") to #ConfigurationProperties(prefix="project").
Also replace the #ConstructorBinding with #Configuration.

jHipster use enum in specification to find only entites that have one concrete value of Enum

There is entity:
#Getter
#Setter
#ToString()
#Entity
#Table
#Builder
#NoArgsConstructor
#AllArgsConstructor
class DocumentEntity implements Serializable {
(...)
#Enumerated(EnumType.STRING)
public DocumentStatus documentStatus;
}
I have serializable class:
#Data
#NoArgsConstructor
public class DocumentCriteria implements Serializable, Criteria {
private StringFilter documentStatus;
(...)
}
and auto generated class:
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(DocumentEntity.class)
public abstract class DocumentEntity_ {
public static volatile SingularAttribute<DocumentEntity, DocumentStatus> documentStatus;
public static final String DOCUMENT_STATUS = "documentStatus";
(...)
}
DocumentStatus is simple enum:
public enum DocumentStatus {
A,
B
}
I want to add specification that I search only entites with DocumentStatus set to A:
private Specification<DocumentEntity> createSpecification(DocumentCriteria criteria) {
Specification<DocumentEntity> specification = Specification.where(null);
if (criteria != null) {
StringFilter globalStringFilter = new StringFilter();
globalStringFilter.setContains(DocumentStatus.A.name());
specification.and(buildStringSpecification(globalStringFilter, DocumentEntity_.documentStatus));
I have an error here saying:
Required type:
SingularAttribute
<? super DocumentEntity,
String>
Provided:
SingularAttribute
<DocumentEntity,
DocumentStatus>
How can I search for it?
I tried also:
specification = specification.and(buildSpecification(criteria.getDocumentStatus(), DocumentStatus.A.name());
but it says:
Cannot resolve method 'buildSpecification(StringFilter, String)
Should i use other type than StringFilter even though database type is varchar ?
Does trying using String like: DocumentStatus.A.name() does not help here ?
Another option that comes in my head is writing something like that:
RangeFilter<DocumentStatus> globalStringFilter = new RangeFilter<DocumentStatus>();
List<DocumentStatus> documentStatuses = new ArrayList<>();
documentStatuses.add(DocumentStatus.A);
globalStringFilter.setIn(documentStatuses);
specification.and(buildRangeSpecification(globalStringFilter, DocumentEntity_.documentStatus));
And changing DocumentCriteria documentStatus field type to RangeFilter.
Above option does not seem to take effect when running application.
Solution:
RangeFilter<DocumentStatus> globalStringFilter = new RangeFilter<DocumentStatus>();
List<DocumentStatus> documentStatuses = new ArrayList<>();
documentStatuses.add(DocumentStatus.A);
globalStringFilter.setIn(documentStatuses);
specification=specification.and(buildRangeSpecification(globalStringFilter, DocumentEntity_.documentStatus));
And changing DocumentCriteria documentStatus field type to RangeFilter.
Make sure that specification= is present, so that result is consumed.

Builder class does not have build method (name: 'build') - Jackson

This error occurs when objectMapper.convertValue(cityEntity, City.class)) is called.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Builder class com.example.PostgresApp.dto.City$Builder does not have build method (name: 'build')
package com.example.PostgresApp.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.*;
import org.apache.commons.lang3.StringUtils;
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = City.Builder.class)
public class City {
String name;
String description;
#JsonPOJOBuilder(withPrefix = StringUtils.EMPTY)
public static class Builder {
}
}
Service calling repo seems to be where the exception is thrown
public List<City> getCities(){
return cityRepo.findAll().stream().map(cityEntity -> objectMapper
.convertValue(cityEntity, City.class))
.collect(Collectors.toList());
}
The problem is that Jackson cannot deserialize the object value.
My solution was to add the following annotations to my class:
// constructor with no args
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
// constructor with all args
#AllArgsConstructor
// ignore unknown properties during deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
My class ended up looking like this:
#Getter
#Builder
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyClass {
private boolean flag;
private boolean flag2;
private MyClassA objectA;
private MyClassB objectB;
}
If you want to read more on why should we use #NoArgsConstructor and #AllArgsConstructor together, here is a good answer.
Are You sure You always pass name and description to the class Builder?
I got the same error and In my case I was trying to to use a generated Builder to create an Object but I did not pass all of the arguments, so the generated method was not the one spring was looking for. It was searching the N+1 arguments method, but I was passing only N arguments. In this case it will look for a different method signature that can not find.

#DefaultValue not working with #ConstructorBinding for Spring boot 2.4.2

Here is an example of my problem. when no value is supplied to default-name in yml file. #DefaultValue should step in and fill with "Name". However, is not how it behaves. An empty string is assigned to defaultName
application.yml:
account:
default-name:
class:
#ConstructorBinding
#ConfigurationProperties(prefix = "account")
public class Account {
private final String defaultName;
public Account(#DefaultValue("Name") String defaultName) {
this.defaultName = defaultName;
}
..
..
}

Jackson java.util.Optional serialization does not include type ID

I got the following classes:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
...
}
public class MySubEntity extends Entity {
...
}
Now when I serialize that MySubEntity wrapped in an Optional then JSON does not contain the clazz attribute containing the type ID. Bug? When I serialize to List<MySubEntity> or just to MySubEntity it works fine.
Setup: jackson-databind 2.9.4, jackson-datatype-jdk8 2.9.4, serialization is done in Spring Boot application providing a RESTful web service.
EDIT: Here is the Spring REST method that returns the Optional:
#RequestMapping(method = RequestMethod.GET, value = "/{uuid}", produces = "application/json")
public Optional<MySubEntity> findByUuid(#PathVariable("uuid") String uuid) {
...
}
EDIT:
I made a SSCCE with a simple Spring REST controller and two tests. The first test is using ObjectMapper directly which is successful in deserialization although the clazz is missing. The second test calls the REST controller and fails with an error because clazz is missing:
Error while extracting response for type [class com.example.demo.MySubEntity] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'
This, indeed, looks like a bug. There is one workaround that I can suggest for this case, is to use JsonTypeInfo.As.EXISTING_PROPERTY and add field clazz to your Entity. There only one case with this approach is that the clazz must be set in java code manually. However this is easy to overcome.
Here is the full code for suggested workaround:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, //field must be present in the POJO
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
#JsonProperty
private String uuid;
//Here we have to initialize this field manually.
//Here is the simple workaround to initialize in automatically
#JsonProperty
private String clazz = this.getClass().getSimpleName();
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
}

Resources