Binding map from config yaml - java

In spring boot project i am trying to bind a map from yaml file. I have tried most of the solutions to bind map but getting following error:
Property: order.events
Value: null
Reason: must not be empty
Action:
Update your application's configuration
As you can see on config class i have getter/setter through lombok and inner class is a static class.
#Data
#Validated
#ConfigurationProperties(prefix = "order", ignoreUnknownFields = false)
public class OrderConfig {
private String clientKey;
private String apiVersion;
private String endpoint;
#Valid
#NotEmpty
private Map<String, Event> events;
#Data
public static class Event {
#NotBlank
private String action;
#NotBlank
private String eventName;
}
}
By using lombok i already have all necessary getter and setter methods. My config yaml file is:
order:
clientKey: client_1
apiVersion: 1.0.0
endpoint: https://www.ordertest.com/api
events:
order.create:
action: track
eventName: purchase
order.place:
action: track
eventName: purchase
And my Application class:
#SpringBootApplication
#EnableConfigurationProperties(OrderConfig.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

You need to encode them in double quotes with square brackets Relaxed Binding
When binding to Map properties, if the key contains anything other than lowercase alpha-numeric characters or -, you need to use the bracket notation so that the original value is preserved. If the key is not surrounded by [], any characters that are not alpha-numeric or - are removed.
order:
clientKey: client_1
apiVersion: 1.0.0
endpoint: https://www.ordertest.com/api
events:
"[order.create]":
action: track
eventName: purchase
"[order.place]":
action: track
eventName: purchase
And also you need to annotate #Configuration on OrderConfig class
Sometimes, classes annotated with #ConfigurationProperties might not be suitable for scanning, for example, if you’re developing your own auto-configuration. In these cases, you can specify the list of types to process on any #Configuration class

If you really want your keys to be order.create and order.place then you have to specify them as "[order.create]" and "[order.place]" in order to escape the periods. Otherwise you should be able to work with just create and place.

I couldn't figured why i had that problem and wanted to give a shot with the latest Spring boot version 2.2.0 which they introduced ConstructorBinding.
I have changed my config class to following version and its fixed.
#Data
#Validated
#ConstructorBinding
#ConfigurationProperties(prefix = "order", ignoreUnknownFields = false)
public class OrderConfig {
private final String clientKey;
private final String apiVersion;
private final String endpoint;
#Valid
#NotEmpty
private final Map<String, Event> events;
#Data
#ConstructorBinding
public static class Event {
#NotBlank
private final String action;
#NotBlank
private final String eventName;
}
}

Related

Include/exclude Attributes in json response from application.yml

I am using JHipster(spring boot) to generate my project. I would like to hide/show fields in JSON from application.yml. for exemple:
I have the following class
#Entity
#Table(name = "port")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Port implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "city")
private String city;
#Column(name = "description")
private String description;
//getters & setters
}
My GET method return a response like:
{
"id": 1,
"city": "boston",
"description": "test test"
}
I would like to be able to include/exclude some fields from application.yml (since i don't have application.properties) otherwise to have something like:
//application.yml
include: ['city']
exclude: ['description']
in this exemple my json should look like:
{
"id": 1,
"city": "boston",
}
for exemple if I have 40 fields and I need to hide 10 and show 30 I just want to put the 10 I want to hide in exclude in application.yml without go everytime to change the code. I guess #jsonignore hide fields but I don't know how to do it from application.yml
Sorry for not explaining well. I hope it's clear.
Thank you in advance for any suggestion or solution to do something similar
Spring boot by default uses Jackson JSON library to serialize your classes to Json. In that library there is an annotation #JsonIgnore which is used precisely for the purpose to tell Json engine to egnore a particular property from serialization/de-serialization. So, lets say in your entity Port you would want to exclude property city from showing. All you have to do is to annotate that property (or its getter method) with #JsonIgnore annotation:
#Column(name = "city")
#JsonIgnore
private String city;
You can try to create a hashmap in your controller to manage your HTTP response.
Map<String, Object> map = new HashMap<>();
map.put("id", Port.getId());
map.put("city", Port.getCity());
return map;
Basically you don't expose your Port entity in your REST controller, you expose a DTO (Data Transfer Object) that you value from your entity in service layer using a simple class (e.g PortMapper). PortDTO could also be a Map as suggested in other answer.
Your service layer can then use a configuration object (e.g. PortMapperConfiguration) that is valued from application.yml and used by PortMapper to conditionally call PortDTO setters from Port getters.
#ConfigurationProperties(prefix = "mapper", ignoreUnknownFields = false)
public class PortMapperConfiguration {
private List<String> include;
private List<String> exclude;
// getters/setters
}
#Service
public class PortMapper {
private PortMapperConfiguration configuration;
public PortMapper(PortMapperConfiguration configuration) {
this.configuration = configuration;
}
public PortDTO toDto(Port port) {
PortDTO dto = new PortDTO();
// Fill DTO based on configuration
return dto;
}
}

Spring Data MongoDB how to set index ttl from system property

Preferably, using the #Indexed annotation, or some other declarative way, is it possible to inject a system property, preferably using SPeL.
I have tried the following but found expireAfterSeconds gives error because it wants an int:
#Data
#Document(collection = "#{#xyzUpdates.collectionName}")
public class UpdatesFromXyz {
#Id
#Field("resourceId")
private UUID resourceId;
#Indexed(expireAfterSeconds = "#{#xyzUpdates.maxRecords}")
private LocalDate updateDate;
}
and my properties class:
#ConfigurationProperties("xyz.updates")
#Getter
#Setter
#Component
public class XyzUpdates {
private String collectionName = "updatesFromXyz";
private int maxRecords;
}
Since SpringData MongoDB 2.2 you can use expireAfter which accepts numeric values followed by their unit of measure or a Spring template expression.

Deserialize JSON to polymorphic types Spring boot

I am working on an e-policy project where i need to save different types of policies. For simplicity i am considering only two types "LifeInsurance" and "AutoInsurance". What i want to achieve is if the JSON request to create policy contains "type":"AUTO_INSURANCE" then the request should be mapped to AutoInsurance.class likewise for LifeInsurance but currently in spring boot app the request is getting mapped to parent class Policy eliminating the specific request fields for auto/Life insurance. The domain model i have created is as below.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#Getter
#Setter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({ #Type(value = AutoInsurance.class, name = "AUTO_INSURANCE"),
#Type(value = LifeInsurance.class) })
public class Policy {
#Id
#GeneratedValue
private Long id;
private String policyNumber;
#Enumerated(EnumType.STRING)
private PolicyType policyType;
private String name;
}
My AutoInsurance class is below.
#Entity
#NoArgsConstructor
#Getter
#Setter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonTypeName(value = "AUTO_INSURANCE")
public class AutoInsurance extends Policy {
#Id
#GeneratedValue
private Long id;
private String vehicleNumber;
private String model;
private String vehicleType;
private String vehicleName;
}
Below is LifeInsurance type child class
#Entity
#NoArgsConstructor
#Getter
#Setter
#JsonTypeName(value = "LIFE_INSURANCE")
public class LifeInsurance extends Policy {
#OneToMany(mappedBy = "policy")
private List<Dependents> dependents;
private String medicalIssues;
private String medication;
private String treatments;
}
To save the policy details, I am sending JSON request from UI with a "type" property indicating the type of insurance in the request.
When i run the below test method, JSON request gets mapped to the correct child class as required.
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper map = new ObjectMapper();
String s = "{\"id\": 1,\"policyNumber\": \"Aut-123\",\"type\": \"AUTO_INSURANCE\",\"policyType\": \"AUTO_INSURANCE\",\"name\": null,\"address\": null,\"contact\": null,\"agentNumber\": null,\"agentName\": null,\"issuedOn\": null,\"startDate\": null,\"endDate\": null,\"vehicleNumber\": \"HR\",\"model\": null,\"vehicleType\": \"SUV\",\"vehicleName\": null}";
Policy p = map.readValue(s, Policy.class);
System.out.println(p.getClass());
//SpringApplication.run(EPolicyApplication.class, args);
}
But when i run the same in Spring boot in a RESTController postmapping, I am getting a PArent class object instead of the child class object.
#RestController
#RequestMapping("/policy")
public class PolicyController {
#PostMapping
public void savePolicy(Policy policy) {
System.out.println(policy.getClass());
}
}
I can get the JSON as string, autowire objectmapper and parse manually but i want to understand if its a known issue and if anyone else has faced the same with Spring boot. I have searched for solutions on this but i got was solution to deserializing to polymorphic classes but nothing related to issue with Spring boot.
In your method you haven't annotated the Policy method argument with #RequestBody. Which leads to Spring creating just an instance of Policy instead of using Jackson to convert the request body.
#PostMapping
public void savePolicy(#RequestBody Policy policy) {
System.out.println(policy.getClass());
}
Adding the #RequestBody will make that Spring uses Jackson to deserialize the request body and with that your annotations/configuration will be effective.

How to validate a configuration property only if a certain profile is active?

Is there a way to expect a property not to be null, only if a certain profile is active?
#Data
#Component
#ConfigurationProperties(prefix = "sample")
#Validated
public class SampleProperties {
#NotNull
private String sample1;
#NotNull
#Valid
private MoreProperties more = new MoreProperties()
#Data
public static class MoreProperties {
#NotNull // ← This should not be null only if the prod profile is active
private String sample2;
}
}
How about using two #ConfigurationProperties classes.
One like this:
#Validated
#ConfigurationProperties(prefix = "sample")
public class SampleProperties {
#NotNull
private String sample1;
// Getters + Setters
}
And one like this
#Validated
#ConfigurationProperties(prefix = "sample")
public class SamplePropertiesForProfile extends SampleProperties {
#NotNull // ← This should not be null only if the prod profile is active
private String sample2;
// Getters + Setters
}
And using these classes as beans only if the correct profile is active like this.
The #EnableConfigurationProperties provides the #ConfigurationProperties class as bean.
I prefer this over the #Component annotation on the #ConfigurationProperties class, because it's guaranteed that the bean is only created if it is required.
I see #ConfigurationProperties as a 'dumb' container of properties that does not know anything about their usage and therefore it can't know when its required and when it is not.
#Configuration
#Profile("!yourDesiredProfile")
#EnableConfigurationProperties(SampleProperties.class)
public class SampleNotProfileConfiguration {
private readonly SampleProperties sampleProperties;
#Autowired
public SampleNotProfileConfiguration(SampleProperties sampleProperties){
this.sampleProperties = sampleProperties;
}
// Configure your beans with the properties required for this profile
}
#Configuration
#Profile("yourDesiredProfile")
#EnableConfigurationProperties(SamplePropertiesForProfile .class)
public class SampleProfileConfiguration {
private readonly SamplePropertiesForProfile samplePropertiesForProfile ;
#Autowired
public SampleProfileConfiguration (SamplePropertiesForProfile samplePropertiesForProfile ){
this.samplePropertiesForProfile = samplePropertiesForProfile ;
}
// Configure your beans with the properties required for this profile
}
Doing this you have an explicit configuration for each profile with explicit configuration properties.
Even the name #ConfigurationProperties already states that this class contains properties of a configuration. These properties should match the requirements of the #Configuration class.
Therefore if a bean should be configured in a different way for a given profile it should be another #Configuration which then requires another explicit #ConfigurationProperties.
Can't verify this solution at the moment but for my understanding of #ConfigurationProperties this should work and looks more clean to me than mixing properties of different profiles.

Is it possible to use a MongoRepository with not-fixed document structure? [duplicate]

Mongodb is a no-schema document database, but in spring data, it's necessary to define entity class and repository class, like following:
Entity class:
#Document(collection = "users")
public class User implements UserDetails {
#Id private String userId;
#NotNull #Indexed(unique = true) private String username;
#NotNull private String password;
#NotNull private String name;
#NotNull private String email;
}
Repository class:
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String username);
}
Is there anyway to use map not class in spring data mongodb so that the server can accept any dynamic JSON data then store it in BSON without any pre-class define?
First, a few insightful links about schemaless data:
what does “schemaless” even mean anyway?
“schemaless” doesn't mean “schemafree”
Second... one may wonder if Spring, or Java, is the right solution for your problem - why not a more dynamic tool, such a Ruby, Python or the Mongoshell?
That being said, let's focus on the technical issue.
If your goal is only to store random data, you could basically just define your own controller and use the MongoDB Java Driver directly.
If you really insist on having no predefined schema for your domain object class, use this:
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
private Map<String, Object> schemalessData;
// getters/setters omitted
}
Basically it gives you a container in which you can put whatever you want, but watch out for serialization/deserialization issues (this may become tricky if you had ObjectIds and DBRefs in your nested document). Also, updating data may become nasty if your data hierarchy becomes too complex.
Still, at some point, you'll realize your data indeed has a schema that can be pinpointed and put into well-defined POJOs.
Update
A late update since people still happen to read this post in 2020: the Jackson annotations JsonAnyGetter and JsonAnySetter let you hide the root of the schemaless-data container so your unknown fields can be sent as top-level fields in your payload. They will still be stored nested in your MongoDB document, but will appear as top-level fields when the ressource is requested through Spring.
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
// add all other expected fields (getters/setters omitted)
private String foo;
private String bar;
// a container for all unexpected fields
private Map<String, Object> schemalessData;
#JsonAnySetter
public void add(String key, Object value) {
if (null == schemalessData) {
schemalessData = new HashMap<>();
}
schemalessData.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> get() {
return schemalessData;
}
// getters/setters omitted
}

Categories