Spring REST API Swagger UI #ModelAttribute request URL and parameter type - spring

i have some problems trying to make my swagger UI return what i want.
The problem is that i want to display the areaName as a path parameter type NOT a query in the Swagger UI. I can do that by using #PathVariable String "areaName".
BUT i want to validate the areaname in a seperate requestclass and now im trying to use #Valid #ModelAttribute instead. The problem with this is that Swagger gives me a boring request URL like:
/v1/areas/{areaName}/series?areaName=testarea&from=20151201
I want it to show the same way as when im using #PathVariable:
/v1/areas/testarea/series?from=20151201
I have tried playing around with the #ApiParam in the requestclass and even tried to hidden=true to keep a #PathVariable in the controller and just hide the #ApiParam in the requestclass to not get a duplicate of areaName in the Swagger UI but the hidden doesn't seem to work. Im using Swagger/SwaggerUI version 2.3.0.. Any ideas?
Requestclass:
public class AreaSeriesRequest {
#ApiParam(value = "Area selector, wich area to get series from.", required = true)
#EnergyAreas
private String areaName;
public String getAreaName() {
return AreaName;
}
public void setAreaName(String areaName) {
this.areaName = areaName;
}
Controller:
#RequestMapping(value = "/{areaName}/series", method = GET, produces = json)
#ResponseStatus(HttpStatus.OK)
public Page<GroupSeriesDto> getAreaSeriesPaginated(
//#PathVariable String areaName,
#Valid #ModelAttribute AreaSeriesRequest seriesRequest, BindingResult seriesResult,
#ModelAttribute PagingRequest pagingRequest,
Principal currentUser) {

So the way i worked around this was to still use the #PathVariable but instead of #Valid #ModelAttribute on the areaName i did a seperate validator for this parameter.
public class AreaValidator implements Validator {
private static final List<String> types = Arrays.asList(
"ALL",
"XX1",
"XX2",
"XX3",
"XX4"
);
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors e) {
String value = (String) target;
if (value == null || !types.contains(value.toUpperCase())) {
e.reject(String.format("Area '%s' does not exist", value));
}
}
And then used it in the controller like:
new AreaValidator().validate(areaName, seriesResult);
if (seriesResult.hasErrors())
throw new AreaNotFoundException(areaName);

Related

Can spring map POST parameters by a way other than #RequestBody

I am using #RestControllers with an application where all requests are POST requests ... As I learned from this post , you can't map individual post parameters to individual method arguments, rather you need to wrap all the parameters in an object and then use this object as a method parameter annotated with #RequestBody thus
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestParam(value="idNumber") String idNumber , #RequestParam(value="applicationId") String applicationId) {
return customerService.requestOTP(idNumber, applicationId);
will not work with a POST request of body {"idNumber":"345","applicationId":"64536"}
MY issue is that I have A LOT of POST requests , each with only one or two parameters, It will be tedious to create all these objects just to receive the requests inside ... so is there any other way similar to the way where get request parameters (URL parameters) are handled ?
Yes there are two ways -
first - the way you are doing just you need to do is append these parameter with url, no need to give them in body.
url will be like - baseurl+/requestotp?idNumber=123&applicationId=123
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestParam(value="idNumber") String idNumber , #RequestParam(value="applicationId") String applicationId) {
return customerService.requestOTP(idNumber, applicationId);
second- you can use map as follows
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestBody Map<String,Object> body) {
return customerService.requestOTP(body.get("idNumber").toString(), body.get("applicationId").toString());
I have change your code please check it
DTO Class
public class DTO1 {
private String idNumber;
private String applicationId;
public String getIdNumber() {
return idNumber;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
}
Rest Controller Method
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestBody DTO1 dto){
System.out.println(dto.getApplicationId()+" (------) "+dto.getIdNumber());
return "";
}
Request Type -- application/json
{"idNumber":"345","applicationId":"64536"}
OR
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestBody String dto){
System.out.println(dto);
return "";
}

Spring Boot - How can I pass custom values in HTTP Post api?

I'm new with Spring Boot and I have difficult to understand how can I pass data. For example:
I want pass those data to my server:
{
"code", 1,
"name": "C01"
}
So I have create always a custom Object with code and name as attributes to have this http post api?
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
Another solution I see that can be this but I can't pass numbers (int code), right?
#RequestMapping(value = "/new/{code}/{name}", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
Kind regards :)
You can pass code and name as PathVariables just like in your example:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
A PathVariable can be an int or a String or a long or a Date, according to the docs:
A #PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so.
You could also define a PathVariable of type Map<String, Object> like this:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("map") Map<String, Object> map) {
Integer code = (Integer) map.get("code");
String name = (String) map.get("name");
...
}
You could even use #RequestParam and supply the data in the form of URL query parameters.
So, there are numerous ways in which data can be passed to a Spring MVC controller (more details in the docs) but I think the convention for posting complex data (by "complex" I mean more than a single piece of state) is to define a request body which contains a serialised form of that complex state i.e. what you showed in the first example in your queston:
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
If this question is about RESTful best practice, since you are developing webservice for creating an Order object, this is how I would design it
Order.java
public class Order {
private Integer code;
private String name;
public Integer getCode() {
return code;
}
public void setCode(final Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
Controller
#RequestMapping(value = "/orders", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Order> createOrder(#Valid #RequestBody Order order){
...
}
Technically, you can do many things to achieve the same thing, but that will not be a RESTful service, it will be an RPC at best.

Spring REST Service Controller not being validate by #PathVariable and #Valid

#Controller
#EnableWebMvc
#Validated
public class ChildController extends ParentController<InterfaceController> implements InterfaceController{
#Override
#RequestMapping(value = "/map/{name}", produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
#ResponseStatus( HttpStatus.OK)
#ResponseBody
public List<Friends> getAllFriendsByName(
#Valid
#Size(max = 2, min = 1, message = "name should have between 1 and 10 characters")
#PathVariable("name") String name,
#RequestParam(value="pageSize", required=false) String pageSize,
#RequestParam(value="pageNumber", required=false) String pageNumber,
HttpServletRequest request) throws BasicException {
//Some logic over here;
return results;
}
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}
Hi, I am trying to do pretty basic validation for a spring request parameter but it just doesn't seem to call the Exception handler, could someone point me into the right direction
P.S. I keep getting NoHandlerFoundException
Spring doesn't support #PathVariable to be validated using #Valid. However, you can do custom validation in your handler method or if you insist on using #Valid then write a custom editor, convert your path variable value to an object, use JSR 303 bean validation and then use #Valid on that object. That might actually work.
Edit:
Here's a third approach. You can actually trick spring to treat your path variable as a model attribute and then validate it.
1. Write a custom validator for your path variable
2. Construct a #ModelAttribute for your path variable and then use #Validator (yes not #Valid as it doesn't let you specify a validator) on that model attribute.
#Component
public class NameValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
String name = (String) target;
if(!StringUtils.isValidName(name)) {
errors.reject("name.invalid.format");
}
}
}
#RequestMapping(value = "/path/{name}", method = RequestMethod.GET)
public List<Friend> getAllFriendsByName(#ModelAttribute("name") #Validated(NameValidator.class) String name) {
// your code
return friends;
}
#ModelAttribute("name")
private String nameAsModelAttribute(#PathVariable String name) {
return name;
}

How to validate Spring MVC #PathVariable values?

For a simple RESTful JSON api implemented in Spring MVC, can I use Bean Validation (JSR-303) to validate the path variables passed into the handler method?
For example:
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") String customerNumber) {
...
}
Here, I need to validate the customerNumber variables's length using Bean validation. Is this possible with Spring MVC v3.x.x? If not, what's the best approach for this type of validations?
Thanks.
Spring does not support #javax.validation.Valid on #PathVariable annotated parameters in handler methods. There was an Improvement request, but it is still unresolved.
Your best bet is to just do your custom validation in the handler method body or consider using org.springframework.validation.annotation.Validated as suggested in other answers.
You can use like this:
use org.springframework.validation.annotation.Validated to valid RequestParam or PathVariable.
*
* Variant of JSR-303's {#link javax.validation.Valid}, supporting the
* specification of validation groups. Designed for convenient use with
* Spring's JSR-303 support but not JSR-303 specific.
*
step.1 init ValidationConfig
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
return processor;
}
}
step.2 Add #Validated to your controller handler class, Like:
#RequestMapping(value = "poo/foo")
#Validated
public class FooController {
...
}
step.3 Add validators to your handler method:
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public ResponseEntity<Foo> delete(
#PathVariable("id") #Size(min = 1) #CustomerValidator int id) throws RestException {
// do something
return new ResponseEntity(HttpStatus.OK);
}
final step. Add exception resolver to your context:
#Component
public class BindExceptionResolver implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex.getClass().equals(BindException.class)) {
BindException exception = (BindException) ex;
List<FieldError> fieldErrors = exception.getFieldErrors();
return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
}
}
}
The solution is simple:
#GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(#PathVariable(value = "hash", required = false) String historyHash)
{
// Accepted requests: either "/" or "/{40 character long hash}"
}
And yes, PathVariables are ment to be validated, like any user input.
Instead of using #PathVariable, you can take advantage of Spring MVC ability to map path variables into a bean:
#RestController
#RequestMapping("/user")
public class UserController {
#GetMapping("/{id}")
public void get(#Valid GetDto dto) {
// dto.getId() is the path variable
}
}
And the bean contains the actual validation rules:
#Data
public class GetDto {
#Min(1) #Max(99)
private long id;
}
Make sure that your path variables ({id}) correspond to the bean fields (id);
#PathVariable is not meant to be validated in order to send back a readable message to the user. As principle a pathVariable should never be invalid. If a pathVariable is invalid the reason can be:
a bug generated a bad url (an href in jsp for example). No #Valid is
needed and no message is needed, just fix the code;
"the user" is manipulating the url.
Again, no #Valid is needed, no meaningful message to the user should
be given.
In both cases just leave an exception bubble up until it is catched by
the usual Spring ExceptionHandlers in order to generate a nice
error page or a meaningful json response indicating the error. In
order to get this result you can do some validation using custom editors.
Create a CustomerNumber class, possibly as immutable (implementing a CharSequence is not needed but allows you to use it basically as if it were a String)
public class CustomerNumber implements CharSequence {
private String customerNumber;
public CustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
#Override
public String toString() {
return customerNumber == null ? null : customerNumber.toString();
}
#Override
public int length() {
return customerNumber.length();
}
#Override
public char charAt(int index) {
return customerNumber.charAt(index);
}
#Override
public CharSequence subSequence(int start, int end) {
return customerNumber.subSequence(start, end);
}
#Override
public boolean equals(Object obj) {
return customerNumber.equals(obj);
}
#Override
public int hashCode() {
return customerNumber.hashCode();
}
}
Create an editor implementing your validation logic (in this case no whitespaces and fixed length, just as an example)
public class CustomerNumberEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
setValue(new CustomerNumber(text));
} else {
throw new IllegalArgumentException();
// you could also subclass and throw IllegalArgumentException
// in order to manage a more detailed error message
}
}
#Override
public String getAsText() {
return ((CustomerNumber) this.getValue()).toString();
}
}
Register the editor in the Controller
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
// ... other editors
}
Change the signature of your controller method accepting CustomerNumber instead of String (whatever your ResponseObject is ...)
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") CustomerNumber customerNumber) {
...
}
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
Path variable may not be linked with any bean in your system. What do you want to annotate with JSR-303 annotations?
To validate path variable you should use this approach Problem validating #PathVariable url on spring 3 mvc
Actually there is a very simple solution to this. Add or override the same controller method with its request mapping not having the placeholder for the path variable and throw ResponseStatusException from it. Code given below
#RequestMapping(value = "/number")
#ResponseBody
public ResponseObject searchByNumber() {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"customer number missing")
}

Server side Validation not working properly

I am configuring server side validation for my form.My problem is that when the control comes in the Areavalidator class
#Override
public boolean supports(Class<?> clazz) {
return Area.class.isAssignableFrom(clazz);
}
from the above method the control again back to the controller class and in the error set it shows zero error.My question is that why it is not entering in the method where I am doing my validation stuff.
#Override
public void validate(Object target, Errors errors) {
Area object = (Area)target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "areaName",
"label.areaNameRequired");
if(object.getAreaCode().length()==0)
{
{
errors.rejectValue("areaCode", "label.areaCode", null);
}
}
}
The code in my controller class for validation
#Autowired
private AreaValidator areaValidator;
#InitBinder("area")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(areaValidator);
}
#RequestMapping(value = "/saveGridArea", method = RequestMethod.POST)
public String saveCountry(#ModelAttribute #Valid Area area,ModelMap map,BindingResult error) {
if (error.hasErrors()) {
return "area";
}
It's because when you do #Valid, it is expected to have the corresponding BindingResult right next to the modelAttribute:
Here, your ModelMap is in between, making it impossible for the framework to associate the linked/associated errors to the modelAttribute .
You just need to change the order of your variable of the method.
Try this and it should work:
#RequestMapping(value = "/saveGridArea", method = RequestMethod.POST)
public String saveCountry(#ModelAttribute #Valid Area area,BindingResult error, ModelMap map){
...
}

Resources