how to log the parent and child field using slf4j? - spring

I have parent class which is commonLoanRequest and child class LoanRequest given below. I get the loanRequest from the application received in the controller and passing the same object to the service where i am logging the payload which is 'LoanRequest'. But it's getting logged only fields available in 'LoanRequest' not the parent field 'commonLoanRequest'. I want to log the entire payload. I am using spring boot, slf4j and lombok.
Parent class:
#SuperBuilder
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#AllArgsConstructor
public class CommonCalculationRequest {
Integer assetQuantity;
BigDecimal assetUnitPrice;
BigDecimal netAmount;
BigDecimal initialPaymentAmount;
String disbursementDate;
String firstInstallmentDate;
Integer tenorInMonths;
String marginRatePercent;
String baseRatePercent;
String rateType;
InstallmentFrequency frequency;
BigDecimal subsidyAmount;
BigDecimal commissionAmount;
BigDecimal commissionPercent;
BigDecimal tradeInAmount;
RepaymentType repaymentType;
}
child class:
#Data
#SuperBuilder
#ToString(callSuper = true)
public class LoanCalculationRequest extends CommonCalculationRequest {
Integer governmentGrantPaymentMonth;
BigDecimal governmentGrantAmount;
Integer vatRefundPaymentMonth;
BigDecimal vatRefundAmount;
Integer gracePeriod;
Boolean isVatFinancingApplicable;
BigDecimal vatTaxAmount;
BigDecimal initialPaymentAmount;
BigDecimal tradeInAmount;
}
Service:
log.info("get amortizationSchedule for loanCalculationRequest={}", loanCalculationRequest);
above line is logging only loanCalculationRequest not the parent one. Since it's child class i am expecting parents fields should be accessible and logged.
Any suggestions would be appreciated!.
Thanks,
Adithyan

Related

How to mock jpa repository save(without return object) and modify id of input object

I use Spring Data JPA in my project and my model code is here:
#Getter
#Setter
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Activity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String number;
//...
}
My service code is here:
activityRepository.save(activity);//activity has no data in field id
activity.setNumber("D"+activity.getId()); //A
activityRepository.save(activity);
And my mock code is here:
when(activityRepository.save(activity)).thenReturn(tempActivity);
//invoke service method
verify(activityRepository).save(activity);
The question is that I have always been met with the Null Pointer Exception in code A.So how can I mock this repository save method?
Two points:
Use the returned object from activityRepository.save(activity)
activity = activityRepository.save(activity);//activity has no data in field id
activity.setNumber("D"+activity.getId()); //A
activity = activityRepository.save(activity);
Return a modified version, that is returned by the mock
tempActivity = activity.toBuilder().id(5).build();
when(activityRepository.save(activity)).thenReturn(tempActivity);
//invoke service method
verify(activityRepository).save(activity);

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.

JaversException ENTITY_INSTANCE_WITH_NULL_ID for ignored id

Using javers 5.11.2 I get the following exception although the id is set to be ignored. Why is that?
JaversException ENTITY_INSTANCE_WITH_NULL_ID: Found Entity instance 'my.package.javers.Leaf' with null Id-property 'id'
Update: I learned that
JaVers matches only objects with the same GlobalId
The id is specified using javax.persistence.Id. However, with each ORM it is possible to have an entity with a collection, then add a new element without id to that entity and then save it (CascadeType.Persist).
Is there any way to compare objects with javers in such a case?
Example (used lombok for boiler plate code).
The leaf:
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
#Entity
public class Leaf {
#DiffIgnore <============ id is ignored
#Id
private Long id;
private String color;
}
The tree:
#Builder
#Data
#Entity
public class Tree {
#Id
private Long id;
private String name;
#OneToMany
private Set<Leaf> leafs;
}
Test adds a leaf to the oakSecond without an id set. The diff cannot be made. An Exception is thrown.
#Test
public void testCompare_AddLeafToTree() {
Leaf leaf = Leaf.builder().id(1L).color("11").build();
Set<Leaf> leafsOfOakFirst = new HashSet<>();
leafsOfOakFirst.add(leaf);
Tree oakFirst = Tree.builder().id(1L).name("oakFirst").build();
oakFirst.setLeafs(leafsOfOakFirst);
Set<Leaf> leafsOfOakSecond = new HashSet<>();
leafsOfOakSecond.add(leaf);
leafsOfOakSecond.add(Leaf.builder().color("12").build());
Tree oakSecond = Tree.builder().id(1L).name("oakFirst").build();
oakSecond.setLeafs(leafsOfOakSecond);
Javers javers = JaversBuilder.javers().build();
Changes changes = javers.compare(oakFirst, oakSecond).getChanges();
assertThat(changes).isNotEmpty();
}
Same with the following definition of the Javers instance:
EntityDefinition leafEntityDefinition = EntityDefinitionBuilder.entityDefinition(Leaf.class).withIgnoredProperties("id").build();
Javers javers = JaversBuilder.javers().registerEntity(leafEntityDefinition).build();
You can't pass an Entity to Javers with null Id because it would be non-identifiable. If you use Hibernate to generate your Ids, make sure that you pass your object to javers.commit() after hibernate are done with its job. That's how the #JaversSpringDataAuditable aspect works.
Alternatively, you can model those objects with unstable IDs as Value Object in Javers.

Spring Boot + Webflux + Reactive MongoDB - get document by property Id

I'd like to find all Offer documents by Offer.ProductProperties.brand:
#Document(collection = "offers")
public class Offer {
#Id
private String id;
#NotNull
#DBRef
private ProductProperties properties;
ProductProperties:
#Document(collection = "product_properties")
public class ProductProperties {
#Id
private String id;
#NotNull
#NotEmpty
private String brand;
Service:
Flux<ProductProperties> all = productPropertiesRepository.findAllByBrand(brand);
List<String> productPropIds = all.toStream()
.map(ProductProperties::getId)
.collect(Collectors.toList());
Flux<Offer> byProperties = offerRepository.findAllByProperties_Id(productPropIds);
But unfortunately byProperties is empty. Why?
My repository:
public interface OfferRepository extends ReactiveMongoRepository<Offer, String> {
Flux<Offer> findAllByProperties_Id(List<String> productPropertiesIds);
}
How to find all Offers by ProductProperties.brand?
Thanks!
After reading some documentation found out that You cannot query with #DBRef. Hence the message
Invalid path reference properties.brand! Associations can only be
pointed to directly or via their id property
If you remove DBRef from the field, you should be able to query by findAllByProperties_BrandAndProperties_Capacity.
So the only ways is how you are doing. i.e. Fetch id's and query by id.
As I said in the comment, the reason it is not working is because return type of findAllByProperties_Id is a Flux. So unless u execute a terminal operation, you wont have any result. Try
byProperties.collectList().block()

Ignore xml tags while serializing pojo fields to xml

I am using jackson library to map POJO to XML.
compile ('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.0')
While serializing I need to ignore some of the fields. This is my POJO class. For example, the field lineNumber should be ignored.
#NoArgsConstructor
#AllArgsConstructor
#Getter
#XmlAccessorType(XmlAccessType.FIELD)
public class InvoiceLineItem {
#JacksonXmlProperty(localName = "LineNumber")
#XmlTransient
private Integer lineNumber;
#JacksonXmlProperty(localName = "ProductCode")
#XmlTransient
private String productCode;
#JacksonXmlProperty(localName = "ProductDescription")
#XmlTransient
private String productDescription;
}
I am using #XmlTransient with XmlAccessorType to ignore the fields. But the lineNumber field annotated with XmlTransient is not ignored while serializing.
Try adding the #JsonProperty(access = Access.WRITE_ONLY)
annotation to the lineNumber field.
Even thought it looks like a JSON thing,
the Jackson XmlMapper identifies the annotation and reacts accordingly.
Edit
The conclusion XmlMapper should support JSON serizlization is an example of the following, incorrect attempt at reasoning:
All men are mortal.
Socrates was mortal.
Therefore, all men are Socrates.
The XmlMapper is not a wrapper class around ObjectMapper.
It came after ObjectMapper and appears to share many features,
like the handling of some JSON annotation.

Resources