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.
CodePudding user response:
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) { ... }
}
CodePudding user response:
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