route request to a different controller - spring

I have a controller that has read function and handles urls like this
-> /{id}/EnumElement
class FirstController {
Object read(#PathVariable UUID id, #PathVariable EnumeratedEntity value,
HttpServletRequest request)
}
I want to add the second controller that would handle only a single request
class SecondController {
-> /{id}/metadata
Object meta(#PathVariable UUID id, HttpServletRequest request)
}
I also have a controller advice that supposed to handle EnumeratedEntity values
#RestControllerAdvice
public class DefaultControllerAdvice {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(EnumeratedEntity.class, new PropertyEditorSupport() {
#Override
public void setAsText(final String text) {
try {
setValue(EnumeratedEntity.valueOf(text));
} catch (IllegalArgumentException e) {
throw new UnknownResourceException("The requested resource is not supported", e);
}
}
});
}
The problem is that each time I request /metadata the advicer is trying to get the value of metadata (that does not exist) and throws an error. Also it seems like it takes first controller as a priority or something.
Is there a way to route /metadata request to a second controller and ignore advicer altogether?

After some search I decided to go with this solution for the first controller
#GetMapping(/{id}/{enumElement:^(?!metadata).*})
Object read(#PathVariable UUID id, #PathVariable EnumeratedEntity value,
HttpServletRequest request)

Related

How to reject the request and send custom message if extra params present in Spring boot REST Api

#PostMapping()
public ResponseEntity<ApiResponse> createContact(
#RequestBody ContactRequest contactRequest) throws IOException {
}
How to reject the API request, if extra params present in the request, by default spring boot ignoring extra parameters.
I believe you can add an HttpServletRequest as a parameter to the controller method (createContact in this case). Then you'll get access to all the parameters that come with the requests (query params, headers, etc.):
#PostMapping
public ResponseEntity<ApiResponse> createContact(HttpServletRequest request,
#RequestBody ContactRequest contactRequest) throws IOException {
boolean isValid = ...// validate for extra parameters
if(!isValid) {
// "reject the request" as you call it...
}
}
First add an additional parameter to the method. This gives you access to information about the request. If Spring sees this parameter then it provides it.
#PostMapping()
public ResponseEntity<ApiResponse> createContact(
#RequestBody ContactRequest contactRequest,
WebRequest webRequest) throws IOException {
if (reportUnknownParameters(webRequest) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
I do something like this to get the bad request into the log.
private boolean reportUnknownParameters(WebRequest webRequest) {
LongAdder unknownCount = new LongAdder();
webRequest.getParameterMap().keySet().stream()
.filter(key -> !knownParameters.contains(key))
.forEach(key -> {
unknownCount.increment();
log.trace("unknown request parameter \"{}\"=\"{}\"", key, webRequest.getParameter(key));});
return unknownCount.longValue() > 0;
}
add #RequestParam annotation in your methods parameter list and add it as a map, then you can access for it's key list and check if it contains anything else other than your required params.
public ResponseEntity<ApiResponse> createContact(#RequestParam Map<String,String> requestParams, #RequestBody ContactRequest contactRequest) throws IOException {
//Check for requestParams maps keyList and then pass accordingly.
}

How can I make sure exceptions during parsing lead to the same kind of response as the (custom) response returned for validation failures?

I'm using Spring to create an API, but I'm having some trouble introducing custom error reporting on (a part of) the validation of the request body.
When parsing/validation errors occur, I want to give a custom response back to the user.
This works well for fields annotated with #Valid along with validators like #javax.validation.constraints.NotNull by using a custom ResponseEntityExceptionHandler annotated with #ControllerAdvice.
It does not work however if an Exception is thrown while parsing the request body (before the validations even run). In that case I get an html error page with status 500 (Server Error)
How can I make sure the exceptions during parsing lead to the same kind of response as the (custom) one I return for validation failures?
My endpoint's code looks like this:
#RequestMapping(value= "/endpoint"
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(#Valid #RequestBody MyRequestBody requestData){
// ...
}
MyRequestBody class looks like this:
#Validated
public class MyRequestData {
#JsonProperty("stringValue")
private String stringValue = null;
#NotNull
#Valid
public String getStringValue() {
return stringValue;
}
// ...
public enum EnumValueEnum {
VALUE_1("value 1"),
VALUE_1("value 2");
private String value;
EnumValueEnum(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static EnumValueEnum fromValue(String text) {
if(text == null){
return null;
}
for (EnumValueEnum b : EnumValueEnum.values()){
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
}
}
#JsonProperty("enumValue")
private EnumValueEnum enumValue = null;
}
The custom validation error handling (and reporting) looks like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
}
}
In this code, if a user sends a request with an enum value that doesn't exist, an HttpMessageNotReadableException is thrown. I would like to catch that somewhere and replace it with a custom response that is consistent with the other exception handling I do. Where/How can I do that?
I found a solution to my own problem.
You can actually use Spring MVC's normal exception handling:
Annotating a method with #ExceptionHandler will make Spring try to use it for exception handling for the exception type specified (in the annotation's value field or the method's argument). This method can be placed in the controller or even in the ResponseEntityExceptionHandler I use for the other validation response handling.
#ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}
Mind which type of exception you handle:
The catch here was that the exception thrown while parsing is wrapped in (some subtype of) a JsonMappingException which in turn is wrapped again in a HttpMessageConversionException.
e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception
The #ExceptionHandler should therefor accept HttpMessageConversionException instead of the originally thrown exception (which in my case was HttpMessageNotReadableException)
It will not work if you write an #ExceptionHandler that only accepts your original Exception!

Handling Form Validation Result in ErrorHandler

I use spring-boot as a backend server. It has tens of Action Methods. As usual Some of them contains validation. Actually I use BindingResult and returns validation error for returning Http 400 Status.
#CrossOrigin
#RestController
public class ValidationTestController {
#RequestMapping(value = {"/validation-test", "/validation-test/"}, method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> login(#RequestBody #Valid final TestData data, final BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<>("Sorry incoming data is not valid!", HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("OK!", HttpStatus.OK);
}
private static final class TestData {
#NotNull
private String value;
}
}
My aim is removing follpwing lines:
if (result.hasErrors()) {
return new ResponseEntity<>("Sorry incoming data is not valid!", HttpStatus.BAD_REQUEST);
}
IMHO it's a cross cutting concern like Authentication and Auditing. I want to handle it in a one global ErrorHandler Method. It's possible to throw a CustomValidationException Before executing the method. So I can handle the exception in ErrorController.
Yes, you can centralize the exception handling logic at one place, using #ExceptionHandler which is a ControllerAdvice from Spring.
You can look at here

How to get the HTTP Request body content in a Spring Boot Filter?

I want to get the raw content that is posted towards a RestController. I need it to do some processing on the raw input.
How can I get the raw body content without interfering with the Filter Chain?
Here is a sample of controllerAdvice where you can access RequestBody and RequestHeader as you do in your controller. The Model attribute method is basically to add model attributes which are used across all pages or controller flow. It gets invoked before the controller methods kick in. It provides cleaner way of accessing the RESTful features rather than convoluted way.
#ControllerAdvice(annotations = RestController.class)
public class ControllerAdvisor {
#ModelAttribute
public void addAttributes(HttpServletRequest request, HttpServletResponse response,Model model, #RequestBody String requestString, #RequestHeader(value = "User-Agent") String userAgent) {
// do whatever you want to do on the request body and header.
// with request object you can get the request method and request path etc.
System.out.println("requestString" + requestString);
System.out.println("userAgent" + userAgent);
model.addAttribute("attr1", "value1");
model.addAttribute("attr2", "value2");
}
}
I use #ModelAttribute method to set value from #RequestBody.
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler
{
public CustomRestExceptionHandler() {
super();
}
private Object request;
#ModelAttribute
public void setRequest(#RequestBody Object request) {
this.request = request;
}
#Override protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.info(this.request)
}
}

Why isn't my Spring Model getting populated with the proper attributes?

I'm using Spring 3.1.0.RELEASE. For some reason, in my controller, when I POST my form and return the original screen when an error occurs, model attributes are not getting populated like they do when I invoke the page through a GET method. In my controller I have
#Controller
public class StandardsUploadController {
…
#RequestMapping(value = "/upload")
public String getUploadForm(Model model) {
model.addAttribute(new StandardsUploadItem());
model.addAttribute("gradeList", gradeList);
model.addAttribute("subjectList", subjectList);
return "upload/index";
}
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public ModelAndView processFile(final StandardsUploadItem uploadItem,
final BindingResult result,
final HttpServletRequest request,
final HttpServletResponse response) throws InvalidFormatException, CreateException, NamingException {
stdsUploadValidator.validate(uploadItem, result);
if (!result.hasErrors()) {
try {
…
} catch (IOException e) {
LOG.error(e.getMessage(), e);
e.printStackTrace();
}
} // if
return new ModelAndView("upload/index");
}
What am I doing wrong and how can I correct it?
When you return to the upload/index view from the POST, it is not re-populating the Model, since your code to populate the model is done on the GET.
One potential option is to use the #ModelAttribute annotation in your Controller class
You would have, for example, a method that looks like this for the StandardsUploadItem:
#ModelAttribute("uploadItem")
public StandardsUploadItem getStandardsUploadItem(){
return new StandardsUploadItem();
}
You could then remove the line below from your GET method:
model.addAttribute(new StandardsUploadItem());
Since a method annotated with #ModelAttribute and returning an object will automatically be put in the ModelMap, regardless of which Controller RequestMapping method is activated.
Your method signature for the POST method would contain something like this:
..., #ModelAttribute("uploadItem") StandardsUploadItem uploadItem, ...
You would need to do something similar for the other attributes in your model (gradeList, and subjectList), but since you do not seem to need them on the POST, you could do something like add a Model parameter to your POST method signature, and re-populate that Model before you return the ModelAndView in the error case.

Resources