I have a HandlerInterceptor to add some "global" model variables. It works.
Now, I try to reuse it in Spring Web Flow, for the same reason.
But HandlerInterceptors have the ModelAndView parameter set to NULL under Spring Web Flow (couldn't figure why, but it's a fact).
I have referenced my interceptor in the FlowHandlerMapping bean :
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="order" value="0" />
<property name="flowRegistry" ref="flowRegistry" />
<property name="interceptors">
<list>
<ref bean="myInterceptor" />
</list>
</property>
</bean>
How can I add variables to the model ?
Is there a workaround, with the request parameter for example ?
Starting with Spring Webflow 2, the ModelAndView object is not generated anymore (see this post (and thread) at the SpringSource forum).
The FlowHandlerAdapter handle() function does not generate a ModedAndView anymore (it just returns null) even if this function is :
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler)
So overriding this function is pointless, but this function creates a ServletExternalContext object, which holds all the flow variable, by calling its method :
protected ServletExternalContext createServletExternalContext(
HttpServletRequest request, HttpServletResponse response)
By overriding this function you can pretty much do what you want with this flow variables.
To do this, just create a class that extends FlowHandlerAdapter, register it instead of FlowHandlerAdapter and override the createServletExternalContext function.
Basically you use ServletExternalContext.getSessionMap() to access a SharedAttributeMap and register your properties.
As you have access to the HttpServletRequest and HttpServletResponse objects, this method can act petty much like a HandlerInterceptorAdapter.postHandle function.
See an example below.
I left out how to use generic way to reuse the same code for a HandlerInterceptor for the MVC and this object but it's easy to code, by implementing HandlerInterceptor.
MyFlowHandlerAdapter :
package my.package;
public class MyFlowHandlerAdapter extends FlowHandlerAdapter {
#Override
protected ServletExternalContext createServletExternalContext(
HttpServletRequest request,
HttpServletResponse response) {
ServletExternalContext context =
super.createServletExternalContext(request,response);
context.getSessionMap().put("myproperty", "myvalue");
return context;
}
}
You have the FlowHandlerAdapter object defined in you webflow-context.xml file like that :
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>
Just replace it with :
<bean class="my.package.MyFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>
ModelAndView can be null when the interceptor is processing an Ajax request.
Just check if ModelAndView is null. If not, that's because the interceptor is processing a view-model, so you can add your variables at this time.
Related
I am trying to use Spring's ReloadableResourceBundleMessageSource for LocalValidatorFactoryBean so that when I update an error message it should reflect without requiring the server to be restarted. I am using Spring 4.1.4, hibernate-validator 4.3.2.Final.
Below are the code details -
context.xml -
<mvc:annotation-driven validator="validator" />
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>file:../conf/fileapplication</value> <!-- Messages here will override the below properties file-->
<value>/WEB-INF/application</value>
</list>
</property>
<property name="cacheSeconds" value="10"></property> <!-- Will check for refresh every 10 seconds -->
</bean>
<bean name="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource">
<ref bean="messageSource"/>
</property>
</bean>
Model -
import org.hibernate.validator.constraints.NotBlank;
public class InputForm {
#NotBlank ( message = "{required.string.blank}")
String requiredString;
Controller -
#RequestMapping(value = "/check/string", method = RequestMethod.POST)
public String checkString(
#ModelAttribute("formModel") #Valid InputForm inputForm ,
BindingResult result, Model model, HttpServletResponse response,
HttpServletRequest request) {
if (result.hasErrors()) {
model.addAttribute("formModel", inputForm);
return "userInput";
}
// Do some backend validation with String
result.reject("string.not.valid",
"String is Invalid");
model.addAttribute("formModel", inputForm);
return "userInput";
}
application.properties (in /WEB_INF/ folder)
required.string.blank=Please enter the required string.
string.not.valid=Please enter a valid string.
fileapplication.properties (in /conf/ folder. Will override above file)
required.string.blank=You did not enter the required string. #Does not reflect when I change here
string.not.valid=You did not enter a valid string. #Reflects when I change here
Now the problem I am facing is, when I update "string.not.valid" in fileapplication.properties it reflects at runtime and I see the updated message. But when I update "required.string.blank" in fileapplication.properties it does not reflect at runtime.
Note that the overriding part is working fine for both messages upon application start up. But the "reloading" part is not working fine for "required.string.blank".
This is what I figured out based on my research - We need to create our own MessageInterpolator and add it as dependency to the validator instead of message source. Because when we add a messageSource as dependency, it is cached by default by the validator and any message reloads spring does won't take effect in the validator's cached instance of messageSource.
Below are the details:
In context.xml, add the custom MessageInterpolator as dependency to LocalValidatorFactoryBean instead of messageSource:
<mvc:annotation-driven validator="validator" />
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>file:../conf/fileapplication</value> <!-- Messages here will override the below properties file-->
<value>/WEB-INF/application</value>
</list>
</property>
<property name="cacheSeconds" value="10"></property> <!-- Will check for refresh every 10 seconds -->
</bean>
<bean name="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="messageInterpolator">
<ref bean="messageInterpolator"/>
</property>
</bean>
<bean name="messageInterpolator"
class="com.my.org.support.MyCustomResourceBundleMessageInterpolator">
<constructor-arg ref="messageSource" />
</bean>
Create your custom MessageInterpolator by extending Hibernate's org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.
public class MyCustomResourceBundleMessageInterpolator extends
ResourceBundleMessageInterpolator {
public MyCustomResourceBundleMessageInterpolator(MessageSource messageSource)
{
// Passing false for the second argument
// in the super() constructor avoids the messages being cached.
super(new MessageSourceResourceBundleLocator(messageSource), false);
}
}
Model, Controller and properties file can be same as in the question.
I recently upgraded to spring 3.2 and noticed that AnnotationMethodHandlerAdapter had been deprecated in favor of RequestMappingHandlerAdapter. So I reconfigured to use the new class, complete with a custom MessageConverter I need. All fine and good.
However, when attempting to hit a URL supported by an annotated Controller, I'm getting an error:
[java] javax.servlet.ServletException: No adapter for handler [my.company.TagController#1c2e7808]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
[java] at org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1128)
[java] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:903)
[java] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
When debugging the dispatcher, and in particular, the Dispatcher.getHandlerAdapter() method, it's finding my HandlerAdapter, but the AbstractHandlerMethodAdapter.supports() that is invoked wants a MethodHandler:
public final boolean supports(Object handler) {
return handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler);
}
and the controller is not a HandlerMethod. The AnnotatedMethodHandlerAdapter's support method is.. well, different (and works still!)
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}
So I apparently cannot simply upgrade to the new class... I'm missing some additional configuration, but the documentation isn't really helping me out. Any ideas?
Thanks.
Use "<mvc:annotation-driven/>" in the spring configuration file instead of writing your own implementation of WebMvcConfigurationSupport
example
<mvc:annotation-driven/>
<context:component-scan base-package="com.springapp.mvc"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
</list>
</property>
</bean>
So as it turns out, simple switching the bean definition doesn't work due to the fact that the RequestMappingHandlerAdapter is depending on a whole host of entities being created and configured. Spring, by default, is using a WebMvcConfigurationSupport entity to do all this default configuration, but simply creating my own bean version doesn't help because spring creates its own.
My approach ended up being something along the lines of below, where I left basically all of the configuration up to spring's default, but then added my own converter. The only drawback is that it's switching xml configuration to javaconfig, but in my case, it's ok. There's an article here that describes something similar.
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter handlerAdapter = super.requestMappingHandlerAdapter();
handlerAdapter.getMessageConverters().add(0, getProtobufJsonMessageConverter());
return handlerAdapter;
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
return new XXXXRequestMappingHandlerAdapter();
}
}
I am learning the spring mvc and when i try to use the ConversionService ,i encounterrd 500
#RequestMapping("/handle81")
public String handle81(#RequestParam("user")User user,ModelMap modelMap) {
System.out.println(user);
modelMap.put("user", user);
return "/user/success";
}
this is the handler method ,i've put the #RequestMapping("/user") at the class
and the converter
public class StringToUserConverter implements Converter<String, User> {
public User convert(String source) {
System.out.println(source);
User user=new User();
String[] item=source.split(":");
user.setUserName(item[0]);
user.setPassword(item[1]);
user.setName(item[2]);
return user;
}
}
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.baobaotao.domain.StringToUserConverter" />
</list>
</property>
</bean>
so when i browse
http://localhost:8080/spring-mvc/user/handle81.html?user=asdf:asdf:fdas
it gets 500 and prints nothing at the console(i use maven-jetty to do the test)
thx for helping~
I think your Request URL may not be not matching. You specify "/handle81" in the annotation, but are requesting "/handle81.html".
It's hard to tell, without further information, whether the problem is matching & dispatching the request to the handler; or in the conversion.
Try another handler with the parameter of type String, and see whether you can call that successfully. At least you'll then know where the problem is.
And what is the exception stack-trace? Why didn't you post it? That's your most important clue & you should always post the ex message & top few lines/ where it was thrown, when you ask a question. It should be in either the application or Tomcat/ other server logs.
There are two major means of data binding initialization, but there is a drawback in the oldschool one, that I can't figure out. This annotation way is great :
#InitBinder("order")
public void initBinder(WebDataBinder binder) {
// Problem is that I want to set allowed and restricted fields - can be done here
binder.setAllowedFields(allowedFields.split(","));
}
but I can't be done with ConfigurableWebBindingInitializer. First off, the binder instance is created in AnnotationMethodHandlerAdapter and initializer is passed the binder instance somewhere in HandlerMethodInvoker so I can't set it up... I can't do something like this :
<bean id="codesResolver" class="org.springframework.validation.DefaultMessageCodesResolver" />
<bean id="binder" class="org.springframework.web.portlet.bind.PortletRequestDataBinder" scope="prototype">
<property name="allowedFields" value="${allowedFields}" />
<aop:scoped-proxy />
</bean>
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="messageCodesResolver" ref="codesResolver" />
</bean>
Because binder instance is passed into it in handlerAdapter. How can I set up the binder then ?
There is no way of setting it up in xml configuration. You must implement your custom WebBindingInitializer ... The ConfigurableWebBindingInitializer is obviously missing the possibility of setting up allowed and restricted fields...
Or you can vote up SPR-8601
This is very old, however for anyone that dislike the use of annotations in production code (like me) here is a solution I found to add a init binder without use of annotations. You only need to overwrite initBinder method that extends from most of base controllers provided by Spring:
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception
{
System.out.println("Binding!!!!!");
super.initBinder(request, binder);
binder.registerCustomEditor(Double.class, new CurrencyPropertyEditor());
}
Where my CurrencyPropertyEditor class is a subclass of java.beans.PropertyEditorSupport with getAsText, getValue, setValue and setAsText methods overwrited as well.
Hope it helps!!!
I am using springmvc for a REST project and whenever the client calls a rest resource with the wrong HTTP method, a servletrequestbindingexception is thrown. I cannot handle these exceptions with a #ExceptionHandler in the controller, as it happens not within the handler method but in the spring mapping layer.
Currently I declared a web.xml exception handling, this works:
<error-page>
<exception-type>org.springframework.web.bind.ServletRequestBindingException</exception-type>
<location>/servletRequestBindingException.jsp</location>
</error-page>
<error-page>
<error-code>405</error-code>
<location>/methodNotSupported.jsp</location>
</error-page>
I'd rather use spring exception handling though. For example I'd like to create a dynamic response based on teh incoming Accept header, so either writing out json or xml for a rest exception for example. The best would be to return an object from this handler that would automatically be converted to json or xml just like a normal dto returned from a handler.
Is there a way to catch these lower level mapping exceptions?
You can't use #ExceptionHandler (since as you say, this is for dealing exceptions thrown from within the handler code), but you can still use the HandlerExceptionResolver framework to do this.
By default, DispatcherServlet registers an instance of DefaultHandlerExceptionResolver:
Default implementation of the HandlerExceptionResolver interface that resolves standard Spring exceptions and translates them to corresponding HTTP status codes.
The generation of the HTTP 405 is actually handled in this class, by catching HttpRequestMethodNotSupportedException thrown by the handler-mapping code.
So if you want to handle this exception differently, you can provide your own implementation of HandlerExceptionResolver. It's probably easiest to subclass DefaultHandlerExceptionResolver and override the handleHttpRequestMethodNotSupported method, returning your ModelAndView from there.
Be careful to include the default exception resolvers if you include your own. If you are using annotated #Exception handlers you need to explicitly load these or they will no longer function.
In this case, FooBarHandlerExceptionResolver extends DefaultHandlerExceptionResolver and provides a method that the default resolver doesn't cover. This lets FooBarHandlerExceptionResolver handle class-level exceptions that I couldn't catch with annotated #Exception handler methods.
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<property name="order" value="1"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver" >
<property name="order" value="2"/>
</bean>
<bean class="com.company.package.foo.FooBarHandlerExceptionResolver">
<property name="order" value="3"/>
</bean>
Here is the exception resolver
public class FooBarHandlerExceptionResolver extends DefaultHandlerExceptionResolver {
#Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof UnsatisfiedServletRequestParameterException) {
return handleUnsatisfiedServletRequestParameter((UnsatisfiedServletRequestParameterException) ex, request, response,
handler);
}else {
super.doResolveException(request,response,handler,ex);
}
}
catch(Exception handlerException){
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
protected ModelAndView handleUnsatisfiedServletRequestParameter(UnsatisfiedServletRequestParameterException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
logger.warn(ex.getMessage());
return new ModelAndView("blank", new ModelMap("reason", ex.getMessage()));
}
}