Spring MVC: a better way to get localized messages from within controller method? - model-view-controller

I have a Spring controller it needs to set a message in the request scope and sends the user back to a form because of errors. Here is the method signature:
public String update(HttpServletRequest request,
Model model,
#ModelAttribute("command") User user,
BindingResult result, SessionStatus status)
In the method the request object is available, here is my way of setting a message in the request scope, which I feel is convoluted.
.....
WebApplicationContext ctx = RequestContextUtils.getWebApplicationContext(reque st);
Locale locale = RequestContextUtils.getLocale(request);
request.setAttribute("formError", ctx.getMessage("errors.unique.value", new Object[]{new DefaultMessageSourceResolvable(new String[]{"label.userName"})}, locale));
.......
Here are my questions:
Is the above way correct for setting a message?
Is any better or simpler way?
Thanks for help!

Are you trying to give a feedback to the user? Then you should have a look at result.rejectValue(field, property)
http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/validation/Errors.html#rejectValue%28java.lang.String,%20java.lang.String%29
Example:
if the field on which the validation didnt passed is called "new_password" and the language property is named "new_passwort_invalid" then you could handle it like this:
result.rejectValue("new_password", "new_passwort_invalid");
greets

(Even if the post is old, I hope this can be useful for others)
In servlet-context.xml configuration we declare the bean:
<beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="/WEB-INF/locale/messages" />
<beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>
Then in controller we autowire the messageSource:
#Controller
public class SomeCtrl {
#Autowired
private MessageSource messageSource;
...
Finally in the action we can use the locale (by adding the locale formal parameter and using the message):
#RequestMapping(value="/doLogin", method=RequestMethod.POST)
#ResponseBody
public String tryLogin(
#ModelAttribute("loginBean") LoginBean loginBean,
BindingResult result, SessionStatus status,
Locale locale) {
....
....
String generalErrorMsg = messageSource.getMessage("login.generalError",new Object[] {}, locale);
....
....

Related

#ExceptionHandler annotation in controller does not catching MaxUploadSizeExceededException

I'm trying a very straightforward thing - catching the MaxUploadSizeExceededException errors associated with file uploads.
Here's the relevant bit in my spring-servlet.xml :
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000" />
</bean>
And here's the #ExceptionHandler method in the controller that also handles the file upload stuff
#ExceptionHandler(MaxUploadSizeExceededException.class)
public String handleException(MaxUploadSizeExceededException e, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("uploadErrorMessage", e.getCause().getMessage());
return "redirect:/page";
}
File upload is working fine. However, when I test with a file that exceeds the size limit - it throws the error, and the method for ExceptionHanler is never called.
Any help or pointers on what else to look for / at would be greatly appreciated.
Can you try changing the ExceptionHandler to look for LimitExceededException and MultiPartException instead of the sub Exceptions?
Try creating a separate class with #ControllerAdvice this will then take care of exception coming from all controllers.
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(MaxUploadSizeExceededException.class)
public String handleException(MaxUploadSizeExceededException e, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("uploadErrorMessage", e.getCause().getMessage());
return "redirect:/page";
}
}
Thus if we define our #ExceptionHandler annotation on method in #ControllerAdvice class, it will be applied to all the controllers.
You must define following in your spring-servlet.xml file.
<mvc:annotation-driven/>

How do I use Spring/JUnit to verify a controller is not sending me to a non-existent view?

I'm using Spring 3.1.1.RELEASE and JUnit 4.11. I setup my JUnit tests like so
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-context.xml" })
public class MySpringTest
{
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;
protected MockHttpSession session;
#Autowired
protected RequestMappingHandlerAdapter handlerAdapter;
#Autowired
protected RequestMappingHandlerMapping handlerMapping;
When testing controllers, I have this line to verify that the view the controller's method is returning is the right view …
import static org.springframework.test.web.ModelAndViewAssert.assertViewName;
...
final ModelAndView mav = submitMyForm(…);
assertViewName(mav, "folder/myView");
...
protected ModelAndView submitMyForm(… params ...) throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
request.setRequestURI("/myurl");
request.setMethod("POST");
request.addParameter("param1", param1);
...
final Object handler = handlerMapping.getHandler(request).getHandler();
return handlerAdapter.handle(request, response, handler);
}
My question is, once I verify the view returned my the controller is the expected view, how do I verify it won't result in a 404? The main problem I'm gaving now is testing whether or not the view actually maps to an underlying page in my WAR file.
why don't use spring-mvc-test and do something like this ?
#Autowired
private ViewResolver viewResolver;
// code
View view = viewResolver.resolveViewName(viewName, locale);
//assert view not null
or something like this, in wich you can check both if the view is ok and the returned status (is status 200/404?)
(more code here: http://goo.gl/fMqBsl)
#Test
public void indexTest() throws Exception {
mockMvc.perform(get("/")).andDo(print())
.andExpect(handler().handlerType(MainController.class))
.andExpect(handler().methodName("index"))
.andExpect(view().name("index"))
.andExpect(forwardedUrl("/WEB-INF/tiles/template.jsp"))
.andExpect(status().isOk());
}
i am using standard jsp view
basically, you need to know the view resolver(s). can a specific view be resolved? that means, if you DON'T have a file called abc.xml, it might still be a valid view.
for simplicity sake, lets assume that we have only one view resolver, and, its
"org.springframework.web.servlet.view.UrlBasedViewResolver"
and here is the bean definition
spring 3.2.4 documentation pdf, page 477
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
eg: the view name "page1" => /WEB-INF/jsp/page1.jsp and "admin/page2" => /WEB-INF/jsp/admin/page2.jsp
using this, you can Inject the view resolved to your junit test using #Autowired and/or #Qualifier
then read the "prefix" and suffix value and find the full path like "src/main/webapp/" + prefix + viewname + suffix
and check if the file exists.
you may have multiple view resolvers, so you may want to inject the context and handle the view => filename resolution using a strategy pattern.
something like
foreach resolver
{
if i can resolve the view to a file (resolver type, viewname)
return the physical filename
else
try next resolver
}

#RequestBody or #ModelAttribute with Spring+REST web services

I am creating a Restful website and Web services for iPhone and android apps with Spring 3.1. In my application, i am using Spring Message Convertors (org.springframework.http.converter.json.MappingJacksonHttpMessageConverter) to converting JSON into Java object and vice-versa.
My objective is that there should be only single controller
method(same URL) that should be used by JSP page, Iphone/Andois app.
I am using Spring form tag for object binding from JSP to controller with the help of #ModelAttribute like below.
#RequestMapping(value = "reset-password", method = RequestMethod.POST)
public ModelAndView resetPassword(#ModelAttributeForgot forgotPassword,
HttpServletRequest request) {
System.out.println("data recived=="+forgotPassword.getNewPassword());
}
But the same is NOT working in the case if the data is being posted from iPhone/Android app and the result is:
data recived==null;
So to overcome this problem i have used #RequestBody annotation at place of #ModelAttribute.
So my controller looks like below:
#RequestMapping(value = "reset-password", method = RequestMethod.POST)
public ModelAndView resetPassword(#RequestBody Forgot forgotPassword,
HttpServletRequest request) {
System.out.println("data recived=="+forgotPassword.getNewPassword());
}
It works then and the result i got is:
data recived==somedata;
But #RequestBody then doesn't work with spring form on JSP page and the data doesn't get converted into object and i got null values.
Can't i use #RequestBody annotation to post data in form of JSON
with spring form tag from JSP page??
Is there any way by using which i can post data from my JSP form as
well as from I phone App by using only a single controller method(either #ModelAttribute or #RequestBody).
EDIT:
While writing String in place of Bean class, i am able to get the content in form of plain text, as below:
#RequestMapping(value = "reset-password", method = RequestMethod.POST)
public ModelAndView resetPassword(#RequestBody String string,
HttpServletRequest request) { }
Result from web page call:
uid=11&confirmPassword=somepassword&newPassword=somepassword
Result from iPhone using web service call(in **JSON)**
{"newPassword":"somepassword","confirmPassword":"somepassword","uid":"11"}
But problem is that using this approach i have to parse the JSON string into Java object manually. And in web page content i have to find the values manually that i don't want.
Please help.
Regards,
Arun Kumar
Sorry, but I don't believe there is a way, because #ModelAttribute is bound from form post parameters and #RequestBody passes the body straight to the Json converter. You could replace the spring form tag with a simple json post, but that is probably less convenient than having two #RequestMapping methods.
Its #RequestBody. I feel its better to specify the mime type that you are expecting and producing as output using #RequestMapping as,
#RequestMapping(value="/authenticate",produces="application/json",
consumes="application/json",method=RequestMethod.POST)
Then register appropriate message converters with AnnotationMethodHandlerAdapter
This message converter is responsible for Marshalling & unmarshalling of your request & response entity based on produces & consumes attributes.
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
<property name="order" value="1" />
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" >
<property name="supportedMediaTypes" value="application/json"/>
</bean>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
</bean>
</list>
</property>
</bean>

Internationalized drop downs using Spring 3

Story
I have a select control that represents user access level. I'm looking for a way to internationalize it. The label should be loaded from a message resource and the value should be used as is. I prepare all my drop down lists in controllers using a simple SelectOption class that has a label and a value properties. This way, my select's look consistent accross all jsp's.
Problem
I've found some examples but they are based on logic within jsp. Developer loops through his labels and manually constructs the option tag using a message resource. While this works, there just has to be a better way. I've also found some comments that Spring 3 will have support for internationalizing option labels but I can't find anything concrete on that.
Controller logic
Collection<SelectOption> optionList = new ArrayList<SelectOption>();
optionList.add(new SelectOption("-SELECT-", "-"));
optionList.add(new SelectOption("Administrator", "ADMIN"));
optionList.add(new SelectOption("Editor", "EDIT"));
bean.setFilterUserAccessLevelOptionList(optionList);
JSP logic
<form:select path="filterUserAccessLevel" items="${bean.filterUserAccessLevelOptionList}" itemLabel="label" itemValue="value"/>
Questions
I would like to add options in my controller in this way: optionList.add(new SelectOption("userAccessLevelAdministratorLabel", "ADMIN")); and have Spring convert userAccessLevelAdministratorLabel to a value from a message resource. Is this possible?
If Spring 3 cannot do this for me, how else can this be achieved without manually constructing the option tag within jsp?
=== 2012-01-15 ==============================================================
Still trying to work out a solution using aweigold's idea.
Controller
#Controller
public class UserController {
#Autowired
private UserService userService;
#Autowired
SelectOptionListBuilder listBuilder;
#RequestMapping("/userIndex/{pageNumber}")
public ModelAndView getUserList(#PathVariable Integer pageNumber, #ModelAttribute("userIndexBean") UserIndexBean phantomBean, Locale locale, Model model) {
UserIndexBean bean = new UserIndexBean();
// prepare filter form
Collection<SelectOption> optionList = listBuilder.getUserAccessLevelOptionList(true, SortOrder.NONE, locale);
bean.setFilterUserAccessLevelOptionList(optionList);
SelectOptionListBuilderImpl
#Component
public class SelectOptionListBuilderImpl implements SelectOptionListBuilder, MessageSourceAware {
private MessageSource messageSource;
#Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
#Override
public List<SelectOption> getUserAccessLevelOptionList(boolean addSelectPrompt, SortOrder sortOrder, Locale locale) {
List<SelectOption> optionList = new ArrayList<SelectOption>();
if(addSelectPrompt) {
optionList.add(new SelectOption(messageSource.getMessage("common.selectPromptLabel", null, locale), "-"));
}
messageSource mapping
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/i18n/messages" />
<property name="defaultEncoding" value="UTF-8"/>
<property name="UseCodeAsDefaultMessage" value="true"/>
</bean>
Exception
org.springframework.context.NoSuchMessageException: No message found under code 'common.selectPromptLabel' for locale 'en_CA'
When I need to do operations like this in a Controller outside of a jsp, I've been making my Controllers MessageSourceAware. Spring will then inject a new MessageSource when they are swapped, and you can interrogate it much like Spring does. In your example, you would do something like this:
#Controller
public class someController implements MessageSourceAware {
private MessageSource messageSource;
#Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
#RequestMapping
// Pass in the locale from the LocaleResolver
public void someMapping(Locale locale){
optionList.add(new SelectOption(
messageSource.getMessage("userAccessLevelAdministratorLabel", null, locale),
"ADMIN"))
}
}
Have a look at a spring roo project. They managed this kind of problem by creating tagx tags. This tags do what you already descibed (it contains a litte logic to load the messages from ressources and build the option tags). But because the logic is witten once and you can use this tags like normal tags in you jspx files, it feels like a tag that do what you want to have.

Need some explanation about BeanNameViewResolver

i read the documentation here:
http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/servlet/view/BeanNameViewResolver.html
but i think that the spring documentation sometimes can become complex and hard to understand, so i need little explanation about this class.
As described in the documentation, BeanNameViewResolver resolves Views declared as beans. Usually you need it for some special-purpose views.
Imagine, for example, that one of your controllers should render an Excel spreadsheet. So, you subclass AbstractExcelView and implement your custom logic to render a spreadsheet based on model values:
public class MyExcelView extends AbstractExcelView { ... }
and declare it as a bean:
<bean id = "myExcelView" class = "MyExcelView" />
Then declaring an BeanNameViewResolver makes it available to controllers: when controller returns ModelAndView with view name myExcelView, your spreadsheet will be rendered.
BeanNameViewResolver is usually used in conjunction with some other view resolver that handles "regular" views (so that if BeanNameViewResolver can't find a view, the other resolver tries to find it):
<bean class = "...BeanNameViewResolver">
<property name = "order" value = "0" />
</bean>
<bean class = "...InternalResourceViewResolver">
<property name = "order" value = "1" />
...
</bean>
Indeed the documentation is not fantastic.
The view resolver in spring mvc is a bean which translates from view names to views.
A view name is s simple string. It is returned by the controller's handleRequest() method within the ModelAndView object.
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
....
return ModelAndView("thisIsTheViewName", ...);
}
This view name is resolved to an actual view class by the view resolver.
The BeanNameViewResolver has an easy job: It looks for a view bean in the applicationContext which has this view name as its id.
<bean id="thisIsTheViewName" class="....MyView" />
For simple applications this can be the simplest way to translate from view name to view.
If you are looking for a very simple but complete example:
#Controller
public class MyController {
#GetMapping("/hello")
public String getHello() {
return "helloView";
}
}
#Component
public class HelloView extends AbstractView {
#Override
protected void renderMergedOutputModel(Map<String, Object> map,
HttpServletRequest req, HttpServletResponse res) throws Exception {
res.setContentType("text/plain");
res.getOutputStream().println("hello world");
}
}

Resources