Localdate json formatting - spring-boot

In a rest controller of a spring boot 3 application,
#GetMapping("/invoices")
public Page<InvoiceDto> getInvoices(#RequestParam Long clientId, #PageableDefault(direction = Sort.Direction.DESC,sort = "dateCreation" )Pageable pageable){
....
}
public record InvoiceDto(
long clientId,
#JsonFormat(pattern="yyyy-MM-dd")
LocalDate dateCreation
)
{
}
When I do a test with postman,
for a localdate, i get
"dateCreation":[2018,2,15]
I was thinking to get
"dateCreation":"2018-2-15"

Related

RestTemplate: While calling an API from one localhost to another i get error

I am working on spring boot project. In my eclipse I run two project first one is flight_system and second one is travel-site. I am sending request from travel-site to flight_system for get booking but i am getting error in postman. Url for get booking in flight_system is localhost:8050/booking which is work correctly in postman. Url for get booking in travel-site is localhost:8051/travel-site/booking which produce error in postman.
Here down is my code of flight_system:
Entity
#Entity
public class Booking {
#Id
private String bookingId;
private String passangerName;
private String flightName;
private String source;
private String destination;
// getter setter constructor
}
Controller
#RestController
public class BookingController {
#Autowired
private BookingService bookingService;
#GetMapping("/booking")
public List<Booking> getBooking() {
return bookingService.getAllBooking();
}
}
Here down is my code of travel-site:
Model
public class Booking {
private String bookingId;
private String passangerName;
private String flightName;
private String source;
private String destination;
// getter setter constructor
}
Controller
#RestController
public class TravelSiteController {
#Autowired
private RestTemplate restTemplate;
static final String baseUrl = "http://localhost:8050/";
#GetMapping(value = "/travel-site/booking")
public Booking getBooking() {
ResponseEntity<Booking> responseEntity = restTemplate.exchange(baseUrl + "booking", HttpMethod.GET, null,
Booking.class);
return responseEntity.getBody();
}
}
Stack trace in postman
"message": "Error while extracting response for type [class com.travelsite.model.Booking] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type com.travelsite.model.Booking from Array value (token JsonToken.START_ARRAY); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type com.travelsite.model.Booking from Array value (token JsonToken.START_ARRAY)\n at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]"
Try changing
ResponseEntity<Booking> responseEntity = restTemplate.exchange(baseUrl + "booking", HttpMethod.GET, null,
Booking.class);
return responseEntity.getBody();
to
List<Booking> booking = restTemplate
.getForObject(baseUrl + "booking",List.class);
To know better about rest template method you can go through this link

Strategy pattern in Spring boot application for payment gateway and methods

I have a controller where I am being passed a gatewayid for different providers. I have a BasePaymentService interface which is implemented by Payu service and Razorpay service for now.
I want to avoid using if condition for and have the strategy added without changing code and have the container inject correct strategy.
How do I add a strategy pattern here?
Would callback method be also a part of strategy here?
How do I account for different payment methods here? (cards, wallets)
BasePaymentService
public interface BasePaymentService {
PaymentDetail makePayment(PaymentDetail detail);
Payment callbackPayment(PaymentCallback detail);
}
Concrete PaymentService
#Service
#Slf4j
public class PayuPaymentService implements BasePaymentService {
#Autowired
PaymentRepository paymentRepository;
public PaymentDetail makePayment(PaymentDetail paymentDetail) {
PaymentUtil paymentUtil = new PaymentUtil();
paymentDetail = paymentUtil.populatePaymentDetail(paymentDetail);
savePaymentDetail(paymentDetail);
return paymentDetail;
}
#Override
public Payment callbackPayment(PaymentCallback paymentResponse) {
log.info("inside callback service >>>>>");
String msg = "Transaction failed.";
Payment payment = paymentRepository.findByTxnId(paymentResponse.getTxnid());
if(payment != null) {
log.info("in condition callback service");
//TODO validate the hash
PaymentStatus paymentStatus = null;
if(paymentResponse.getStatus().equals("failure")){
paymentStatus = PaymentStatus.Failed;
}else if(paymentResponse.getStatus().equals("success")) {
paymentStatus = PaymentStatus.Success;
msg = "Transaction success";
}
payment.setPaymentStatus(paymentStatus);
payment.setMihpayId(paymentResponse.getMihpayid());
payment.setMode(paymentResponse.getMode());
paymentRepository.save(payment);
}
return payment;
}
private void savePaymentDetail(PaymentDetail paymentDetail) {
log.info("in proceedPayment save");
Payment payment = new Payment();
payment.setAmount(Double.parseDouble(paymentDetail.getAmount()));
payment.setEmail(paymentDetail.getEmail());
payment.setName(paymentDetail.getName());
payment.setPaymentDate(new Date());
payment.setPaymentStatus(PaymentStatus.Pending);
payment.setPhone(paymentDetail.getPhone());
payment.setProductInfo(paymentDetail.getProductInfo());
payment.setTxnId(paymentDetail.getTxnId());
paymentRepository.save(payment);
}
}
Controller
#Api(value = "swipe: payment Service", tags = "Example")
#Validated
#RestController
#Slf4j
#RequestMapping(value = CommonConstants.BASE_CONTEXT_PATH)
public class CommonController {
#Autowired
private BasePaymentService paymentService;
#CrossOrigin(origins = "*")
#PostMapping(path = "/payment-details")
public #ResponseBody
PaymentDetail proceedPayment(#RequestBody PaymentDetail paymentDetail){
if(paymentDetail.getGatewayId().equalsIgnoreCase("payu") ){
paymentService.makePayment(paymentDetail);
}
else if(paymentDetail.getGatewayId().equalsIgnoreCase("rp")){
paymentService.makePayment(paymentDetail);
}
return paymentDetail;
}
#CrossOrigin(origins = "*" )
#RequestMapping(path = "/payment-response", method = RequestMethod.POST)
public #ResponseBody
Payment payuCallback(#RequestParam String mihpayid, #RequestParam String status, #RequestParam PaymentMode mode, #RequestParam String txnid, #RequestParam String hash, #RequestParam String amount, #RequestParam String productinfo, #RequestParam String firstname, #RequestParam String lastname, #RequestParam String email, #RequestParam String phone, #RequestParam String error, #RequestParam String bankcode, #RequestParam String PG_TYPE, #RequestParam String bank_ref_num, #RequestParam String unmappedstatus){
log.info("inside callback");
PaymentCallback paymentCallback = new PaymentCallback();
paymentCallback.setMihpayid(mihpayid);
paymentCallback.setTxnid(txnid);
paymentCallback.setMode(mode);
paymentCallback.setHash(hash);
paymentCallback.setStatus(status);
return paymentService.callbackPayment(paymentCallback);
}
}

How do I parse snake case fields in a FeignClient response json?

I have configured a FeignClient in my spring boot webapp where I'm calling an external api that returns the following object.
public class Issue {
private Assignee assignee;
private Date createdAt;
private Date updatedAt;
private Date closedAt;
private String description;
private Date dueDate;
public Assignee getAssignee() {
return assignee;
}
public void setAssignee(Assignee assignee) {
this.assignee = assignee;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getDueDate() {
return dueDate;
}
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
public Date getClosedAt() {
return closedAt;
}
public void setClosedAt(Date closedAt) {
this.closedAt = closedAt;
}
#Override
public String toString() {
return (JacksonJson.toJsonString(this));
}
}
The fields updatedAt, createdAt and closedAt are all in snake case. All multi-word fields show up as null. Is there any way of configuring the FeignClient's Jackson parser so that it can process snake case characters? Note, that I cannot change the default Jackson Parser for my spring boot webapp because I myself render json in camel case. I just need to configure this parser on the FeignClient that I'm using to connect to an external REST api.
I have verified that the json response returned from the api call contains valid values in each of these json fields.
Here's how I solved it. I created a custom JacksonParser as a Spring Bean.
#Configuration(proxyBeanMethods = false)
public class FeignClientDateFormatConfig {
#Bean
public Decoder feignDecoder() {
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
public ObjectMapper customObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
This successfully parses all snake case properties.
Please note that this has a severe limitation. If you have multiple FeignClients and only one of them returns snake-case json, then you're out of luck. This overrides the default FeignClient config. The only workaround possible with this solution is to move your FeignClient calls into a separate microservice so other FeignClient calls are not affected.

Spring throwing 403 exception on POST request but POSTMAN request working

I am trying to POST some data to rest api, When I send the request to API using SPRING REST I get the 403 exception.
I have tried adding user-agent header as suggested by other answers but nothing has worked for me so far. I also checked that access key when using POSTMAN and when calling the service is same. Any advice would be helpful;
The wrapper class to create the body of POST request
public class ApiRequest implements Serializable {
private static final long serialVersionUID = 3729607216939594972L;
#JsonProperty("id")
List<Integer> id;
#JsonProperty("sdate")
String sdate;
#JsonProperty("edate")
String edate;
#JsonProperty("fields")
List<String> fields;
public ApiRequest(List<Integer> id, String sdate, String edate, List<String> fields){
this.id=id;
this.sdate=sdate;
this.edate=edate;
this.fields=fields;
}
public void setEdate(String edate) {
this.edate = edate;
}
public void setSdate(String sdate){
this.sdate=sdate;
}
public void setFields(List<String> fields) {
this.fields = fields;
}
public void setId(List<Integer> id) {
this.id = id;
}
public String getEdate() {
return edate;
}
public String getSdate() {
return sdate;
}
public List<String> getFields() {
return fields;
}
public List<Integer> getId() {
return id;
}
#Override
public String toString() {
return "ApiRequest{" +
"id=" + id +
", sdate=" + sdate +
", edate=" + edate +
", fields=" + fields+
'}';
}
}
Code to call the api
private HttpHeaders getRequestHeaders() {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
requestHeaders.setAccept(Arrays.asList(MediaType.ALL));
requestHeaders.set("user-agent","Some User Agent);
requestHeaders.set("access_token", "ACCESS_TOKEN");
return requestHeaders;
}
ApiRequest request=new ApiRequest(Arrays.asList(10),DateUtil.today().toString(),DateUtil.today().plusDays(10).toString(),Arrays.asList("ALL"));
String response=post("RANDOM_URL",null,null,request,getRequestHeaders(),String.class,"");
Post super method:
public <T> T post(String baseUrl, String url, String query, Object body, HttpHeaders requestHeaders, Class<T> responseClassType, String logTag) {
// In this method body is converted to Json String and called the restExchange
If you are sure that with Postman you are getting correct results then you can enable debug logs for the underlying httpclient ( if apache http client is the underlying http library) by setting logging.level.org.apache.http=DEBUG. This will print all the request details like url, headers etc by which you can compare with what you are sending with Postman. If the client library is something different then you may need to write an interceptor to capture all the request details as explained here.

Getting a NullPointer instead of creating a token

I'm currently writing a Spring Boot application using JWT. Testing the functionality responsible for creating a token using different dates I encountered a problem. Well, instead of a token I get NullPointer. That's how I test it:
#Test
public void testGenerateTokenFromDifferentDates() {
when(clockMock.now())
.thenReturn(DateUtil.yesterday())
.thenReturn(DateUtil.now());
String token = createToken();
String tokenLater = createToken();
assertThat(token).isNotEqualTo(tokenLater);
}
private String createToken() {
String token = tokenUtil.generateToken(new TestUser(USERNAME));
return token;
}
And this is the class responsible for creating the token:
#Component
public class TokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
#Value("${jwt.secret}")
private String secret;
private Clock clock = DefaultClock.INSTANCE;
#Value("${jwt.expires.days}")
private Long expiration;
public String getUsernameFromToken(String token) {
return getClaimsFromToken(token, Claims::getSubject);
}
public <T> T getClaimsFromToken(String token, Function<Claims, T> resolverClaims) {
final Claims claims = getAllClaimsFromToken(token);
return resolverClaims.apply(claims);
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJwt(token).getBody();
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, this.secret)
.compact();
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration * 1000);
}
}
I can't think what the reason may be. Debugger also doesn't help me because it doesn't come to this moment. Here is the repository.
As #theonlyrao suggested here is the stack trace:
java.lang.NullPointerException
at com.github.springjwt.security.jwt.TokenUtil.calculateExpirationDate(TokenUtil.java:59)
at com.github.springjwt.security.jwt.TokenUtil.doGenerateToken(TokenUtil.java:47)
at com.github.springjwt.security.jwt.TokenUtil.generateToken(TokenUtil.java:38)
at com.github.springjwt.security.jwt.TokenUtilTest.createToken(TokenUtilTest.java:42)
at com.github.springjwt.security.jwt.TokenUtilTest.testGenerateTokenFromDifferentDates(TokenUtilTest.java:35)
It seems like either createdDate or expiration are null.
I'm not sure how createdDate get instantiated because I've not used that DefaultClock library.
I think the issue with expiration is that you haven't told Spring where to look for the the application properties in your test. Unless that happening elsewhere in code, you need to specific the path to the resource as described in https://www.baeldung.com/spring-classpath-file-access.

Resources