I wrote a Request Interceptor to add some Information to Requests in Test-Environment.
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
...
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception {
...
}
Currently I'm retrieving the URLs like this:
String url = request.getServletPath();
For a Controller like this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET)
public ResponseEntity<?> getByID(#PathVariable long ID) {
...
}
And for a Request like /1/
url would be /1/
Is there any way to get the Request-Mapping-Value ==> /{id}
Thanks in advance
#RequestMapping and its composed annotation methods (i.e. #GetMapping , #PostMapping etc.) are handled by HandlerMethod. So cast the handler object to it and you can access the #RequestMapping information that you want:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
RequestMapping mapping = hm.getMethodAnnotation(RequestMapping.class);
if (mapping != null) {
for(String val : mapping.value()) {
//***This is the mapping value of #RequestMapping***
System.out.println(val);
}
}
}
}
Related
I have the following Controller
#RestController
#RequestMapping("/v4/base")
public class ExampleController {
#PostMapping(value = "/users/{userId}")
public ResponseEntity<List<ExampleRequest>> test(#RequestHeader HttpHeaders headers,
#PathVariable String orgId, #RequestBody List<ExampleRequest> request) {
...
}
}
I would like to extract this url from an interceptor
/v4/base/users/{userId}
With this approach,
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws IOException, ServletException {
RequestMapping requestMapping = method.getMethodAnnotation(RequestMapping.class);
if (requestMapping != null) {
String[] path = requestMapping.path();
}
}
}
It gives me this in the path string[] variable above:
/users/{userId}
How can I get the full spring request mapping path?
I do not want the servlet path that looks like this: /v4/base/users/23232
Found the solution. We can extract the controller annotation like this.
Class controller = method.getBeanType();
if (controller.isAnnotationPresent(RequestMapping.class)) {
RequestMapping controllerMapping = (RequestMapping) controller.getAnnotation(RequestMapping.class);
if (controllerMapping.value() != null && controllerMapping.value().length > 0) {
controllerPath = controllerMapping.value()[0];
}
}
And then prefix that to the method annotation.
Update: The only problem with this approach is that it's not able to extract the resolved annotation value like this one:
#RequestMapping(value = "${app.version.v4}")
Something like this will work as per your code.
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequestMapping methodAnnotation = handlerMethod.getMethodAnnotation(RequestMapping.class);
RequestMapping classAnnotation = handlerMethod.getBeanType().getAnnotation(RequestMapping.class);
if (methodAnnotation != null && classAnnotation != null) {
String[] classValues = classAnnotation.value();
String[] methodValues = methodAnnotation.value();
String fullPath = classValues[0] + methodValues[0];
// do something here`enter code here`
}
}
return true;
}
I have a spring boot app(v2.3.0.RELEASE) and I need to get any request being sent from my restcontroller when there is a bean validation error.
My Request is as follows:
public class PaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#NotBlank(message = "name.required")
private String name;
}
For instance, if name is null, I need an interceptor to capture values of transactionTypeCode and
paymentIdentification before exception is triggered.
I tried implementing the following interceptor to capture all not null parameters value being sent:
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception {
//capture required valued set it in HttpServletRequest attribute to be used for exception handling
HandlerMethod h1 = (HandlerMethod) handler;
MethodParameter[] param = null;
System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
}
When the name is null it enters the method preHandle but I am not able to get the parameters and its corresponding values being sent, any idea how to do it pls?
The reason why I am doing the above changes is because I need to set the value of transactionTypeCode and paymentIdentification in my interceptor above so as to use them below in my exception handler as follows:
#ControllerAdvice
public class RestControllerExceptionHandler extends ResponseEntityExceptionHandler {
private #Autowired
HttpServletRequest httpServletRequest;
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
HttpStatus status, WebRequest request) {
log.error(exception.getMessage(), exception);
// mapParam is size zero
Map<String, String[]> mapParam = httpServletRequest.getParameterMap();
if (!ObjectUtils.isEmpty(exception) && !ObjectUtils.isEmpty(this.request1)) {
paymentValidator.onErrorUpdatePayment(this.request1.getAttribute("transactionTypeCode"), this.request1.getAttribute("paymentIdentification "), exception.toString());
}
....
return new ResponseEntity<>(ipsResponse, new HttpHeaders(), ipsResponse.getHttpStatus());
}
I am wondering how to read response in filter from request body if #Controller method returns Callable interface.
My filter looks like this. Response is always empty. Any solution to this? Is this allowed only using AsyncListener?
#Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
#Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
#Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
The problem of Callable is that the dispatcher servlet itself starts async processing and the filter is exited before actually processing of a request.
When Callable arrives to dispatcher servlet, it frees container thread from pool by releasing all filters (filters basically finish their work). When Callable produces results, the dispatcher servlet is called again with the same request and the response is immidiately fulfilled by the data return from Callable. This is handled by request attribute of type AsyncTaskManager which holds some information about processing of async request. This can be tested with Filter and HandlerInterceptor. Filter is executed only once but HandlerInterceptor is executed twice (original request and the request after Callable completes its job)
When you need to read request and response, one of the solution is to rewrite dispatcherServlet like this:
#Bean
#Primary
public DispatcherServlet dispatcherServlet(WebApplicationContext context) {
return new DispatcherServlet(context) {
#Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.service(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
};
}
This way you ensure that you can read request and response multiple times. Other thing is to add HandlerInterceptor like this (you have to pass some data as request attribute):
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData == null) {
request.setAttribute(LOGGER_FILTER_ATTRIBUTE, new AsyncRequestData(request));
}
return true;
}
#Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex
) throws Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData != null && response instanceof ContentCachingResponseWrapper) {
log(request, (ContentCachingResponseWrapper) response, (AsyncRequestData) asyncRequestData);
}
}
afterCompletion method is called only once after async request has been completely processed. preHandle is called exactly twice so you have to check existance of your attribute. In afterCompletion, the response from the call is already present and if you do want to replace it, you should call response.resetBuffer().
This is one possible solution and there could be better ways.
I'm new to spring mvc , I'm working on a web project admin panel.
Here is some example of my admin pages controllers :
#Controller
#RequestMapping("/admin/article/**")
public class ArticleController {
private ArticleDao articleDao;
private String fileName;
private String baseUrl;
public ArticleController() {
articleDao = ArticleDaoFactory.create();
}
#RequestMapping(value = "/admin/article",method = RequestMethod.GET)
public String doGet(ModelMap model,HttpServletRequest request,ArticleForm articleForm) {
//some codes
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.GET)
public String doGetAdd(ModelMap model,ArticleForm articleForm) {
model.addAttribute("article", articleForm);
return "admin/articleAdd";
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.POST)
public String doPost(#ModelAttribute ArticleForm article, BindingResult result ,ModelMap model){
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.GET)
public String getEdit(ModelMap model, #PathVariable("id") int id) {
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.POST)
public String postEdit(ModelMap model, #PathVariable("id") int id, ArticleForm article, BindingResult result) {
//some codes
}
#RequestMapping(value = "/admin/article/delete/{id}",method = RequestMethod.GET)
public void getDelete(ModelMap model, #PathVariable("id") int id, HttpServletResponse response) {
//some codes
}
}
now I need another mapping in another contoller named AdminController (for example) to Authenticate admin and bring him to login page if he is not logged in. for sure Authenthication is one example, I might want to use more classes on every admin page.
Note that my authenthication class needs request and session references (and for sure my other classes will need other references created by spring)
I got to know that I can not get HttpServletRequest and ... using a constructor method so I wrote another request mapping to call a method.
Eventhough I can set my properties this way ,but I can not use this method on every admin url.
#Controller
#RequestMapping(value = "/admin/**",method = RequestMethod.GET)
public class AdminController {
Authentication authentication;
HttpServletRequest request;
HttpSession session;
HttpServletResponse response;
public void checkAndSet(HttpSession session,HttpServletRequest request,HttpServletResponse response) {
authentication = new Authentication(session,request);
this.request = request;
this.session = session;
this.response = response;
if(!authentication.isLoggedIn()){
System.out.println(" I'm not logged in");
response.setHeader("Location","/admin/login");
}
}
So I need some suggestion on how to write a request mapping in a controller to call a method on every other controllers that are 'admin' page child ?
FYI : I'm not thinking for spring security for this.
thanks;
I think you can do it by implementing a servlet filter.
For example :
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getServletPath();
HttpSession session = request.getSession(false);
Authentication authentication = new Authentication(session,request);
if (isAdminUrl(url) && !authentication.isLoggedIn()) {
res.sendRedirect/admin/login");
}
chain.doFilter(req, res);
}
}
And then, you have to implement the method isAdminUrl(String url) to determine if you want to apply your filter.
Otherwise, I strongly recommend you to take a look at Spring Security
I am using Dropwizard and I would like to wrap a request object from within a filter, and gain access to that instance from my resource class
e.g. do the following
Filter.java
public class ServiceRequestExtractionFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
ProxyResourceRequest requestWrapper = new RequestWrapper(httpRequest, svcRequestData);
chain.doFilter(requestWrapper, response);
}
}
...
Resource.java
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public class DemoResource {
#GET
#Timed
public Response get(#Context UriInfo uriInfo, #Context RequestWrapper request) {
...
this doesn't work nor do I have any reason to expect it to, but it seemed like a good way to explain what I was trying to do.
Try this ContainerFilter documented here: http://dropwizard.io/manual/core.html#jersey-filters
public class DateNotSpecifiedFilter implements ContainerRequestFilter {
#Context ExtendedUriInfo extendedUriInfo;
#Override
public ContainerRequest filter(ContainerRequest request) {
boolean methodNeedsDateHeader = extendedUriInfo.getMatchedMethod().isAnnotationPresent(DateRequired.class);
String dateHeader = request.getHeaderValue(HttpHeaders.DATE);
if (methodNeedsDateHeader && dateHeader == null) {
Exception cause = new IllegalArgumentException("Date Header was not specified");
throw new WebApplicationException(cause, Response.Status.BAD_REQUEST);
} else {
return request;
}
}
}
Just add this in you application run():
environment.jersey().getResourceConfig().getContainerRequestFilters().add(new DateNotSpecifiedFilter());