Is is possible to modify a #ModelAttribute before it is validated via #Validated.
ie
#RequestMapping(value = "/doSomething", method = RequestMethod.POST)
public final ModelAndView save(
#Validated(value = {myGroup.class}) #ModelAttribute("myObject") MyObject myObject)
I need to change the state of myObject before #Validated is executed
What about add a ModelAttribute populate method?
#ModelAttribute("myObject")
public MyObject modifyBeforeValidate(
#ModelAttribute("myObject") MyObject myObject) {
//modify it here
return myObject;
}
The side affect is this method will be invoked before every #RequestMapping method if I'm not mistaken.
Update1: example
#ModelAttribute("command")
public ChangeOrderCommand fillinUser(
#ModelAttribute("command") ChangeOrderCommand command,
HttpServletRequest request) {
command.setUser(securityGateway.getUserFrom(request));
return command;
}
#RequestMapping(value = "/foo/bar", method = RequestMethod.POST)
public String change(#ModelAttribute("command") ChangeOrderCommand command,
BindingResult bindingResult, Model model, Locale locale) {
}
There are 2 ways to modify the model attribute object before the #Validated will trigger:
Remove #Validated and autowire the validator and manually trigger the validator:
class MyController {
private final Validator validator;
class MyController(Validator validator) {
this.validator = validator;
}
#PostMapping("/doSomething")
public final ModelAndView save(
#ModelAttribute("myObject") MyObject myObject, BindingResult result) {
// edit MyObject here
validator.validate(myObject, result)
// original method body here
}
Decorate the default validator and pre-process the myObject object inside the decorated validator.
class MyController {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new PreProcessMyObjectValidator(binder.getValidator()));
}
#PostMapping("/doSomething")
public final ModelAndView save(
#Validated(value = {myGroup.class}) #ModelAttribute("myObject") MyObject myObject, BindingResult result) {
...
}
private static class PreProcessMyObjectValidator implements Validator {
private final Validator validator;
public PreProcessMyObjectValidator(Validator validator) {
this.validator = validator;
}
#Override
public boolean supports(#Nonnull Class<?> clazz) {
return validator.supports(clazz);
}
#Override
public void validate(#Nonnull Object target, #Nonnull Errors errors) {
if (target instanceof MyObject) {
MyObject myObject = (MyObject) target;
// manipulate myObject here
}
validator.validate(target, errors);
}
}
}
(This second tip is what I picked up from https://github.com/spring-projects/spring-framework/issues/11103)
Related
Hi I'm trying to write some tests with Mockito and PowerMockito (I need to mock private methods) for a rest service written with SpringMVC and I'm facing the following issue
This is the semplified version of the controller
#Controller
#RequestMapping(value = "/test")
public class SimpleController {
#Autowired
private HttpServletRequest httpRequest;
#RequestMapping(value = "/simpleservice", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
#ResponseBody
public SimpleServiceResponse simpleService(#RequestBody SimpleServiceRequest simpleServiceRequest, HttpServletRequest httpServletRequest) {
SimpleServiceResponse simpleServiceResponse=new SimpleServiceResponse(simpleServiceRequest.getValue());
httpRequest.getHeader("Header");
return simpleServiceResponse;
}
}
and this is the correspoding test class
#WebAppConfiguration
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:WebApplicationContext.xml","classpath:SimpleApplicationContext.xml"})
#PrepareForTest(WebController.class)
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
public class TestSimpleControllerMockito {
private Logger logger = LoggerFactory.getLogger(TestSimpleControllerMockito.class.getName());
private ObjectMapper objectMapper= new ObjectMapper();
#InjectMocks
private SimpleController controller;
#Test
public void testSimpleService() throws Exception {
MockitoAnnotations.initMocks(this);
SimpleService mockedSimple = mock(SimpleService.class);
when(mockedSimple.doSimpleService(any(SimpleServiceRequest.class))).thenReturn(new SimpleServiceResponse("MockMock"));
SimpleController mockedController=PowerMockito.spy(controller);
SimpleServiceRequest simpleServiceRequest= new SimpleServiceRequest("ciao");
String requestAsStr=objectMapper.writeValueAsString(simpleServiceRequest);
MockMvc mMockMvc=MockMvcBuilders.standaloneSetup(mockedController).build();
MvcResult result= mMockMvc.perform(post("/test/simpleservice").content(requestAsStr).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String content = result.getResponse().getContentAsString();
SimpleServiceResponse simpleServiceResponse=objectMapper.readValue(content,SimpleServiceResponse.class);
Assert.assertEquals("MockMockMockedSessionManager",simpleServiceResponse.getValue());
}
}
When running the test case I got a NullPointerEception on httpRequest.getHeader("Header");
My guess is that using the #InjectMocks annotation and then using
SimpleController mockedController=PowerMockito.spy(controller);
is the cause of the NullPointerException. I don't know how to preserve the #Autowire annotation processing on the controller Object. I already found a workaround, but it requires to write some redundant code.
Is there a way to make the #autowired annotation work?
Thanks a lot.
P.S.
the SimpleServiceXXX classes are like this one:
public class SimpleServiceResponse {
private String value;
public SimpleServiceResponse() {
}
public SimpleServiceResponse(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I would like to check if the user entered the country is in the list of properties.
public class CountryValidator implements ConstraintValidator<CountryValid,String> {
#Value("#{countryOptions}")
Map<String, String> countryOptions;
#Override
public boolean isValid(String girilenDeger, ConstraintValidatorContext arg1) {
// TODO Auto-generated method stub
return countryOptions.containsKey(girilenDeger);
}
#Override
public void initialize(CountryValid constraintAnnotation) {
// TODO Auto-generated method stub
ConstraintValidator.super.initialize(constraintAnnotation);
}
}
However, I have successfully used this list before in the controller class. I get NullPointerException error when I use it again in my validation class.
#Controller#RequestMapping("/customerForm")
public class CustomerController {
#Value("#{countryOptions}")
Map<String, String> countryOptions;
#RequestMapping("/mainMenu")
public String returnForm(Model model) {
model.addAttribute("theCountryOptions", countryOptions);
Customer customer1 = new Customer();
model.addAttribute("customer1", customer1);
return "customer-view/main-menu";
}
#RequestMapping("/resultPage")
public String returnResult(#Valid #ModelAttribute("customer1") Customer customer, BindingResult result,
Model model) {
model.addAttribute("theCountryOptions", countryOptions);
if (result.hasErrors())
return "customer-view/main-menu";
else {
AddDatabase<Customer> database = new AddDatabase<Customer>();
database.setObj(customer);
database.addData();
System.out.println("Ekleme islemi tamamlandı.");
return "customer-view/result-page";
}
}
}
Or can I retrieve theCountryOptions attribute from the model?
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(String str) throws IOException {
System.out.println(str);
}
all I got is null:
You need to tell Spring where to get str from.
If you're sending the JSON
{ "str": "sasfasfafa" }
You'll need a class that deserialises from this and annotate the method parameter with #RequestBody.
public class StrEntity {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
public class MyController {
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(#RequestBody StrEntity entity) throws IOException {
System.out.println(entity.getStr());
}
}
If you just want to send a string as the request body (i.e. sasfasfafa) instead of the JSON document you can do this:
public class MyController {
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(#RequestBody String str) throws IOException {
System.out.println(str);
}
}
There is no way to send the JSON { "str": "sasfasfafa" } as request body and only have a String as a method parameter in the controller.
Use #RequestParam annotation to get the parameter.
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(#RequestParam(name="str") String str) throws IOException {
System.out.println(str);
}
How can I achieve the following in spring mvc 3.2.2?
I get the following error:
The method addObject(String, Object) from the type ModelMap is deprecated?
model.addObject("obj", obj); // obj is pojo
Then want to access it within .jsp/jstl view
${obj.id}
Controller:
public class obj {
private String Id;
public void setId(String value) {Id = value;}
public String getId() {
return Id;
}
}
View
${obj.Id} <-- DOES NOT work
${obj.getId()} <-- works!
replace model.addObject() with model.addAttribute("obj", obj)
My problem is to how to call this. I could do
MyObject o = new MyObject();
myController.save(o, "value");
but this is not what I would like to do. I would like the MyObject to be in the request post body? How can this be done?
#Requestmapping(value="/save/{value}", method=RequestMethod.POST)
public void post(#Valid MyObject o, #PathVariable String value{
objectService.save(o);
}
Just to be clear I am talking about unit testing.
Edit:
#RequestMapping(value = "/", method = RequestMethod.POST)
public View postUser(ModelMap data, #Valid Profile profile, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return dummyDataView;
}
data.put(DummyDataView.DATA_TO_SEND, "users/user-1.json");
profileService.save(profile);
return dummyDataView;
}
See sample code below that demonstrates unit testing a controller using junit and spring-test.
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
#Transactional
#ContextConfiguration(locations = {
"classpath:rest.xml"
})
public class ControllerTest{
private MockHttpServletRequest request;
private MockHttpServletResponse response;
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() throws Exception
{
this.request = new MockHttpServletRequest();
request.setContentType("application/json");
this.response = new MockHttpServletResponse();
}
#Test
public void testPost(){
request.setMethod("POST");
request.setRequestURI("/save/test"); //replace test with any value
final ModelAndView mav;
Object handler;
try{
MyObject o = new MyObject();
//set values
//Assuming the controller consumes json
ObjectMapper mapper = new ObjectMapper();
//set o converted as JSON to the request body
//request.setContent(mapper.writeValueAsString(o).getBytes());
request.setAttribute("attribute_name", o); //in case you are trying to set a model attribute.
handler = handlerMapping.getHandler(request).getHandler();
mav = handlerAdapter.handle(request, response, handler);
Assert.assertEquals(200, response.getStatus());
//Assert other conditions.
}
catch (Exception e)
{
}
}
}
You need to use RequestBody:
#Requestmapping(value="/save/{value}", method=RequestMethod.POST)
public void post(#RequestBody MyObject o, #PathVariable String value{
objectService.save(o);
}
general info about request body documentation : http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html#mvc-ann-requestbody