Configuring additional spring configuration metadata for list-nested properties - spring

I have the following model:
#ConfigurationProperties("example.foo")
class Foo {
private String name;
private List<Bar> bars;
public static class Bar {
private String url;
private String type;
}
}
I have configured properties for top-level class like this (in additional-spring-configuration-metadata.json):
{
"properties": [
{
"name": "example.foo.name",
"type": "java.lang.String",
"description": "The name of foo"
},
{
"name": "example.foo.bars",
"type": "java.util.List<com.example.Foo.Bar>",
"description": "These are bars of foo"
}
]
}
How can I add metadata for Bar.url and Bar.type? I tried using something like example.foo.bars[].url but it didn't work.

Make sure you have getters and setters in both Foo and Bar classes. This configuration worked for me (I am using Lombok to generate getters/setters):
#ConfigurationProperties("example.foo")
#Getter
#Setter
class Foo {
private String name;
private List<Bar> bars;
#Getter
#Setter
public static class Bar {
private String url;
private String type;
}
}
Metadata generated by spring-boot-configuration-processor:
{
"properties": [
{
"name": "example.foo.name",
"type": "java.lang.String",
"sourceType": "com.demo.Foo"
},
{
"name": "example.foo.bars",
"type": "java.util.List<com.example.Foo.Bar>",
"sourceType": "com.demo.Foo"
}
]
}

Related

springboot mongodb crud update only changed fields

Hello i have springboot with mongodb (spring-boot-starter-data-mongodb)
My problem is if I send only one or only the fields I want to change so the other values are set to null. I found something on the internet like #DynamicUpdate but not working on mongodb can you help me with this problem. I'm a beginner, I don't know how to help and it's quite important for me, if you need more code or more information, I'll write in the comment. I hope I have described the problem sufficiently. :)
MY POJO:
#Data
#Getter
#Setter
#NoArgsConstructor
public class Person {
#Id
private String id;
private String firstName;
private String lastName;
private boolean enabled;
private String note;
Repo
#Repository
public interface PersonRepository extends MongoRepository <Person, String> {
}
i have this call
#PutMapping("/{id}")
#ResponseBody
public void UpdatePerson (#PathVariable String id , #RequestBody Person person) {
personRepository.save(person);
}
#GetMapping(path = "/{id}")
public Person getPersonByid(#PathVariable String id ){
return personRepository.findById(id).orElseThrow(PersonNotFound::new);
}
sample:
get call before update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Rambo",
"lastName": "Norris",
"enabled": true,
"note": "hello this is my first note from you",
}
put call :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck"
}
get call after update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck",
"lastName": null,
"enabled": false,
"note": null,
}
what I would like
get call before update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Rambo",
"lastName": "Norris",
"enabled": true,
"note": "hello this is my first note from you",
}
put call :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck"
}
get call after update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck",
"lastName": "Norris",
"enabled": true,
"note": "hello this is my first note from you",
}
You are inserting a new collection instead of updating. First, you need to get the old value from mongodb, then you need to update the collection, then save to DB.
Use the below code in #putmapping.
#PutMapping("/{id}")
#ResponseBody
public void UpdatePerson (#PathVariable String id , #RequestBody Person person) {
Person personFromDB = personRepository.findById(person.getId());
personFromDB.setFirstName(person.getFirstName());
personRepository.save(personFromDB);
}
Try updating like this
#PutMapping("/{id}")
public ResponseEntity<Person> UpdatePerson (#PathVariable String id , #RequestBody
Person person) {
Optional<Person> personData = personRepository.findById(id);
if (personData.isPresent()) {
Person _tutorial = personData.get();
if(!StringUtils.isEmpty(person.getFirstName())) {
_tutorial.setFirstName(person.getFirstName());
}
if(!StringUtils.isEmpty(person.getLastName())) {
_tutorial.setLastName(person.getLastName());
}
if(!StringUtils.isEmpty(person.getNote())) {
_tutorial.setNote(person.getNote());
}
if(!StringUtils.isEmpty(tutorial.isEnabled())) {
_tutorial.setEnabled(tutorial.isEnabled());
}
return new ResponseEntity<>(repo.save(_tutorial), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

How to parse belowJSON in springboot

How to parse the response JSON and get summary and action each time and form a separate object with that.
{
"issues": [
{
"fields":
{
"summary": "This is summary",
"action": "Start"
}
}, {
"fields":
{
"summary": "Second summary",
"action": "Stop"
}
}
]
}
You can create a Issues class and Fields class. Below is a snippet for reference,
public class Issues {
private List<Fields> fields;
// Getters and Setters
}
public class Fields {
private String summary;
private String action;
// Getters and Setters
}
You can map the response to the Issues object and go ahead iterating the fields List to fetch the summary and action out of it.

How can I get the nested list of object with reactive couchbase?

I recently started reviewing/learning some about reactive couchbase and I'm trying to implement a sample using that technology with spring data and spring boot, I have the following models:
#Document
public class Person {
#Id
private String id;
#Field
private String name;
#Field
private String lastName;
#Field
private List<Address> address;
// Getters & Setters
}
#Document
public class Address {
#Id
private String code;
#Field
private String mainStreet;
#Field
private String secondStreet;
// Getters & Setters
}
Repositories:
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "address")
public interface AddressRepository extends ReactiveCouchbaseRepository<Address, String> {
}
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "person")
public interface PersonRepository extends ReactiveCouchbaseRepository<Person, String> {
}
Service:
public interface PersonService {
Flux<Person> getAllPersons();
Mono<Person> getPerson(String id);
Flux<Address> getAddressesByPerson(String id);
}
#Service
public class PersonServiceImpl implements PersonService {
#Autowired
private PersonRepository personRepository;
#Override
public Flux<Person> getAllPersons() {
return personRepository.findAll();
}
#Override
public Mono<Person> getPerson(String id) {
return personRepository.findById(id);
}
#Override
public Flux<Address> getAddressesByPerson(String id) {
return null;
}
}
Controller:
#RestController
#RequestMapping("/sample_couchbase")
public class PersonController {
#Autowired
private PersonService personService;
#GetMapping("/people")
public Flux<Person> getAllPersons() {
return personService.getAllPersons();
}
#GetMapping("/person/{id}")
public Mono<ResponseEntity<Person>> getPersonsById(#PathVariable String id) {
return personService.getPerson(id)
.map(person -> ResponseEntity.status(HttpStatus.OK).body(person))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}
As so far this works well, I can retrieve all the people and also filter by an id of a specific person, on the other hand I would like to retrieve all list of addresses of a specific person, I mean I have this document in couchbase:
{
"id": "1",
"name": "Scooby",
"lastName": "Doo",
"address": [
{
"code": "A1",
"mainStreet": "AAA",
"secondStreet": "BBB",
"phone": "11111",
"place": "home"
},
{
"code": "A2",
"mainStreet": "CCC",
"secondStreet": "DDD",
"phone": "22222",
"place": "work"
},
{
"code": "A3",
"mainStreet": "EEE",
"secondStreet": "FFF",
"phone": "33333",
"place": "treasury"
}
],
"classType": "com.jcalvopinam.model.Person"
}
When I call the service e.g.: http://localhost:8080/sample_couchbase/person/1/addresses I want to get this:
"address": [
{
"code": "A1",
"mainStreet": "AAA",
"secondStreet": "BBB",
"phone": "11111",
"place": "home"
},
{
"code": "A2",
"mainStreet": "CCC",
"secondStreet": "DDD",
"phone": "22222",
"place": "work"
},
{
"code": "A3",
"mainStreet": "EEE",
"secondStreet": "FFF",
"phone": "33333",
"place": "treasury"
}
]
I imagined create in the repository a method something like this:
Mono<Person> findById(String id);
And in the service layer I imagine getting the whole object and then filtering by Address, but this is something different from what I'm used to doing, now I'm using Mono and Flux and not a simple object or a List, so I don't know how to do that, can someone give me an idea? or is there a better solution?
I found an easy way to retrieve the Addresses of a specific Person, I used a query where I specified what I want to retrieve:
#Query("SELECT addresses, META(sample).id as _ID, META(sample).cas as _CAS FROM `sample` WHERE id = $1")
Flux<Person> findAddressesByPerson(String id);
I noticed that it is necessary to specify the META(sample).id as _ID and META(sample).cas as _CAS attributes to avoid the following error:
org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to retrieve enough metadata for N1QL to entity mapping, have you selected _ID and _CAS?; nested exception is rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: com.couchbase.client.java.query.DefaultAsyncN1qlQueryRow.class
If you want to take a look at the implementation in more detail, here is the sample: https://github.com/juanca87/sample-couchbase-rx

Convert a pojo variable to another bean which takes it as list of name and value

I have a bean with set of variables. I want to convert them to another bean say Parameters which has just name and value. I need to make the list of parameters of those all variables in name-value pair.
I am thinking of Dozer or mapstruct but this seems to be not really helpful.
Today, using objectmapper to convert it to maps,iterating over maps and creating the list of parameter.
Any help would be appreciated.
class TestClass{
private String str1;
private String str2;
private String str3;
}
Class Paramters{
private String name;
private String value;
}
**Bean1:**
'[
{
"str1": "string",
"str2": "string",
"str3": "string"
},
{
"str1": "string1",
"str2": "string1",
"str3": "string1"
}
]'
To Convert **Bean2**
'[{
"parameters": [
{
"name": "str1",
"value": "string"
},
{
"name": "str2",
"value": "string"
},
{
"name": "str3",
"value": "string"
}
]
},
{
"parameters": [
{
"name": "str1",
"value": "string"
},
{
"name": "str2",
"value": "string"
},
{
"name": "str3",
"value": "string"
}
]}]'
Mapstruct and the like generally convert from one bean to another, your are essentially trying to convert to a map and wrap that to a bean.
Something like BeanMap would be more appropriate.
Untested sample code:
class Bean2 {
private final Map<String, Object> properties;
public Bean2(Map properties) { this.properties = properties }
public static class Entry { String name, String value /* getters and setters */}
#JsonProperty
public List<Map<String, Object>> getProperties() {
// build a map of maps here, each map would have
}
}
Then just:
new Bean2(new BeanMap(bean2))

Using Spring Data Projection on DBRef objects

I'm starting to learn Spring Data on a pretty simple project using a MongoDB database, and I'm having some trouble when using DBRef - and maybe in how to model a NoSQL db in general
Description
My project should organize a simple competition with a organizer (CD) and one-to-many participants. Because people can participate in multiple competitions I made respositories for both Competition and Person.
The complete code can be seen on GitHub : https://github.com/elkjaerit/rest-sample
Here is the base classes:
public class Competition {
#Id private String id;
private String name;
#DBRef
private Person organizer;
private List<Participant> participants = new ArrayList<>();
}
public class Participant {
private String freq;
#DBRef
private Person person;
}
public class Person {
#Id
private String id;
private String name;
}
Repositories:
#RepositoryRestResource(collectionResourceRel = "competition", path = "competition")
public interface CompetitionRepository extends MongoRepository<Competition, String> {
}
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String> {
}
Problem
When I'm requesting a competition resource I'm not getting enough info on the participants - only "freq" field is shown. I've tried using #Projection and managed to get it to work for the organizer but I don't know how to get the person object for the participant ?
Result without projection
{
"_links": {
"competition": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a{?projection}",
"templated": true
},
"organizer": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a/organizer"
},
"self": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a"
}
},
"name": "Competition #1",
"participants": [
{
"freq": "F0"
},
{
"freq": "F1"
},
{
"freq": "F2"
},
{
"freq": "F3"
}
]
}
And with projection
{
"_links": {
"competition": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a{?projection}",
"templated": true
},
"organizer": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a/organizer"
},
"self": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a"
}
},
"name": "Competition #1",
"organizer": {
"name": "Competition organizer"
},
"participants": [
{
"freq": "F0"
},
{
"freq": "F1"
},
{
"freq": "F2"
},
{
"freq": "F3"
}
]
}
Any suggestions ?
You may be able to use SPEL to call the getter for your related documents.
Your projection may look something like this -
#Projection(name = "comp", types = {Competition.class})
public interface CompetitionProjection {
String getName();
Person getOrganizer();
#Value("#{target.getParticipants()}")
List<Participant> getParticipants();
}

Resources