How to turn off deserialization for enum - java

I would like to turn off deserialization for concrete enum. Is it possible?
Exercise model class:
package main.exercise;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Entity
#Builder
public class Exercise {
#Id
#GeneratedValue
private int id;
#NonNull
private String name;
#NonNull
private ExerciseType exerciseType;
private double caloriesBurned;
private String exerciseDescription;
}
I got method in controller:
#PostMapping("/addExercise")
public List<String> addExercise(#RequestBody Exercise exercise) {
return exerciseCrudActivitiesService.addExercise(exercise);
}
which takes Exercise body and if type of Exercise is wrong I got error while POST http request:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `main.exercise.ExerciseType` from String "CARdDIO": not one of the values accepted for Enum class: [CARDIO, WEIGHTLIFTING]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `main.exercise.ExerciseType` from String "CARdDIO": not one of the values accepted for Enum class: [CARDIO, WEIGHTLIFTING]
at [Source: (PushbackInputStream); line: 4, column: 25] (through reference chain: main.exercise.Exercise["exerciseType"])]
The point is in service I got validator, which validates whether type of enum is right or wrong and return string to list of errors from all validators. Unfortunately this code cannot be reached because of error.
public List<String> addExercise(Exercise exercise) {
ExerciseValidator validator = new ExerciseValidator();
List<String> messages = validator.validate(exercise);
if (messages.isEmpty()) {
exerciseRepository.save(exercise);
}
return messages;
}
Validator
package main.exercise.validator.attributesvalidators;
import main.exercise.Exercise;
import main.exercise.ExerciseType;
import main.exercise.validator.ExerciseAttributesValidator;
import java.util.InputMismatchException;
public class ExerciseTypeValidator implements ExerciseAttributesValidator {
#Override
public String validate(Exercise exercise) {
if (exercise.getExerciseType() == null) {
return "You didn't put exercise type!";
}
try {
ExerciseType.forName(exercise.getExerciseType().name());
} catch (InputMismatchException e) {
return "Wrong exercise type!";
}
return null;
}
}

To turn off (de-)serialization, you can add the #JsonIgnore to the exerciseType field. However, I don't think this will help you anyways.
If serialization is ignored, the field would always be null which is not the intended behavior.
Your validator is too late. Note: the validate method takes an Exercise object as a parameter. The problem occurs during the creation of this object already.
When you get to the point that the line ExerciseType.forName(exercise.getExerciseType().name()); get's executed, it will NEVER throw an exception, because getExerciseType() is already a valid enum.
Instead of this custom validator, you could make use of a Spring #ControllerAdvice to register your own Exception handler for that error type.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import com.fasterxml.jackson.databind.exc.InvalidFormatException
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<?> badFormatException(InvalidFormatException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
See e.g. https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services for more details.

Related

Unsatisfied dependency expressed through constructor parameter

Error Message I am getting
Error creating bean with name 'libraryController' defined in file :
Unsatisfied dependency expressed through constructor parameter 0:
Error creating bean with name 'libraryService' defined in file.
Unsatisfied dependency expressed through constructor parameter 2:
Error creating bean with name 'lendRepository' defined in
com.restapi.respository.LendRepository defined in
#EnableDynamoDBRepositories declared on DynamoDBConfig: Could not
create query for public abstract java.util.Optional
com.restapi.respository.LendRepository.findByBookAndStatus(com.restapi.model.Book,com.restapi.model.LendStatus);
Reason: No property 'book' found for type 'Lend' Did you mean
''bookId''
LendRepository.Java:
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import com.restapi.model.Book;
import com.restapi.model.Lend;
import com.restapi.model.LendStatus;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
#EnableScan
public interface LendRepository extends CrudRepository<Lend, String> {
Optional<Lend> findByBookAndStatus(Book book, LendStatus status);
}
Book.java:
package com.restapi.model;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#DynamoDBTable(tableName = "book")
public class Book {
#DynamoDBHashKey
#DynamoDBAutoGeneratedKey
private String id;
#DynamoDBAttribute
private String name;
#DynamoDBAttribute
private String isbn;
#DynamoDBAttribute
private String authorId;
}
LibraryService.java:
public List<String> lendABook (BookLendRequest request) throws Exception {
Optional<Member> memberForId = memberRepository.findById(request.getMemberId());
if (!memberForId.isPresent()) {
throw new Exception("Exception message");
}
Member member = memberForId.get();
if (member.getStatus() != MemberStatus.ACTIVE) {
throw new RuntimeException("User is not active to proceed a lending.");
}
List<String> booksApprovedToBurrow = new ArrayList<>();
request.getBookIds().forEach(bookId -> {
Optional<Book> bookForId = bookRepository.findById(bookId);
if (!bookForId.isPresent()) {
try {
throw new Exception("Exception message");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Optional<Lend> burrowedBook = lendRepository.findByBookAndStatus(bookForId.get(), LendStatus.BURROWED);
if (!burrowedBook.isPresent()) {
booksApprovedToBurrow.add(bookForId.get().getName());
Lend lend = new Lend();
lend.setMemberId(memberForId.get().getId());
lend.setBookId(bookForId.get().getId());
lend.setStatus(LendStatus.BURROWED);
lend.setStartOn(Instant.now().toString());
lend.setDueOn(Instant.now().plus(30, ChronoUnit.DAYS).toString());
lendRepository.save(lend);
}
});
The error message and solution suggestion is pretty obvious.
Could not create query for public abstract java.util.Optional com.restapi.respository.LendRepository.findByBookAndStatus(com.restapi.model.Book,com.restapi.model.LendStatus); Reason: No property 'book' found for type 'Lend' Did you mean ''bookId''
Your findByBookAndStatus() method that you created in LendRepository appears incorrect. You don't have a book property for your Lend object. Perhaps you are keeping the id value of the Book as a property in your Lend object.
If you have an attribute named bookId in your Lend object, your method should be:
Optional<Lend> findByBookIdAndStatus(ID id, LendStatus status);
Your first argument in the method should be the same as the data type of the id attribute of your Book object.
Annotate 'LibraryService' class with #Service.

The method getType() is undefined for the type Ingredient

package tacos.web;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import java.lang.reflect.Field;
import lombok.extern.slf4j.Slf4j;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
#Slf4j
#Controller
#RequestMapping("/design")
#SessionAttributes("tacoOrder")
public class DesignTacoController {
#ModelAttribute
public void addIngredientsToModel(Model model) {
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
new Ingredient("CARN", "Carnitas", Type.PROTEIN),
new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
new Ingredient("LETC", "Lettuce", Type.VEGGIES),
new Ingredient("CHED", "Cheddar", Type.CHEESE),
new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
new Ingredient("SLSA", "Salsa", Type.SAUCE),
new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
);
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
}
#GetMapping
public String showDesignForm(Model model) {
model.addAttribute("taco", new Taco());
return "design";
}
private Iterable<Ingredient> filterByType(
List<Ingredient> ingredients, Type type) {
return ingredients
.stream()
.filter(x -> x.getType().equals(type))
.collect(Collectors.toList());
}
}
I was going through the book Spring in action edition 6 chapter. In that in the filterByType method the '.getType()' is showing the error
The method getType() is undefined for the type Ingredient
I thought it was the error due to lombok but I have installed that as well. I have also import the package 'java.lang.reflect.Field' but still getting the error.
package tacos;
import lombok.Data;
#Data
public class Ingredient {
public Ingredient(String string, String string2, Type wrap) {
// TODO Auto-generated constructor stub
}
private final String id = "";
private final String name = "";
private final Type type = null;
public enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
The above class is the Ingredient Class
Seems you are not the first person who faced with this issue https://coderanch.com/t/730026/java/lombok
In addIngredientsToModel of class DesignTacoController the flagged
error is "The constructor Ingredient(String, String, Ingredient.Type)
is undefined". Also, in method filterByType the flagged error is "The
method getType() is undefined for the type Ingredient". It zappears
that lombok is just not working. But I have lombok in the pom:
Answer:
Just adding Lombok as a dependency does not make Eclipse recognize it,
you'll need a plugin for that. See https://www.baeldung.com/lombok-ide
for instructions on installing Lombok into Eclipse (and IntelliJ for
those who prefer it).

Catch cast exception in #PathVariable when type is Long and Client sent String(not number)

I have a Spring Boot controller with param #PathVariable long stopPointId, when user will be send request like "url/StopPoints/1" everything work perfect, but when request will be look like "url/StopPoints/StopPointNumber" nothing will be happening. I want to catch this situation and throw my custom error because user need to know, that param only take long value. For instance: You are not able to pass String as parameter. You should use number value e.g 123."
One way would be to handle the NumberFormatException that would be thrown by Spring Boot while trying to typecast a String into a Long.
This is my custom HTTP-Response class, but I trust you have your own...
package com.stackoverflow.rav.StackOverflowExamples.api;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.http.HttpStatus;
import java.util.Date;
public class HttpResponse {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "MM-dd-yyyy hh:mm:ss", timezone = "UTC")
private Date timeStampUTC;
private int httpStatusCode;
private HttpStatus httpStatus;
private String reason;
private String message;
/* Include setters, getters, constructors, or use Lombok */
}
Then the exception handler... (exception message should be generic in order to increase reusability)
package com.stackoverflow.rav.StackOverflowExamples.api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
#RestControllerAdvice
public class ExampleExceptionHandler {
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<HttpResponse> accountDisabledException() {
return createHttpResponse(HttpStatus.BAD_REQUEST, "Should pass long not string!");
}
private ResponseEntity<HttpResponse> createHttpResponse(HttpStatus httpStatus, String message) {
return new ResponseEntity<>(new HttpResponse(
httpStatus.value(),
httpStatus,
httpStatus.getReasonPhrase().toUpperCase(),
message.toUpperCase()),
httpStatus);
}
}
Finally the controller...
package com.stackoverflow.rav.StackOverflowExamples.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class ExampleController extends ExampleExceptionHandler {
#GetMapping("/url/{someValue}")
public String hello(#PathVariable("someValue") long someValue) {
return "You entered: " +someValue;
}
}
If all goes well you should get a response like the screen-snippet below when doing http://localhost:8080/url/abcd
This answer might look lengthy, but we are Java developers :D

How to return a proper validation error for enum types in Spring Boot?

I have created a demo spring boot application. The request takes in a car object and returns the same. I am trying to figure out a way to send a proper message to the user if the carType is not a valid enum.
Request
{
"carType": "l"
}
Response
{
"message": "JSON parse error: Cannot deserialize value of type `com.example.demo.Car$CarType` from String \"l\": not one of the values accepted for Enum class: [Racing, Sedan]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.example.demo.Car$CarType` from String \"l\": not one of the values accepted for Enum class: [Racing, Sedan]\n at [Source: (PushbackInputStream); line: 2, column: 13] (through reference chain: com.example.demo.Car[\"carType\"])",
"statusCode": "BAD_REQUEST"
}
How can I send a proper message to the user without displaying the class names and the stack trace? I want the user to know what are the valid types of enum but do not want the message to have java error trace. Is there a way to extract proper message having just the field that is erroneous. What is the standard way of validating enums?
Car.java
import lombok.*;
#Getter
#Setter
public class Car {
enum CarType {
Sedan,
Racing
}
CarType carType;
}
DemoControllerAdvice.java
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
#RestControllerAdvice
public class DemoControllerAdvice {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(HttpMessageNotReadableException.class)
public ErrorObject handleJsonErrors(HttpMessageNotReadableException exception){
return new ErrorObject(exception.getMessage(), HttpStatus.BAD_REQUEST);
}
}
DemoController.java
#RestController
public class DemoController {
#PostMapping("/car")
public Car postMyCar(#RequestBody Car car){
return car;
}
}
Is there any neat way to achieve using Hibernate Validator but without using a custom hibernate Validator like in the answer below?
How to use Hibernate validation annotations with enums?
No. The de-serialisation happens before the validation.
If you want Hibernate to do it, then you already linked to the answer. Otherwise you'll have to handle the exception yourself.
private static final Pattern ENUM_MSG = Pattern.compile("values accepted for Enum class: \[([^\]])\]);"
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(HttpMessageNotReadableException.class)
public ErrorObject handleJsonErrors(HttpMessageNotReadableException exception){
if (exception.getCause() != null && exception.getCause() instanceof InvalidFormatException) {
Matcher match = ENUM_MSG.matcher(exception.getCause().getMessage());
if (match.test()) {
return new ErrorObject("value should be: " + match.group(1), HttpStatus.BAD_REQUEST);
}
}
return new ErrorObject(exception.getMessage(), HttpStatus.BAD_REQUEST);
}

In Java, how do I return meaningful, JSON formatted errors from Resteasy validation?

I have a RESTFul API consuming/returning JSON in the request/response body. When the client sends invalid data (valid JSON but invalid values for the fields) I want to be able to return a JSON structure (as well as the relevant 400+ code).
This structure would then allow the frontend to parse the errors on a per-field basis and render the errors alongside the input fields.
E.g. ideal output:
{
"errors":{
"name":["invalid chars","too long","etc"]
"otherfield":["etc"]
}
}
I am using Resteasy for the API, and using violation exceptions it's fairly easy to get it to render JSON errors:
#Provider
#Component
public class ValidationExceptionHandler implements ExceptionMapper<ResteasyViolationException> {
public Response toResponse(ResteasyViolationException exception) {
Multimap<String,String> errors = ArrayListMultimap.create();
Consumer<ResteasyConstraintViolation> consumer = (violation) -> {
errors.put(violation.getPath(), violation.getMessage());
};
exception.getParameterViolations().forEach(consumer);
Map<String, Map<String, Collection<String>>> top = new HashMap<>();
top.put("errors", errors.asMap());
return Response.status(Status.BAD_REQUEST).entity(top)
.build();
}
}
However, the error paths (violation.getPath()) are property-centric rather than XmlElement-name-centric.
E.g. the above outputs:
{
"errors":{"createCampaign.arg1.name":["invalid chars","etc"]}
}
I have tried stripping the index back from the last dot to get "name" but there are other issues with that hack.
E.g. if my "name" property isn't "name" it doesn't work:
#XmlElement(name="name")
#NotNull
private String somethingelse;
"somethingelse" will be returned to client, but they client has no idea what that is:
{
"errors":{"somethingelse":["cannot be null"]}
}
The client wants "name" since that is what the field was called when they sent it.
My resource:
package com.foo.api;
import org.springframework.stereotype.Service;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.foo.dto.CarDTO;
#Service
#Path("/car")
public class CarResource {
#POST
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public CarDTO create(
#Valid CarDTO car
) {
//do some persistence
return car;
}
}
example dto:
package com.foo.dto;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.xml.bind.annotation.XmlElement;
public class CarDTO {
#Min(1)
#Max(10)
#NotNull
#XmlElement(name="gears")
private int cogs;
}
This article describes quite well what you need to do.
Basically you should implement an ExceptionMapper.
#Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
#Override
public Response toResponse(ValidationException exception) {
Response myResponse;
// build your Response based on the data provided by the exception
return myResponse;
}
}
A custom error message can be used so you wouldnt need to look at the path
#NotNull(message="name cannot be null")

Categories