How to get Request URL in Spring Boot - spring

I need to submit request URL as a String parameter to a method
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void testItt(#RequestParam String requestParameter, #RequestURL String requestUrl) {
// Do something with requestUrl
}
How to submit Request URL correctly?
I tried request.getRequestURL().toString()
But I feel there must be a better way.

Never just grab the URL from the request. This is too easy! programming is supposed to be hard and when it's not hard, you MAKE it hard! :)
But you can retrieve the URL the way you show up above
So lets start off with an annotation that represents the value you want to retrieve
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestURL {
}
This will work as a way to inject the value you already have access to.
Next we need to create a class that can build the URL string
public class RequestUrlArgumentResolver
implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(RequestURL.class) != null;
}
#Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request
= (HttpServletRequest) nativeWebRequest.getNativeRequest();
//Nice and cozy at home surrounded by safety not obfuscation
return request.getRequestURL().toString();
}
}
Next thing we need to do is get the framework to recognize the handler for this annotation.
add the method below to your configuration (If your config does not implement WebMvcConfigurer you may need to implement this class or create a new config which does and include the new config)
...
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestUrlArgumentResolver());
}
...
Then finally we are back to your original request mapping and it should work as originally written
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void testItt(#RequestParam String requestParameter,
#RequestURL String requestUrl) {
// Do something with requestUrl
}
Credits - https://www.baeldung.com/spring-mvc-custom-data-binder

Related

Spring POST controller request body as controller variable

Usually, we get request body as a parameter of controller methods. Spring binds the body to the type of the variable.
I want the request body as a property of the controller so that other private methods can access it.
public class UserController {
private String body; // Request body should automatically bind to this String.
private HttpServletRequest request;
#Autowired
public UserController(HttpServletRequest request) {
this.request = request;
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> create(#RequestBody String body) {
// I know I can do like: this.body = body.
// But don't want to do that.
}
private someMethod() {
// Give me access of request body please....
}
Controllers are by default singleton scoped beans (created once per container initialisation). Assigning request body (which changes in every request) to something which is created once and can be used everywhere can lead you to serious trouble. If you are just looking to apply some logic in controller by using private method, you can pass the body as argument to that method like this.
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> create(#RequestBody String body) {
someMethod(body);
}
private void someMethod(String body) {
// voila! you have the request body here
}

How to override BroadleafCategoryController [Broadleaf Commerce]

When i try to override the BroadleafCategoryController handleRequest method everything works fine for me. But when i try to call the same method as show below with the Device parameter to identify the user device
In my Java Class CategoryController.java
#Controller("blCategoryController")
public class CategoryController extends BroadleafCategoryController {
private static final Logger logger = LoggerFactory.getLogger(CategoryController.class);
#RequestMapping("/")
public ModelAndView handleRequest(Device currentDevice, HttpServletRequest request, HttpServletResponse response) throws Exception {
return super.handleRequest(request, response);
}
}
then when it goes inside the super method (handleRequest) i get the Category Object value as NULL.
Category category = (Category) request.getAttribute(CategoryHandlerMapping.CURRENT_CATEGORY_ATTRIBUTE_NAME);
The above value should have been set through CategoryHandlerMapping
public class CategoryHandlerMapping extends BLCAbstractHandlerMapping {
public static final String CURRENT_CATEGORY_ATTRIBUTE_NAME = "category";
protected String defaultTemplateName = "catalog/category";
private String controllerName = "blCategoryController";
#Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
Category category = null;
if (allowCategoryResolutionUsingIdParam()) {
category = findCategoryUsingIdParam(request);
}
if (category == null) {
category = findCategoryUsingUrl(request);
}
if (category != null) {
request.setAttribute(CURRENT_CATEGORY_ATTRIBUTE_NAME, category);
return controllerName;
}
return null;
}
The CategoryHandlerMapping (provided by Broadleaf Commerce) has been configured inside a class annotated with #Configuration annotation and object is created inside a method annotated with #Bean annotation.
#Bean
public HandlerMapping categoryHandlerMapping() {
CategoryHandlerMapping mapping = new CategoryHandlerMapping();
mapping.setOrder(5);
return mapping;
}
Note:- All the configuration related to Device (Spring-Mobile) is fine
Please let me know if I am missing any xml config like applicationContext.xml ?
Thanks in advance !!!
The category controller is a little different, and is not resolved by the RequestMappingHandlerMapping (which is what resolves your #RequestMapping piece). Instead, as you mentioned, the only way the handleRequest() method of the CategoryController is invoked is via the CategoryHandlerMapping. That is the piece that is handling the category URL.
It looks like you are just trying to figure out the current Device on the category page. Spring Mobile has a facility to do this with simply DeviceUtils.getCurrentDevice(request). So:
Remove the #RequestMapping from handleRequest()
Rewrite your method like this:
#Controller("blCategoryController")
public class CategoryController extends BroadleafCategoryController {
private static final Logger logger = LoggerFactory.getLogger(CategoryController.class);
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
Device currentDevice = DeviceUtils.getCurrentDevice(request);
return super.handleRequest(request, response);
}
}

Spring: how to pass objects from filters to controllers

I'm trying to add a Filter that creates an object that is then to be used inside a controller in a Spring Boot application.
The idea is to use the Filter as a "centralized" generator of this object - that is request-specific and useful only in a controller.
I've tried to use the HttpServletRequest request.getSession().setAttribute method: I can access my object in the controller, but then it will be (clearly) added to the session.
Are the Filters the right way to do so? If yes, where can I keep the temporary object generated by the filter to be used by the controllers?
Why Don't you use a Bean with the #Scope('request')
#Component
#Scope(value="request", proxyMode= ScopedProxyMode.TARGET_CLASS)
class UserInfo {
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String password;
}
and then you can Autowireed this bean in both filter and controller to do setting and getting of data.
lifecycle of this UserInfo bean is only exisits within the request so once the http request is done then it terminates the instance as well
you can use ServletRequest.setAttribute(String name, Object o);
for example
#RestController
#EnableAutoConfiguration
public class App {
#RequestMapping("/")
public String index(HttpServletRequest httpServletRequest) {
return (String) httpServletRequest.getAttribute(MyFilter.passKey);
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Component
public static class MyFilter implements Filter {
public static String passKey = "passKey";
private static String passValue = "hello world";
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setAttribute(passKey, passValue);
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
}
An addition to wcong's answer.
Since Spring 4.3 after setting the attribute by using request.setAttribute(passKey, passValue);, you can access the attribute in your controller by simply annotating it with #RequestAttribute.
ex.
#RequestMapping("/")
public String index(#RequestAttribute passKey) {
return (String) passKey;
}
I dont know actually what is the scenario but If you really want to create an object in a filter and then use it somewhere in the code then you may use ThreadLocal class to do so.
To get know how this work see the most voted answer from that question Purpose of ThreadLocal?
In general using ThreadLocal you will be able to create a class that can store objects available ONLY for the current thread.
Sometimes for optimization reasons the same thread can be used to serve subsequent request as well so it will be nice to clean the threadLocal value after the request is processed.
class MyObjectStorage {
static private ThreadLocal threadLocal = new ThreadLocal<MyObject>();
static ThreadLocal<MyObject> getThreadLocal() {
return threadLocal;
}
}
in the filter
MyObjectStorage.getThreadLocal().set(myObject);
and in the Controller
MyObjectStorage.getThreadLocal().get();
Instead of filter you can use also #ControllerAdvice and pass objects to specified Controllers by using model.
#ControllerAdvice(assignableTypes={MyController.class})
class AddMyObjectAdvice {
// if you need request parameters
private #Inject HttpServletRequest request;
#ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("myObject", myObject);
}
}
#Controller
public class MyController{
#RequestMapping(value = "/anyMethod", method = RequestMethod.POST)
public String anyMethod(Model model) {
MyObjecte myObject = model.getAttribute("myObject");
return "result";
}
}

Mapping HTTP request value to object in Sprint Boot application when field and value has different field name?

This instance of class AuthorizationRequest is created during HTTP request, params are sent in query string.
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
I would like to use this code, this is an example parameter from AuthorizationRequest class:
#NotEmpty
#JsonProperty("client_id")
private String clientId;
but new instance has a filed clientId empty, because in query string there is a value for this parameter under client_id parameter.
Is there some way how to tell Spring which parameter from HTTP request should use for one particular field of created instance? I need to solve problem with different naming clientId andclient_id`.
What you need is a setter to handle each kind of clientId. Keep in mind that if both clientId and client_id is specified that it is unknown which will take precedence.
//These methods will allow clientId or client_id
to be used as arguments setting the same field this.clientId
public void setClient_id(String client_id) {
this.clientId = client_id;
}
public void setClientId(String client_id) {
this.clientId = client_id;
}
I tested this with a post and a get
get - http://localhost:8080/authorize?clientId=2&username=someusername
get - http://localhost:8080/authorize?client_id=2&username=someusername
post - http://localhost:8080/authorize
Content-Type: application/x-www-form-urlencoded
Body: clientId=2&username=someusername
or Body: client_id=2&username=someusername
I was only able to have #JsonProperty("client_id") to be recognized when I annotated AuthorizationRequest with #RequestBody and then used application/json instead of application/x-www-form-urlencoded
I found the solution with own implementation of org.springframework.web.method.supportHandlerMethodArgumentResolver.
Resolver implementation:
public class AuthorizationRequestResolver implements HandlerMethodArgumentResolver {
private static Logger LOG = Logger.getLogger(AuthorizationRequestResolver.class);
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AuthorizationRequest.class);
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
AuthorizationRequest authRequest = mapFromServletRequest(request);
return authRequest;
}
private AuthorizationRequest mapFromServletRequest(HttpServletRequest request) {
AuthorizationRequest authorizationRequest = new AuthorizationRequest();
authorizationRequest.setClientId(request.getParameter("client_id"));
authorizationRequest.setRedirectUri(request.getParameter("request_uri"));
authorizationRequest.setResponseType(request.getParameter("response_type"));
authorizationRequest.setScope(request.getParameter("scope"));
authorizationRequest.setState(request.getParameter("state"));
return authorizationRequest;
}
}
and cofiguration class:
#Configuration
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestResolver());
}
}

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.

Resources