Serializing a request for a JSON PATCH with Jackson - spring-boot

I'm using Java Spring Boot as a gateway to an API with a PATCH endpoint that uses JSON Patch. Is it possible to use Jackson to serialize the JSON Patch document if there are different types? For example, if I want my JSON Patch document to have 3 operations where the values are of different type, is it possible for Jackson to serialize each operation in 3 different ways?
[
{
"op": "replace",
"path": "/name",
"value": "foo bar"
},
{
"op": "replace",
"path": "/tags",
"value": [
"done",
"complete"
]
},
{
"op": "replace",
"path": "/age",
"value": 25
},
]
I'm currently using a #RequestBody annotation to deserialize the request I receive from my frontend application.
// Controller
#PatchMapping(
path = "/images/{imageId}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<String>> updateImage(
#RequestBody #NotBlank List<UpdateOp> request) {
return imageService.updateImage(request);
}
// Image Service
public Mono<ResponseEntity<String>> updateImage(List<UpdateOp> request) {
...
.body(BodyInserters.fromObject(objMapper.writeValueAsBytes(request)))
...
}
I'm a Spring Boot noob so open to suggestions and alternate solutions.

Related

Why do I get random Http 404 from server between same requests with only one change in any field?

I haven an endpoint POST /api/marketplace/add that accepts a DTO object as request body. When I send the body below with platformName field set , server accepts request and processes it with no problem. But when I only try to change field platformName to null I get Http 404 error from server. I debugged the request and found out that it even can not reach controller method. I also got no trace from that error. What might be the cause that makes API respond differently to same request?
below
{
"platformName": "Trendyol",
"commissionAmounts": [
{
"amount": 23.45,
"categoryInfos": [
{
"categoryName": "Game"
}
],
"isCategoryBasedPricing": true
}
],
"shipmentAmounts": [
{
"amount": 23.45,
"scaleInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"volumeInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"isVolumeBasedPricing": true
}]
}
EDIT: dto model is
#Generated
public class MarketPlaceDTO {
#JsonProperty("platformName")
private String platformName;
#JsonProperty("commissionAmounts")
#Valid
private List<CommissionInfoDTO> commissionAmounts = new ArrayList<>();
#JsonProperty("shipmentAmounts")
#Valid
private List<ShipmentInfoDTO> shipmentAmounts = new ArrayList<>();
Controller is implementing swagger generated api interface. with postmapping and requestbody annotations.
#RequiredArgsConstructor
#RestController
public class MarketPlaceApiController implements MarketplaceApi {
private final MarketPlaceDAOService marketPlaceDAOService;
#Override
public ResponseEntity<BaseResponseDTO> addMarketPlace(MarketPlaceDTO
marketPlaceDTO) {
BaseResponseDTO dto =
marketPlaceDAOService.addMarketPlace(marketPlaceDTO);
return ResponseEntity.ok(dto);
}
}
Swagger generated api interface
#RequestMapping(
method = RequestMethod.POST,
value = "/marketplace/add",
produces = { "application/json", "application/xml" },
consumes = { "application/json" })
default ResponseEntity<BaseResponseDTO> _addMarketPlace(
#Parameter(name = "MarketPlaceDTO", description = "Add new
marketplace with given request body", required = true) #Valid
#RequestBody MarketPlaceDTO marketPlaceDTO) {
return addMarketPlace(marketPlaceDTO);
}
Response is
{
"timestamp": 1666866382906,
"status": 404,
"error": "Not Found",
"path": "/marketplace/add"
}
Obviously, that you use an endpoint with #RequestBody where body is a DTO.
And on trying to call this endpoint Spring Web first should match that a model in your request payload matches a require object in #RequestBody argument.
Ideally, using DTO as a request model is not a good idea. But I don't see your structure and cannot say if it's a problem or not.
The simple solution in your case is preparation (annotating) your DTO with specific JSON annotations:
#JsonInclude
#JsonIgnoreProperties(ignoreUnknown = true)
public class YourDTO {
private String platformName;
}
and for Controller add class annotation #Validated; for #RequestBody add #Valid annotation.
Recommendation: use request models for incoming objects, and later converters to DTO/entities with ability to response them with filtering (or in complex cases add also response model - usually it's overhead).
My problem was global exception handler component annotated with #ControllerAdvice. I tried to handle validation exceptions and forgot to add #ResponseBody to my handler methods which is in my case probabaly required. That somehow caused server to send http 404 message when any input validation exception was thrown. After I made changes , Exceptions was handled correctly by handler component.
#ControllerAdvice
#ResponseBody // this resolved my issue.
public class MVCExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseErrorResponse
methodArgumentExceptions(MethodArgumentNotValidException e){
return BaseErrorResponse.builder()
.errorMessage(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorMessage())
.errorCode(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorCode())
.errorTime(Date.from(Instant.now())).build();
}

Spring Boot catch multiple exceptions and send as error response

I am validating an incoming POST request which will create a database entity after validating the request data. I am trying to gather multiple errors in a single request and respond as error response following JSON API spec:
https://jsonapi.org/examples/#error-objects-multiple-errors
HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json
{
"errors": [
{
"status": "403",
"source": { "pointer": "/data/attributes/secretPowers" },
"detail": "Editing secret powers is not authorized on Sundays."
},
{
"status": "422",
"source": { "pointer": "/data/attributes/volume" },
"detail": "Volume does not, in fact, go to 11."
},
{
"status": "500",
"source": { "pointer": "/data/attributes/reputation" },
"title": "The backend responded with an error",
"detail": "Reputation service not responding after three requests."
}
]
}
Is it possible to do this by #ControllerAdvice. When Global exception handling is enabled by #ControllerAdvice and throws an exception, the next exception won't be caught.
Not directly, no. Not sure what is your business case/logic, therefore I don't know how you handling these exceptions in service layer, but in general, if you want to pass multiple errors in your #ExceptionHanlder - you could create a custom POJO:
public class MyError {
private String status;
private String source;
private String title;
private String detail;
getters/setters...
}
and then create a custom RuntimeException which would accept list of these POJOs:
public class MyRuntimeException extends RuntimeException {
private final List<MyError> errors;
public MyRuntimeException(List<MyError> errors) {
super();
this.errors = errors;
}
public List<MyError> getErrors() {
return errors;
}
}
And in your service layer you could create list of these POJOs, wrap then in your exception and throw it. Then in #ControllerAdvice you simply catch your exception and call accessor method to iterate against your list of POJOs to construct a payload you want.
Something like:
#ExceptionHandler (MyRuntimeException.class)
#ResponseStatus (BAD_REQUEST)
#ResponseBody
public Map<String, Object> handleMyRuntimeException(MyRuntimeException e) {
return singletonMap("errors", e.getErrors());
}

How to log spring-webflux WebClient request + response details (bodies, headers, elasped_time)?

Basically, I want to log a request/response informations in one log containing bodies/headers with a Spring WebClient.
With Spring RestTemplate we can do it with a ClientHttpRequestInterceptor. I find about ExchangeFilterFunction for Spring WebClient but haven't managed to do something similar in a clean way. We can use this filter and log the request AND THEN the response but I need both on the same log trace.
Moreover, I haven't managed to get the response body with ExchangeFilterFunction.ofResponseProcessor method.
I expect a log like this (current implementation working with a ClientHttpRequestInterceptor) with all the informations I need:
{
"#timestamp": "2019-05-14T07:11:29.089+00:00",
"#version": "1",
"message": "GET https://awebservice.com/api",
"logger_name": "com.sample.config.resttemplate.LoggingRequestInterceptor",
"thread_name": "http-nio-8080-exec-5",
"level": "TRACE",
"level_value": 5000,
"traceId": "e65634ee6a7c92a7",
"spanId": "7a4d2282dbaf7cd5",
"spanExportable": "false",
"X-Span-Export": "false",
"X-B3-SpanId": "7a4d2282dbaf7cd5",
"X-B3-ParentSpanId": "e65634ee6a7c92a7",
"X-B3-TraceId": "e65634ee6a7c92a7",
"parentId": "e65634ee6a7c92a7",
"method": "GET",
"uri": "https://awebservice.com/api",
"body": "[Empty]",
"elapsed_time": 959,
"status_code": 200,
"status_text": "OK",
"content_type": "text/html",
"response_body": "{"message": "Hello World!"}"
}
Does anyone manage to do something like this with Spring WebClient ? Or how would one proceed to track request/reponses issue with a Spring WebClient ?
You can use filter(), something like this:
this.webClient = WebClient.builder().baseUrl("your_url")
.filter(logRequest())
.filter(logResponse())
.build();
private ExchangeFilterFunction logRequest() {
return (clientRequest, next) -> {
log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers()
.forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
return next.exchange(clientRequest);
};
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.info("Response: {}", clientResponse.headers().asHttpHeaders().get("property-header"));
return Mono.just(clientResponse);
});
}
I don't think you can call .bodyToMono twice (once in the filter and then again where you use the client), so you might not be able to log that in the filter. As for the other details...
The WebClient config:
#Configuration
class MyClientConfig {
#Bean
fun myWebClient(): WebClient {
return WebClient
.builder()
.baseUrl(myUrl)
.filter(MyFilter())
.build()
}
}
The filter:
class MyFilter : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return next.exchange(request).flatMap { response ->
// log whenever you want here...
println("request: ${request.url()}, response: ${response.statusCode()}")
Mono.just(response)
}
}
}

Spring boot using Postman when making a POST with foreign key is returning null

I am trying to make a post request using POSTMAN with Spring Boot
The relation between User and Role is (ManyToOne).
Why does role returns this: ("role":null)
POSTMAN VIEW:
{
"name": "usertest",
"lastname": "usertest",
"email": "usertest#gmail.com",
"role": {
"id": 1
}
}
POSTMAN OUTPUT:
{
"id": 29,
"name": "usertest",
"lastname": "usertest",
"email": "usertest#gmail.com",
"role": {
"id": 1,
"role": null
}
}
CONTROLLER:
#PostMapping("user")
public ResponseEntity<User> addUser(#RequestBody User user){
try {
userService.save(user);
HttpHeaders httpHeaders = new HttpHeaders();
return ResponseEntity.status(HttpStatus.CREATED)
.headers(httpHeaders)
.body(user);
}
catch (Exception e){
e.printStackTrace();
return null;
}
}
ENTITY USER:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, updatable = false)
private Role role;
You are mapping the input request body to a User object and persisting it into DB by calling userService.save(user) and you are NOT re-initializing user property with the persisted entity reference. So, it is a plain POJO, not a JPA managed entity. That's why the "role" property is still null.
You could return the persistent user from userService.save(user) method and return that from the Controller method. Also. you need to take care of loading Role inside User as it is a LAZY property.

Spring boot Authorization server redirection issue to client after successful authentication via Facebook

I am trying to setup a spring boot Authorizaiton server which will have the internal user login and OAuth2 with facebook. I am facing the below issues -
If I make my Authorization server SessionCreationPolicy.STATELESS then after successful authentication from facebook control get stuck in Authorization server itself (Its not returning to the my client application while if SessionCreationPolicy.IF_REQUIRED then control returns to my client app).
When I am using SessionCreationPolicy.IF_REQUIRED then control returns and I can do a authorization_code flow but the jwt token generated by spring-security-jwt gives me only user_name information in token which is facebook user's id (not even name).
My local user authentication code works fine with code flow and I can customize my token with custom token enhancer and add other properties also but when I try to customize facebook principal object to my custom user I get error that string can not be casted to custom user object.
Please refer to the repo for details - dev repo
I am using below code for setup/jwt generation -
#EnableOAuth2Client // for Oauth setup
// jwt enhancer which gives me error when principal is converted to custom user
class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
Authentication auth = authentication.getUserAuthentication();
/* additionalInfo.put("email", ((CustomPrincipal)auth.getPrincipal()).getEmail());
additionalInfo.put("roles", ((CustomPrincipal)auth.getPrincipal()).getRoles());
additionalInfo.put("id", ((CustomPrincipal)auth.getPrincipal()).getId());*/
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
//SSO filter i am using -
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId());
tokenServices.setRestTemplate(template);
filter.setTokenServices(tokenServices);
// filter.setAuthenticationSuccessHandler(authenticationHandler);
return filter;
}
Any help is appreciated.
Thanks!
I was able to get an explanation for 2nd and 3rd point-
Since after the authentication is successful from Facebook; Spring boot authorization server stores authentication object as below format -
{
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "xyzxyzxyzxyzxyz",
"tokenValue": "xyzxyzxyzxyzxyz",
"tokenType": "bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"id": "xyzxyzxyzxyzxyz",
"name": "xyzxyzxyzxyzxyz",
"email": "xyzxyzxyzxyzxyz"
},
"authenticated": true,
"principal": "xyzxyzxyzxyzxyz",
"credentials": "N/A",
"name": "xyzxyzxyzxyzxyz"
},
"principal": "xyzxyzxyzxyzxyz",
"oauth2Request": {
"clientId": "xyzxyzxyzxyzxyz",
"scope": [],
"requestParameters": {},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"grantType": null,
"refreshTokenRequest": null
},
so when I was casting my principal to custom principal I was getting the error since in above model principal is just a string.
Note - I still have no idea how i can customize the above authentication object to my customuser object.

Resources