spring REST RequestMethod how to Map a http LOCK and UNLOCK RequestMapping? - spring-boot

seems that this is the same as Custom HTTP Methods in Spring MVC
I need to implement a call with http method LOCK and UNLOCK.
currently spring's requestMethod only supports
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
how can I implement a #RestController method that is called if the spring app is called with LOCK and UNLOCK http directives?

You can do it using supported method ( GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE) call. Here is the way to call post method internally:
#Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
#Override
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
final RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
requestMappingHandlerAdapter.setSupportedMethods(
"LOCK", "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE"
); //here supported method
return requestMappingHandlerAdapter;
}
#Bean
DispatcherServlet dispatcherServlet() {
return new CustomHttpMethods();
}
}
Custom method handler class. Here call post method internally:
public class CustomHttpMethods extends DispatcherServlet {
#Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
if ("LOCK".equals(request.getMethod())) {
super.doPost(request, response);
} else {
super.service(request, response);
}
}
}
Now you do requestmapping below way:
#RequestMapping(value = "/custom")
ResponseEntity customHttpMethod(){
return ResponseEntity.ok().build();
}

Related

Get exception object in custom error controller

I am using spring boot and write a global exception handler use AbstractErrorController. How could i get an exception object in controller?
#Controller
public class MyCustomErrorController extends AbstractErrorController {
public MyCustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#RequestMapping("/error")
public void handleError(HttpServletRequest req, HttpServletResponse resp) {
Exception e = ...; // how to get exception here
log.error(e);
displayError(req, resp, e);
}
#Override
public String getErrorPath() {
return "/error";
}
}
You can get the exception from the HttpServletRequest as follows:
#Controller
public class MyCustomErrorController extends AbstractErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) {
Exception e = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
...
}
}
An handler intercepts an Exception generated or re-thrown by a controller. It doesn't have an endpoint because it usually does it for all the controllers in your application. The Handler instructs the application server to return a specific error when a specific Exception is thrown.
Here is an example:
#ControllerAdvice // Specialization of #Component for classes that declare #ExceptionHandler, #InitBinder, or #ModelAttribute methods to be shared across multiple #Controller classes.
public class ResourceNotFoundExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
ResponseEntity<Object> response = new ResponseEntity<>(error, headers, HttpStatus.NOT_FOUND);
return response;
}
}
In this example ApiError is a data structure that reports the error to the UI. What this code does is intercepting the Exception "ResourceNotFoundException", create an appropriate error Data transfer object, set the response HttpStatus and headers and return the error.
you can find a different example here: https://github.com/otrebor/springbootseed-openshift/blob/master/src/main/java/com/company/example/springbootseed/core/errorhandling/handlers/
Add Exception as an extra parameter to handleError()

How to intercept requests by handler method in Spring WebFlux

I've got following interceptor in Spring MVC that checks if user can access handler method:
class AccessInterceptor : HandlerInterceptorAdapter() {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any?): Boolean {
val auth: Auth =
(if (method.getAnnotation(Auth::class.java) != null) {
method.getAnnotation(Auth::class.java)
} else {
method.declaringClass.getAnnotation(Auth::class.java)
}) ?: return true
if (auth.value == AuthType.ALLOW) {
return true
}
val user = getUserFromRequest(request) // checks request for auth token
// and checking auth for out user in future.
return renderError(403, response)
In my Controller I do annotate methods, like this:
#GetMapping("/foo")
#Auth(AuthType.ALLOW)
fun doesntNeedAuth(...) { ... }
#GetMapping("/bar")
#Auth(AuthType.ADMIN)
fun adminMethod(...) { ... }
In case if user has wrong token or no permissions, error is being returned.
Is it possible to do this in Spring WebFlux with annotation-style controllers?
My implementation, w/o using toFuture().get() which is potentially blocking.
#Component
#ConditionalOnWebApplication(type = Type.REACTIVE)
public class QueryParameterValidationFilter implements WebFilter {
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#NonNull
#Override
public Mono<Void> filter(#NonNull ServerWebExchange exchange, #NonNull WebFilterChain chain) {
return handlerMapping.getHandler(exchange)
.doOnNext(handler -> validateParameters(handler, exchange))
.then(chain.filter(exchange));
}
private void validateParameters(Object handler, ServerWebExchange exchange) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Set<String> expectedQueryParams = Arrays.stream(handlerMethod.getMethodParameters())
.map(param -> param.getParameterAnnotation(RequestParam.class))
.filter(Objects::nonNull)
.map(RequestParam::name)
.collect(Collectors.toSet());
Set<String> actualQueryParams = exchange.getRequest().getQueryParams().keySet();
actualQueryParams.forEach(actual -> {
if (!expectedQueryParams.contains(actual)) {
throw new InvalidParameterException(ERR_MSG, actual);
}
});
}
}
}
To solve that problem I would most probably use:
A Spring Reactive Web WebFilter from the WebHandler API to intercept the incoming request
The RequestMappingHandlerMapping to retrieve the method which handles the current request
#Autowired
RequestMappingHandlerMapping requestMappingHandlerMapping;
...
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
...
HandlerMethod handler = (HandlerMethod) requestMappingHandlerMapping.getHandler(exchange).toProcessor().peek();
//your logic
}
#Component
class AuditWebFilter(
private val requestMapping: RequestMappingHandlerMapping
): WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
// if not to call - then exchange.attributes will be empty
// so little early initializate exchange.attributes by calling next line
requestMapping.getHandler(exchange)
val handlerFunction = exchange.attributes.get(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE) as HandlerMethod
val annotationMethod = handlerFunction.method.getAnnotation(MyAnnotation::class.java)
// annotationMethod proccesing here
}
}
In newer versions of Spring the .toProcessor() call is deprecated. What worked for me is to use .toFuture().get() instead:
if(requestMappingHandlerMapping.getHandler(exchange).toFuture().get() instanceof HandlerMethod handlerMethod) { ... }
Unfortunately this requires handling of checked exceptions so the code will be a bit less readable but at least not deprecated anymore.

Spring #CrossOrigin does not work with DELETE method

Spring #CrossOrigin annotation does not work with DELETE methods.
Example code (in Groovy):
#CrossOrigin
#RestController
#RequestMapping('/rest')
class SpringController {
#RequestMapping(value = '/{fileName}', RequestMethod.DELETE)
void deleteFile(#PathVariable fileName) {
// logic
}
}
For this code I get the exception:
XMLHttpRequest cannot load http://localhost:8080/rest/filename.txt. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:4200' is therefore not allowed
access. The response had HTTP status code 404.
Notes:
I tested it in Chrome 58 and Postman 4.10.7
According to https://spring.io/guides/gs/rest-service-cors/ by
default #CrossOrigin allows only GET, HEAD and POST cross-origin
requests. Although specifying #CrossOrigin(methods =
[RequestMethod.GET, RequestMethod.DELETE]) did not help
I omitted some code for brevity. Actual controller also has GET request by the same mapping, delete method has return type and produces JSON response, and other minor stuff that I don't think affects the issue.
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("your cross origin url")
.allowedOrigins("your cross origin host/url")
.allowedHeaders("Access-Control-Allow-Origin", "*")
.allowedHeaders("Access-Control-Allow-Headers", "Content-Type,x-requested-with").maxAge(20000)
.allowCredentials(false)
.allowedMethods("DELETE");
}
}
// in your controller
#RequestMapping(value = '/{fileName:.+}', RequestMethod.DELETE)
void deleteFile(#PathVariable fileName) {
// your custom logic
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS");
}
};
}

Create own class that transforms HTTP request to object in Spring?

I would like to create own class that will transform HTTP request and initializes object from this HTTP request in my Spring MVC application. I can create object by defining parameters in method but I need to do mapping in my own way and do it manually.
How can I do it with my own implementation that will pass to Spring and it will use it seamlessly?
Update1
Solution that kindly provided Bohuslav Burghardt doesn't work:
HTTP Status 500 - Request processing failed; nested exception is
java.lang.IllegalStateException: An Errors/BindingResult argument is
expected to be declared immediately after the model attribute, the
#RequestBody or the #RequestPart arguments to which they apply: public
java.lang.String
cz.deriva.derivis.api.oauth2.provider.controllers.OAuthController.authorize(api.oauth2.provider.domain.AuthorizationRequest,org.springframework.ui.Model,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Maybe I should mention that I use own validator:
public class RequestValidator {
public boolean supports(Class clazz) {
return AuthorizationRequest.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
AuthorizationRequest request = (AuthorizationRequest) obj;
if ("foobar".equals(request.getClientId())) {
e.reject("clientId", "nomatch");
}
}
}
and declaration of my method in controller (please not there is needed a validation - #Valid):
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
}
I have two configurations classes in my application.
#Configuration
#EnableAutoConfiguration
#EnableWebMvc
#PropertySource("classpath:/jdbc.properties")
public class ApplicationConfig {
}
and
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestArgumentResolver());
}
}
What is wrong?
Update 2
The problem is with param BindingResult result, when I remove it it works. But I need the result to process it when some errors occur.
If I understand your requirements correctly, you could implement custom HandlerMethodArgumentResolver for that purpose. See example below for implementation details:
Model object
public class AuthorizationRequestHolder {
#Valid
private AuthorizationRequest authorizationRequest;
private BindingResult bindingResult;
// Constructors, accessors omitted
}
Resolver
public class AuthorizationRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return AuthorizationRequestHolder.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// Map the authorization request
AuthorizationRequest authRequest = mapFromServletRequest(request);
AuthorizationRequestHolder authRequestHolder = new AuthorizationRequestHolder(authRequest);
// Validate the request
if (parameter.hasParameterAnnotation(Valid.class)) {
WebDataBinder binder = binderFactory.createBinder(webRequest, authRequestHolder, parameter.getParameterName());
binder.validate();
authRequestHolder.setBindingResult(binder.getBindingResult());
}
return authRequestHolder;
}
}
Configuration
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestMethodArgumentResolver());
}
}
Usage
#RequestMapping("/auth")
public void doSomething(#Valid AuthRequestHolder authRequestHolder) {
if (authRequestHolder.getBindingResult().hasErrors()) {
// Process errors
}
AuthorizationRequest authRequest = authRequestHolder.getAuthRequest();
// Do something with the authorization request
}
Edit: Updated answer with workaround to non-supported usage of #Valid with HandlerMethodArgumentResolver parameters.

How to setup default success status codes for mapped HttpMethods Spring MVC #RestControllers?

Hi there Spring Gurus,
Currently we are annotating every mapped #RestController method with it's own success #ResponseStatus. I wonder, if there is a possibility for a global setup, based on the mapped #RequestMapping method. On Success we'd like to have:
GET -> HttpStatus.OK
POST -> HttpStatus.CREATED
PUT -> HttpStatus.OK
PATCH -> HttpStatus.OK
DELETE -> HttpStatus.NO_CONTENT
If possible at all, what do we have to do?
Best regards,
Marius
You could do it using a Filter:
public class ResponseStatusConverterFilter implements javax.servlet.Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
chain.doFilter(request, response);
switch (request.getMethod()) {
case "GET": response.setStatus(200);
break;
case "POST": response.setStatus(201);
break;
...
}
}
And add filter in web.xml
<filter>
<filter-name>responseStatusConverterFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>responseStatusConverterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Just be careful with error handling because this could overwrite error response code. You should check response code before overwriting it (e.g check if it's some bad code like 500 or 404 and then do not set it to 200 or 201).
Another solution:
Create bean that extends HandlerInterceptorAdapter override
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView)
throws Exception
and add logic similar to my first solution.
Add bean definition to:
<mvc:interceptors>
<bean class="com.your.web.ResponseConverterInterceptor" />
</mvc:interceptors>
Addendum: You will need to wrap HttpServletResponse with your class that extends HttpServletResponseWrapper :
public class StatusConvertingServletResponse extends HttpServletResponseWrapper {
private int httpStatus;
public StatusConvertingServletResponse(HttpServletResponse response) {
super(response);
}
#Override
public void setStatus(int sc) {
httpStatus = sc;
super.setStatus(sc);
}
#Override
public int getStatus() {
return httpStatus;
}
}
And use it like:
StatusConvertingServletResponse res = new StatusConvertingServletResponse((HttpServletResponse)response);
chain.doFilter(request, res);
if (res.getStatus() == 200) {
res.setStatus(n);
}
Just use annotation on above your methods
#ResponseStatus(value=org.springframework.http.HttpStatus.OK)
#ResponseStatus(value=org.springframework.http.HttpStatus.CREATED)
#ResponseStatus(value=org.springframework.http.HttpStatus.OK)
#ResponseStatus(value=org.springframework.http.HttpStatus.OK)
#ResponseStatus(value=org.springframework.http.HttpStatus.NO_CONTENT)
You could extend the Spring #PostMapping and add the default #ResponseStatus i.e.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#PostMapping
#ResponseStatus(value = HttpStatus.CREATED)
public #interface CreatedPostMapping {
}
and then use that annotation instead of the spring ones e.g.
#CreatedPostMapping
public String create() {
return "blah";
}
Similarly for the other http verb annotations.

Resources