How to override BroadleafCategoryController [Broadleaf Commerce] - spring-boot

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);
}
}

Related

spring boot interceptor for specific api, should not be invoked for all the api's

2 api's were exposed in a spring boot controller class. I have a requirement to intercept only 1 api and SHOULD NOT intercept other api. Can someone assist how to do this?
Below is the code
public class HeaderValidationInterceptor extends HandlerInterceptorAdapter{
private static final Logger logger = Logger.getLogger(HeaderValidationInterceptor.class);
//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
validateHeaderParam(request);
request.setAttribute("startTime", startTime);
return true;
}
}
Also I have a configuration class to add interceptor as below
Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
HeaderValidationInterceptor headerValidationInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(headerValidationInterceptor)
}
}
Controller class
#RestController
public class MyController {
#Autowired
private ICityService cityService;
#GetMapping(value = "/cities")
public List<City> getCities() {
List<City> cities = cityService.findAll();
return cities;
}
#GetMapping(value = "/cities/{cityId}")
public City getCityById(#PathVariable("cityId") String cityId) {
City city = cityService.findCityById(cityId);
return cities;
}
}
Inside your interceptor, you can check the request URI for the endpoint you want to intercept.
You can use a regular expression to match the URI. Following for /cities/{cityId} endpoint.
if (request.getRequestURI().matches("(\\/cities\\/[a-zA-Z0-9]+\\/?)")) {
validateHeaderParam(request);
request.setAttribute("startTime", startTime);
}
I'm not sure what is that want to do in your interceptor, but for your example you can do this inside your controller as well. Like this,
#GetMapping(value = "/cities/{cityId}")
public City getCityById(#PathVariable("cityId") String cityId, HttpServletRequest request) {
// Here you can use HttpServletRequest and do your validation
// validateHeaderParam(request);
// request.setAttribute("startTime", startTime);
City city = cityService.findCityById(cityId);
return cities;
}

Spring boot - Pass argument from interceptor to method in controller

For learning purposes, I have made a custom authentication system where I pass a token from the client to the server through the Authorization header.
In the server side, I'd like to know if it's possible to create in the interceptor, before the request reaches a method in the controller, an User object with the email from the token as a property, and then pass this user object to every request where I require it.
This what I'd like to get, as an example:
#RestController
public class HelloController {
#RequestMapping("/")
public String index(final User user) {
return user.getEmail();
}
}
public class User {
private String email;
}
Where user is an object that I created in the pre-interceptor using the request Authorization header and then I can pass, or not, to any method in the RestController.
Is this possible?
#Recommended solution
I would create a #Bean with #Scope request which would hold the user and then put the appropriate entity into that holder and then take from that holder inside the method.
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CurrentUser {
private User currentUser;
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
}
and then
#Component
public class MyInterceptor implements HandlerInterceptor {
private CurrentUser currentUser;
#Autowired
MyInterceptor(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.currentUser.setCurrentUser(new User("whatever"));
return true;
}
}
and in the Controller
#RestController
public class HelloController {
private CurrentUser currentUser;
#Autowired
HelloController(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#RequestMapping("/")
public String index() {
return currentUser.getCurrentUser().getEmail();
}
}
#Alternative solution
In case your object that you would like to have, only contains one field, you can just cheat on that and add that field to the HttpServletRequest parameters and just see the magic happen.
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//TRY ONE AT THE TIME: email OR user
//BOTH SHOULD WORK BUT SEPARATELY OF COURSE
request.setAttribute("email", "login#domain.com");
request.setAttribute("user", new User("login#domain.com"));
return true;
}
}
You can use a local thread context object as follows - which will be handling one parameter per request thread (thread safe):
public abstract class LoggedUserContext {
private static ThreadLocal<User> currentLoggedUser = new ThreadLocal<>();
public static void setCurrentLoggedUser(User loggedUser) {
if (currentLoggedUser == null) {
currentLoggedUser = new ThreadLocal<>();
}
currentLoggedUser.set(loggedUser);
}
public static User getCurrentLoggedUser() {
return currentLoggedUser != null ? currentLoggedUser.get() : null;
}
public static void clear() {
if (currentLoggedUser != null) {
currentLoggedUser.remove();
}
}
}
Then in the interceptor prehandle function:
LoggedUserContext.setCurrentLoggedUser(loggedUser);
And in the interceptor postHandler function:
LoggedUserContext.clear();
From any other place:
User loggedUser = LoggedUserContext.getCurrentLoggedUser();

How to get Request URL in Spring Boot

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

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";
}
}

Spring Data Rest : How to expose custom rest controller method in the HAL Browser

i have created a custom rest controller and I can access the API and get the result from the resource, the problem is, it doesn't appear in the HAL Browser.. how to expose this custom method in the HAL Browser? Thank You...
#RepositoryRestController
public class RevisionController {
protected static final Logger LOG = LoggerFactory
.getLogger(RevisionController.class);
private final DisciplineRepository repository;
Function<Revision<Integer, Discipline>, Discipline> functionDiscipline = new Function<Revision<Integer, Discipline>, Discipline>() {
#Override
public Discipline apply(Revision<Integer, Discipline> input) {
return (Discipline) input.getEntity();
}
};
#Inject
public RevisionController(DisciplineRepository repository) {
this.repository = repository;
}
#RequestMapping(method = RequestMethod.GET, value = "/disciplines/search/{id}/revisions")
public #ResponseBody ResponseEntity<?> getRevisions(
#PathVariable("id") Integer id) {
Revisions<Integer, Discipline> revisions = repository.findRevisions(id);
List<Discipline> disciplines = Lists.transform(revisions.getContent(),
functionDiscipline);
Resources<Discipline> resources = new Resources<Discipline>(disciplines);
resources.add(linkTo(
methodOn(RevisionController.class).getRevisions(id))
.withSelfRel());
return ResponseEntity.ok(resources);
}
}
Register a bean that implements a ResourceProcessor<RepositoryLinksResource> and you can add links to your custom controller to the root resource, and the HAL Browser will see it.
public class RootResourceProcessor implements ResourceProcessor<RepositoryLinksResource> {
#Override
public RepositoryLinksResource process(RepositoryLinksResource resource) {
resource.add(ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(RevisionController.class).getRevisions(null)).withRel("revisions"));
return resource;
}
}

Resources