Spring Boot - Database based request mapping - spring

Using Spring Boot 2 I want to create a database based request mapping. I mean, instead of using hundreds of #RequestMapping annotations on the controllers I would like to store the mapping in a database table.
Each of the controllers implements an interface that has an execute method so I simply search for the relevant controller in the DB and call the execute method on it.
At the moment I have a CustomController with a #RequestMapping("*") and this controller finds the real controller and calls the execute method. It works but it is not a good solution. For example at interceptor level the handler object is the CustomController and not the real controller.
Probably I should use the SimpleUrlHandlerMapping like this:
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
urlMap.put("/dashboard", __???__);
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
But in this case I don't know how to fill the bean value in the urlMap. For example in case of "/dashboard" how to put the DashboardController.execute().
Maybe any better solution?
UPDATE 1
I have created a SimpleUrlHandlerMapping like this:
#Configuration
public class SimpleUrlHandlerMappingConfig {
#Autowired
private ApplicationContext context;
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
String path = "/dashboard";
String controllerName = "dashboardController";
Object myController = context.getBean(controllerName);
urlMap.put(path, myController);
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
}
And a CustomHandlerAdapter as:
#Configuration
public class CustomHandlerAdapter implements HandlerAdapter {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public boolean supports(Object handler) {
logger.debug("Test handler: " + handler);
if (handler instanceof PageController) {
return true;
}
return false;
}
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("Custom handle");
ModelAndView mv = new ModelAndView();
String viewName = ((PageController)handler).execute2(request, response);
mv.setViewName(viewName);
return mv;
}
#Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
But according to logs it seems that SimpleUrlHandlerMapping doesn't work correctly:
- DispatcherServlet with name 'dispatcherServlet' processing GET request for [/dashboard]
- Looking up handler method for path /dashboard
- Did not find handler method for [/dashboard]
- Matching patterns for request [/dashboard] are [/**]
- URI Template variables for request [/dashboard] are {}
- Mapping [/dashboard] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#4bc6044e]]] and 1 interceptor
- Test handler: ResourceHttpRequestHandler [locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#4bc6044e]]
- Last-Modified value for [/dashboard] is: -1
UPDATE 2
Thanks to #M. Deinum I have updated my code and have a working solution.
Please note that #EnableWebMvc was introduced and that can cause other side effects later.
The SimpleUrlHandlerMappingConfig:
#Configuration()
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleUrlHandlerMappingConfig {
#Autowired
private ApplicationContext context;
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
String path = "/dashboard";
String controllerName = "dashboardController";
Object myController = context.getBean(controllerName);
urlMap.put(path, myController);
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
}
The CustomHandlerAdapter:
#Component
public class CustomHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(Object handler) {
if (handler instanceof PageController) {
return true;
}
return false;
}
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ModelAndView mv = new ModelAndView();
String viewName = ((PageController)handler).execute2(request, response);
mv.setViewName(viewName);
return mv;
}
#Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
And the WebConfig:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/style/**")
.addResourceLocations("classpath:" + "/static/style/");
registry.addResourceHandler("/js/**")
.addResourceLocations("classpath:" + "/static/js/");
}
}

If I understood correctly, you want to get rid of the simple actions (get/post/put/delete) -- and those only call the save/find/delete methods from the repository.
If that is the case I suggest using Spring Data REST

I post the final solution (thanks to M. Deinum) here maybe helping somebody else.
So I only created a HandlerMapping using the SimpleUrlHandlerMapping:
#Configuration()
public class SimpleUrlHandlerMappingConfig {
#Autowired
private ApplicationContext context;
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
String path = "/dashboard";
String controllerName = "dashboardController";
Object myController = context.getBean(controllerName);
urlMap.put(path, myController);
simpleUrlHandlerMapping.setUrlMap(urlMap);
simpleUrlHandlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return simpleUrlHandlerMapping;
}
}
And a custom HandlerAdapter:
#Component
public class CustomHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(Object handler) {
if (handler instanceof PageController) {
return true;
}
return false;
}
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ModelAndView mv = new ModelAndView();
String viewName = ((PageController)handler).execute2(request, response);
mv.setViewName(viewName);
return mv;
}
#Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
Please note that this example demonstrates only the concept without proper error handling and real DB access.

Related

Springboot exception handling when there is no controllers

I have a spring-boot application without any controller classes.
How can I write exception handlers for this application. Exception handler classes annotated with #ControllerAdvice doesn't work.
If you are developing web applications, ErrroController is available.
#Controller
#RequestMapping("${server.error.path:${error.path:/error}}")
public class MyErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
public MyErrorController(final ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return null;
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
final WebRequest webRequest = new ServletWebRequest(request);
final Throwable th = errorAttributes.getError(webRequest);
// ...
// see also: BasicErrorController implementation
}
}

Adding custom header to response in spring rest / spring boot

i am trying to send session id in response header in rest controller but excludepathpattern() seems not working
** the configuration class is not triggering **
i have tried changing the sevlet version but it didnt work
ContextListener
#Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
Map<String, HttpSession> map = new HashMap<>();
context.setAttribute("activeUsers", map);
HttpSessionListener
ServletContext context = session.getServletContext();
Map<String, HttpSession> activeUsers = (Map<String, HttpSession>) context.getAttribute("activeUsers");
activeUsers.put(session.getId(), session);
HandlerInterceptor
ServletContext context = request.getServletContext();
Map<String, HttpSession> activeUsers = (Map<String, HttpSession>) context.getAttribute("activeUsers");
String sessionId = request.getHeader("sessionId");
String requestUrl = request.getRequestURL().toString();
if (requestUrl.contains("/getOtp") || requestUrl.contains("/validateOtp")) {
return true;
} else {
if (activeUsers.containsKey(sessionId)) {
return true;
} else {
response.setStatus(401);
return false;
}
}
interceptorconfigurartion by extendig websecurityconfigure
#Configuration
#EnableAutoConfiguration
public class SessionInterceptorConfig implements WebMvcConfigurer {
#Autowired
private SessionHanlderInterceptor sessionHandlerIntercepto;
#Override
public void addInterceptors(InterceptorRegistry registry) {
// List<String> paths = new ArrayList<String>();
// paths.add("/auth/*");
registry.addInterceptor(sessionHandlerIntercepto).excludePathPatterns("/auth/**");
}
#Bean
public ServletListenerRegistrationBean<CustomSessionListener> filterRegistrationBean() {
ServletListenerRegistrationBean<CustomSessionListener> registrationBean = new ServletListenerRegistrationBean<CustomSessionListener>();
CustomSessionListener customURLFilter = new CustomSessionListener();
registrationBean.setListener(customURLFilter);
registrationBean.setOrder(1); // set precedence
return registrationBean;
}
#Bean
public ServletListenerRegistrationBean<CustomServletContextListener> filterContextRregistration() {
ServletListenerRegistrationBean<CustomServletContextListener> registrationBean = new ServletListenerRegistrationBean<CustomServletContextListener>();
CustomServletContextListener customURLFilter = new CustomServletContextListener();
registrationBean.setListener(customURLFilter);
registrationBean.setOrder(1); // set precedence
return registrationBean;
}
Sprinboot main class
#SpringBootApplication
public class CustomerApplication extends SpringBootServletInitializer {
i expect to add the session id to header in response and to check for the sessionid in request
You can use spring web component "OncePerRequestFilter". You need to inject a bean which extends OncePerRequestFilter. Example:
public class CustomHeaderFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
response.setHeader(customHeaderName, customHeaderValue);
chain.doFilter(request, response);
}
}

How to Use SimpleUrlHandlerMapping with SpringBoot

I am using SpringBoot and want to configure SimpleUrlHandlerMapping bean for my custom mapping. For that follow are the piece of code that I wrote.
#Configuration
public class WebConfiguration {
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
System.out.println("creating SimpleUrlHandlerMapping ....");
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
simpleUrlHandlerMapping.setOrder(0);
Properties urlProperties = new Properties();
urlProperties.put("/index", "myController");
simpleUrlHandlerMapping.setMappings(urlProperties);
return simpleUrlHandlerMapping;
}
}
I also have one Controller with name myController and its code looks like this.
#Controller("myController")
public class MyController extends AbstractController {
#Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
System.out.println("My Controller!");
return null;
}
}
Now as per code when http://localhost:7171//index is hit then it should print the My Controller message on console. But it not touch this code.
Because this is an SpringBoot application and on start it print this bean registration with myController.
Could someone help to resolve this issue and tell me whats wrong in this code.
Thanks in advance.
#Autowire Controller Bean in Configuration class and pass it through Properties
SimpleUrlHandlerMapping is the most flexible HandlerMapping implementation. It allows for direct and declarative mapping between either bean instances and URLs or between bean names and URLs.
Let’s map requests “/simpleUrlWelcome” and “/*/simpleUrlWelcome” to the “welcome” bean: here
#Configuration
public class WebConfiguration {
#Autowired
private indexController index;
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
System.out.println("creating SimpleUrlHandlerMapping ....");
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
simpleUrlHandlerMapping.setOrder(0);
Properties<String,Object> urlProperties = new Properties<>();
urlProperties.put("/index", index);
simpleUrlHandlerMapping.setMappings(urlProperties);
return simpleUrlHandlerMapping;
}
}
Controller
#Controller("index")
public class indexController extends AbstractController {
#Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
System.out.println("My Controller index!");
return null;
}
}

Propagating correlation-id not working

I have the problem that the correlation-id is not propagated from my first to the my second microservice. I started to implement a servlet filter, a context and a context-holder as follows:
#Component
// Do not name bean "RequestContextFilter", otherwise filter will not work!
public class CallContextFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;CallContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CallContext.CORRELATION_ID));
filterChain.doFilter(httpServletRequest, servletResponse);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
#Component
#Getter
#Setter
public class CallContext {
public static final String CORRELATION_ID = "correlation-id";
private String correlationId = new String();
}
public class CallContextHolder {
private static final ThreadLocal<CallContext> userContext = new ThreadLocal<>();
public static final CallContext getContext() {
CallContext context = userContext.get();
if (context == null) {
context = new CallContext();
userContext.set(context);
}
return userContext.get();
}
}
Then, I implemented a RestTemplate bean as follows:
#Bean
public RestTemplate getRestTemplate() {
RestTemplate template = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = template.getInterceptors();
interceptors.add(new CallContextInterceptor());
return template;
}
and the interceptor looks as follows:
public class CallContextInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add(CallContext.CORRELATION_ID, CallContextHolder.getContext().getCorrelationId());
return execution.execute(request, body);
}
}
When I call my endpoint, the servlet filter is executed and the correlation-id is stored in the CallContextHolder. So far, so good. However, the CallContextInterceptor seems to be called in an other thread and my ThreadLocal variable in the CallContextHolder is null. What I have to do to make this working?
#GetMapping("/ping")
public String ping() {
return pongRestTemplateClient.getPong();
}
Why not use Spring Cloud Sleuth and just let the libary do the work for you? http://cloud.spring.io/spring-cloud-sleuth/spring-cloud-sleuth.html
The problem was that I'm using Hysterix. Hystrix spawns a new thread to execute the code, completely unaware of the "outer" thread context. So, the executing thread losses access to the ThreadLocal dependant functionality when using Hysterix commands.
I found an answer to my problem here: https://github.com/jmnarloch/hystrix-context-spring-boot-starter

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