I'm trying to write a spring endpoint that generates different reports, depending on the request parameters
#GetMapping
#ResponseBody
public ResponseEntity<String> getReport(
#RequestParam(value = "category") String category,
#Valid ReportRequestDTO reportRequestDTO) {
Optional<ReportCategory> reportCategory = ReportCategory.getReportCategoryByRequest(category);
if (reportCategory.isEmpty()) {
throw new ApiRequestException("Requested report category does not exist.");
}
try {
Report report = reportFactory.getReport(reportCategory.get());
return ResponseEntity.ok().body(report.generate(reportRequestDTO));
} catch (Exception e) {
throw new ApiRequestException("Could not generate report.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
The ReportCategory is an enum and Report is an abstract class of which multiple concrete implementations exist. Depending on the passed category the ReportFactory will instantiate the right Report. ReportRequestDTO is a class that contains all parameters that are required to generate the report. If this is passed to the generate() method, the report is generated.
Depending on the ReportCategory, different parameters may be required and need to be validated, but there can also be some common ones.
Is it possible to have an abstract class ReportRequestDTO with the common parameters and then a concrete DTO implementation for each report with its unique parameters, that is instantiated and validated depending on the report category before it is passed to the generate() method?
Edit:
I want something like this for shared parameters:
#Data
public abstract class ReportRequestDTO {
#NotEmpty
private String foo;
#NotEmpty
private String bar;
}
And then for each Report the individual parameters:
#Data
public class ReportADTO extends ReportRequestDTO {
#NotEmpty
private String foobar;
}
But I can't use and abstract class as DTO, because it can't be instantiated.
Also this would try to validate foobar even if I don't need it in ReportB.
Basically I want this endpoint to be able to generate all reports. Since I don't know yet which reports exist and may be added in the future and which parameters they require, I'd like to have the DTO extendable so that I don't have to touch the endpoint anymore and simply implement the report and create a DTO that extends ReportRequestDTO with the required parameters for that report.
So what I need is an Object that I can use as ReportRequestDTO that is extendable with all parameters for all reports so that I can pass them on the request, and then I would instantiate the DTO for the particular report with the request parameters and validate it.
You can use post-validation. I do not see why you need it for you because you can have only one input structure in the one request endpoint body. Would you like to cut the data from the request and ignore what is not used? This is also a solution anyway.
Option 1:
Inject javax.validation.Validator interface and call validate. It can be autowired. API It is just the result Set.
Option 2:
If you would like to throw exception like controller, you have to create a/more bean(s) with #Validated annotation such as:
public class ModelA {
#NotEmpty
private String text;
// getter setter
}
#Component // or use #Configuration with #Bean
#Validated
public class ReportA {
public void generate(#Valid ModelA model) { ... }
}
So I ended up changing it to a POST request and allowing a JSON body, that is then parsed to the required DTO like so:
ReportRequestDTO reportRequestDTO = report.getDto();
reportRequestDTO = new ObjectMapper().readValue(paramsJson,
reportRequestDTO.getClass());
getDTO() returns an instance of the concrete DTO that is populated with the JSON data and it is then validated as in #Numichi answer
Related
I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.
Is it possible to add a custom message to an enum if validation fails?
I have this enum class:
public enum EngineType{
FOO('F'),
BAR('B'),
QUX('Q');
private char id;
EngineType(char id) {
this.id = id;
}
public char getId() {
return this.id;
}
}
My model class contains private MyEnum myEnum;.
Currently if a value is passed in which isn't a valid enum, I get this BindingException:
{
"status": 400,
"validationErrors": {
"myEnum": "Failed to convert property value of type 'java.lang.String' to required type 'ie.aviva.services.motor.cartellservice.model.EngineType' for property 'myEnum'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.validation.constraints.NotNull ie.aviva.services.motor.cartellservice.model.EngineType] for value 'corge';"
},
"title": "Bad Request"
}
My controller looks like this:
#RequestMapping(
method = RequestMethod.GET,
value = Endpoints.TRUE_MATCH,
produces = {"application/json"})
public ResponseEntity<ResponseWrapper<List<TrueMatch>>> getTrueMatch(
#Valid MyDetails MyDetails) {
LogContext.put(Constants.TAG, myDetails);
LOG.info(
"Controller called to get true match with my details: " + myDetails.toString());
...
}
MyDetails is like this:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#SuperBuilder
public class MyDetails extends BasicDetails {
#NotBlank
#Pattern(
regexp = "^[a-zA-Z]*$",
message = "'${validatedValue}' contains unsupported characters")
private String name;
#NotNull private MyEnum myEnum;
...
}
Is it possible to change the message to some custom message of my own?
I'm already able to do this in annotations that I added to other variables by including the message parameter in the annotation. I tried creating an annotation to validate the pattern as seen here but it didn't work. I think because the annotation was added like this, which was too late. The exception had already been thrown:
#NotNull
#EnumNamePattern(regexp = "foo|bar|qux")
private MyEnum myEnum;
Sure. The simplistic solution would be to simply accept the value as a String, then wrap your parsing logic in a try-catch block, then return the result you want the user to see.
final MyEnum userValue;
try
{
userValue = MyEnum.valueOf(someStringInput);
}
catch (Exception e)
{
//respond to the user, either with an exception, or
//a proper response according to your application
}
Bean Validation & ControllerAdvice
You can make use of the Bean Validation and Exception-handling mechanisms provided by spring.
Note that OP is already using Bean validation in their project, which can be observed by the usage of annotations #NotBlank, #Pattern. So the following reference is exclusively for the Readers.
To include the Bean validation into your Spring Boot project, you can add Spring Boot Starter Validation dependency. In a nutshell, Bean validation is a specification (like for instance JPA) describing an IPA, which offers various annotations and interfaces for verifying that the data of domain objects is correct. And Hibernate Validator is an implementation of this specification which comes with Spring Boot Starter Validation.
To validate that the given String matches one of the enum-constants we would need a separate class, let's say MyDetailsDto. In case if you wonder why a new class? If the validation constant would be applied on the field of type enum it would validate only input that can be successfully parsed into an enum (otherwise an IllegalArgumentException would be thrown before applying the validation annotation), which can be useful only certain enum-constants can be assigned to a field. But that's not the case, we need to find out whether the given string is equal to one of the enum constants (by the way, in addition this approach creates an opportunity for sanitizing the input, for instance replacing some characters if needed).
#Getter
#Setter
public class MyDetailsDto {
private String name;
#EngineTypeConstraint
private String engineType;
public MyDetails toMyDetails() {
return new MyDetails(name, EngineType.valueOf(engineType));
}
}
So, to create a custom Validation constraint, we need two things: a custom annotation a Validator associated with it (for more information refer to the documentation - Configuring Custom Constraints):
Each bean validation constraint consists of two parts:
A #Constraint annotation that declares the constraint and its configurable properties.
An implementation of the jakarta.validation.ConstraintValidator interface that implements the constraint’s behavior.
We can find more information regarding the requirements for the custom annotation in the Bean validation documentation Constraint:
Each constraint annotation must host the following attributes:
String message() default [...]; which should default to an error
message key made of the fully-qualified class name of the constraint
followed by .message. For example
"{com.acme.constraints.NotSafe.message}"
Class<?>[] groups() default {}; for user to customize the targeted groups
Class<? extends Payload>[] payload() default {}; for extensibility purposes
So our custom annotation needs to have at least these three attributes, and we're interested primarily in the first one, providing a message which would be used in case if an exception occurs. Let's our annotation:
#Constraint(validatedBy = EngineTypeValidator.class)
#Documented
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface EngineTypeConstraint {
String message() default "Engine type does not exist";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator should implement generic interface ConstraintValidator, specifying the associated annotation and the validated type as its parameters.
That's how the implementation might look like:
public class EngineTypeValidator implements ConstraintValidator<EngineTypeConstraint, String> {
private static final Set<String> TYPE_NAMES =
EnumSet.allOf(EngineType.class).stream().map(Enum::name).collect(Collectors.toSet());
#Override
public boolean isValid(String engineType,
ConstraintValidatorContext constraintValidatorContext) {
if (!TYPE_NAMES.contains(engineType)) throw new EngineTypeNotValidException(engineType);
return true;
}
}
As you have probably noticed, validator throws a custom exception EngineTypeNotValidException which receives an invalid engine model as a parameter. We need this exception in order to build a response based on it.
public class EngineTypeNotValidException extends RuntimeException {
public EngineTypeNotValidException(String type) {
super(String.format("Engine type '%s' doesn't exist.", type));
}
}
To handle this exception and produces an error-response by using Controller Advice. For that, we need to define a class annotated with #ControllerAdvice and create a method marked with #ExceptionHandler to target the custom exception defined above.
#ControllerAdvice(assignableTypes = MyDetailsController.class)
public class ValidationExceptionHandler {
#ExceptionHandler(EngineTypeNotValidException.class)
public ResponseEntity<ErrorResponse> handleInvalidEngine(RuntimeException e) {
Throwable cause = e.getCause();
return ResponseEntity
.badRequest()
.body(new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
List.of(cause.getMessage())
));
}
}
Finally, to customize the response body, we can create a POJO with a couple of string field for representing the information of a failing response.
#AllArgsConstructor
#Getter
public class ErrorResponse {
private int status;
private String message;
private List<String> errors;
}
Demo
That's it now we can give it a try.
Consider the following dummy Controller:
#RestController
public class MyDetailsController {
#PostMapping("/newMyDetails")
public String newCar(#RequestBody #Valid MyDetailsDto dto) {
// some business logic
return "is valid";
}
}
And here's a couple screenshots from Postman with responses:
Valid request (hard-coded message from the Controller in the response):
Invalid request (response with a customized error message, prompting that enum-name provided in the request doesn't exist):
My group is planning to use Apollo Gateway for federation. Therefore, we need to produce our schemas a little bit differently.
Can we produce something like this with using your amazing lib?
extend type User #key(fields: "id") {
id: ID! #external
reviews: [Review]
}
You want to add some fields and directives to a type?
You can use #GraphQLContext to attach external methods as fields. Or even provide a custom ResolverBuilder that returns additional Resolvers (these later get mapped to fields).
To add directives, you can create annotations meta-annotated with #GraphQLDirective (see the tests for examples).
Lastly, you can of course provide a custom TypeMapper for User and fully take control of how that type gets mapped.
E.g. you can make an annotation like:
#GraphQLDirective(locations = OBJECT)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Key {
public String[] fields;
}
If you then place this annotation on a type:
#Key(fields = "id")
public class User {
#External //another custom annotation
public #GraphQLId #GraphQLNonNull String getId() {...}
}
it will get mapped as:
type User #key(fields: "id") {
id: ID! #external
}
I presume you know about #GraphQLContext, but in short:
//Some service class registered with GraphQLSchemaBuilder
#GraphQLApi
public class UserService {
#GraphQLQuery
public List<Review> getReviews(#GraphQLContext User user) {
return ...; //somehow get the review for this user
}
}
Because of #GraphQLContext, the type User now has a review: [Review] field (even though the User class does not have that field).
The following is just an exmaple for context not the actual implementation. Using Spring MVC, I have the following Model which has validation based on annotations.
#Entity
public class Customer {
#Id private int id;
#NotNull private String name;
}
And the following DTO used to map the data received in the request's body in the Controller's createNewCustomer function.
public class CustmerDTO{
private String name;
}
In my Controller I am using the modelMapper to convert the customerDTO to a new domain model Customer object. Based on the #NotNull annotation, if the name property of the recieved object (customerDTO) is empty the ConstraintViolationException is thrown.
public class CustomerController {
#Autowired private CustomerService customerService;
#Autowired private ModelMapper modelMapper;
#PostMapping(value = "/customer")
public Customer createNewCustomer (#RequestBody CustomerDTO customerDTO) {
try {
Customer newCustomer = modelMapper.map(customerDTO, Customer.class);
return customerService.saveCustomer(newCustomer);
}
catch (ConstraintViolationException e) {
throw new CustomerMissingInformation();
}
}
}
As you can see here I handle validation for the Customer in the Controller, which by definition is not a good habit, as Controllers in MVC are part of the presentation layer, and should have no clue about how to perform data validation, also I want to keep my controllers as light as possible.
Can I keep this design or is there any good approach to move my validation in the Service layer while keeping validation annotations and also allowing the Controller to be able to receive representation objects(DTOs) and convert them to domain models?
Looking more into this, I came to the following conclusion. Validation for persisting an object should happen as soon as possible and at property level, as you don't want your persistence layer or any mappers that you might be using deal with null when it is not expected. Everything that goes past property validation, shall be approached from a contextual validation perspective, and be passed to methods that hold logic for a given context.
Is this order valid to be filled, is this customer valid to check in to the hotel. So rather than have methods like isValid have methods like isValidForCheckIn.
I worked out a concept to conditionally validate using JSR 303 groups. "Conditionally" means that I have some fields which are only relevant if another field has a specific value.
Example: There is an option to select whether to register as a person or as a company. When selecting company, the user has to fill a field containing the name of the company.
Now I thought I use groups for that:
class RegisterForm
{
public interface BasicCheck {}
public interface UserCheck {}
public interface CompanyCheck {}
#NotNull(groups = BasicCheck.class)
private Boolean isCompany
#NotNull(groups = UserCheck.class)
private String firstName;
#NotNull(groups = UserCheck.class)
private String lastName;
#NotNull(groups = CompanyCheck.class)
private String companyName;
// getters / setters ...
}
In my controller, I validate step by step depending on the respective selection:
#Autowired
SmartValidator validator;
public void onRequest(#ModelAttribute("registerForm") RegisterForm registerForm, BindingResult result)
{
validator.validate(registerForm, result, RegisterForm.BasicCheck.class);
if (result.hasErrors()
return;
// basic check successful => we can process fields which are covered by this check
if (registerForm.getIsCompany())
{
validator.validate(registerForm, result, RegisterForm.CompanyCheck.class)
}
else
{
validator.validate(registerForm, result, RegisterForm.UserCheck.class);
}
if (!result.hasErrors())
{
// process registration
}
}
I only want to validate what must be validated. If the user selects "company" fills a field with invalid content and then switches back to "user", the invalid company related content must be ignored by the validator. A solution would be to clear those fields using Javascript, but I also want my forms to work with javascript disabled. This is why I totally like the approach shown above.
But Spring breaks this idea due to data binding. Before validation starts, Spring binds the data to registerForm. It adds error to result if, for instance, types are incompatible (expected int-value, but user filled the form with letters). This is a problem as these errors are shown in the JSP-view by <form:errors /> tags
Now I found a way to prevent Spring from adding those errors to the binding result by implementing a custom BindingErrorProcessor. If a field contains null I know that there was a validation error. In my concept null is not allowed - every field gets annotated with #NotNull plus the respective validation group.
As I am new to Spring and JSR-303 I wonder, whether I am totally on the wrong path. The fact that I have to implement a couple of things on my own makes me uncertain. Is this a clean solution? Is there a better solution for the same problem, as I think this is a common problem?
EDIT
Please see my answer here if you are interested in my solution in detail: https://stackoverflow.com/a/30500985/395879
You are correct that Spring MVC is a bit picky in this regard,and it is a common problem. But there are work-arounds:
Make all your backing fields strings, and do number/date etc conversions and null checks manually.
Use JavaScript to set fields to null when they become irrelevant.
Use JavaScript to validate fields when they are entered. This will fix almost all of your problems.
Good luck!
I know this question is old, but I came upon it looking for an answer for a different situation.
I think for your situation you could use inheritance for the forms and then use two controller methods:
The forms would look like this:
public class RegistrationForm
{
// Common fields go here.
}
public class UserRegistrationForm
extends RegistrationForm
{
#NotNull
private String firstName;
#NotNull
private String lastName;
// getters / setters ...
}
public class CompanyRegistrationForm
extends RegistrationForm
{
#NotNull
private String companyName;
// getters / setters ...
}
The controller methods would look like this:
#RequestMapping(method = RequestMethod.POST, params = "isCompany=false")
public void onRequest(
#ModelAttribute("registerForm") #Valid UserRegistrationForm form,
BindingResult result)
{
if (!result.hasErrors())
{
// process registration
}
}
#RequestMapping(method = RequestMethod.POST, params = "isCompany=true")
public void onRequest(
#ModelAttribute("registerForm") #Valid CompanyRegistrationForm form,
BindingResult result)
{
if (!result.hasErrors())
{
// process registration
}
}
Notice that the #RequestMapping annotations include a params attribute so the value of the isCompany parameter determines which method is called.
Also notice that the #Valid annotation is place on the form parameter.
Finally, no groups are needed in this case.