I have the following class which has errorRequests with #NotEmpty annotation.
public class ErrorsRequests implements Serializable {
private static final long serialVersionUID = -727308651190295062L;
private String applicationName;
#NotEmpty
#Valid
#JsonProperty("errors")
private List<ErrorsRequest> errorRequests;
My Controller looks like the following:
#Autowired
#Qualifier("errorStreamValidator")
private Validator errorStreamValidator;
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> errorReporting(#NotNull #PathVariable String applicationName,
#Valid #NotNull #RequestBody ErrorsRequests errorsRequests, BindingResult results) {
I have the required hibernate validator classes in my classpath.
When I input the following JSON :
{
"errorss": [
]
}
The #NotEmpty validation is not kicked off at all. Hibernate validation only works if the json has errors element in it as below
{
"errors": [
]
}
Can I make the first case also work ?
You are using the annotations in the wrong way and your controller is also missing an annotation.
You should annotate your controller with #Validated:
#Validated
#RestController
#RequestMapping("/mycontroller")
public class MyController { ... }
And in your model class you need to place the annotations like this:
#NotEmpty
private List<#Valid Foo> foo;
The #Valid annotation will check if the items inside the list are also valid.
Check that you have included this dependency in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Related
Spring MVC converts the id from path to corresponding object by making call to JpaRepository's findById method. For example see getVersionTree() method.
public class Controller {
#NonNull
private final MyService service;
#NonNull
private final MyAssembler assembler;
#GetMapping(path = VERSION_TREE_MAPPING, produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public HttpEntity<?> getVersionTree(#PathVariable("id") MappingDocument mappingDocument, Pageable pageable, PagedResourcesAssembler<VersionNode> pagedResourcesAssembler) {
Page<VersionNode> versionNodes = service.getVersionTreeFor(mappingDocument, pageable);
return new ResponseEntity<>(pagedResources, HttpStatus.OK);
}
While testing, SpringMVC throws " Failed to convert value of type 'java.lang.String' to required type 'com.rbc.dna.dtl.mappingdocument.MappingDocument'". I am mocking jpaRepository.findById() method. Test Code is as follows:
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MyRepository repository;
#Mock
MyController controller;
#MockBean
private MyServiceImpl serviceImpl;
#Test
public void testMethod() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(SecurityMockMvcConfigurers.springSecurity()).build();
MappingDocument mappingDocumentl=MappingDocument.builder().id(17L).build();
Mockito.when(repository.findById(17L)).thenReturn(Optional.of(mappingDocumentl));
Mockito.when(serviceImpl.getVersionTreeFor(mappingDocument,pageable)).thenReturn(pagedResponse);
mockMvc.perform(MockMvcRequestBuilders.get("/abc/17/def").param("page","0").param("size","20").contentType(MediaTypes.HAL_JSON_UTF8_VALUE)
.with(authentication(getOauthTestAuthentication()))
.sessionAttr("scopedTarget.oauth2ClientContext", getOauth2ClientContext()))
.andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
Your rest Controller take #PathVariable("id") MappingDocument mappingDocument as variable in your path, but in mockMvc.perform you are passing a simple String/Number or something that doesn't bind with MappingDocument.
Try to replace
public HttpEntity<?> getVersionTree(#PathVariable("id") MappingDocument mappingDocument...
with
public HttpEntity<?> getVersionTree(#PathVariable("id") Long idMappingDocument, ...
If you want to keep your object in #PathVariable you need to change
#GetMapping(path = VERSION_TREE_MAPPING)
To parse an object as PathVariable you need to have path that represent property of your object.
For example if you have
class Person {
String name;
String address;
//getters and setters
}
and you need to define a controller as follow:
#GetMapping(path = "/person/{name}/{address}", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public HttpEntity<?> getVersionTree(Person person) {
Where {name} and {address} must bind Person properties
The domain model is
An industry has many companies
A company belongs to an industry
So I have my entity classes:
#Entity
public class Industry {
#Id #GeneratedValue
private Long id;
private String name;
#OneToMany(targetEntity = Company.class, fetch = FetchType.LAZY, mappedBy = "industry")
private Collection<Company> companies = new ArrayList<>(0);
// Getters and setters
}
and
#Entity
public class Company {
#Id #GeneratedValue
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER, optional = false)
private Industry industry;
// Getters and setters
}
My controller:
#RestController
#RequestMapping("/companies")
public class CompaniesController extends ControllerBase {
#RequestMapping(method = RequestMethod.POST)
public Company create(#RequestBody Company company) {
company.getIndustry(); // returns null
// ...
}
}
When I send request POST /companies with request body
{
"name": "Walmart",
"industry": {
"id": 1
}
}
I found that company.getIndustry() always returns null. How can I make the controller accept nested entities?
Entities are session based. They usually work on basis of Lazy loading I.e only the first level is loaded and other attributes are loaded on Demand. You cannot pass it from one layer to other. (service to controller)
The correct way to do it. Have a Value object (a simple class) n the controller. Use it between front end and back end. Send the same value object to service. And use the entity only between Service and DAo layer
public class CompanyVO{
private Long id;
private String name;
private IndustryVO industryVO; // create similar class
// Getters and setters
}
#RestController
#RequestMapping("/companies")
public class CompaniesController extends ControllerBase {
#RequestMapping(method = RequestMethod.POST)
public Company create(#RequestBody CompanyVO companyVO) {
companyVO.getIndustry(); // returns null
// ...
}
}
This may be because you need another Spring message converter instead of the default one. Just add jackson to your pom.xml and Spring will use MappingJackson2HttpMessageConverter.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.5</version>
</dependency>
I am trying to test my Spring MVC controller with JUnit and I get this:
java.lang.AssertionError: Status expected:<200> but was:<404>
I guess my JUnit setup for controller tests isn't working like it should, but I really can't point out where the mistake is. I've read so many tutorials about this that it's getting frustrating.
This is simplyfied version of my LoginController class, but it will be enough
#Controller
public class LoginController {
#Autowired
private DAO dao;
#RequestMapping(value = "username", method = RequestMethod.GET)
public String GetUsername(Model model){
model.addAttribute("username", dao.getUsername());
return "login"; //login.jsp
}
And this is JUnit class which is testing that controller:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("file:src/main/webapp/WEB-INF/Spring-Context.xml")
public class LoginControllerTest {
#Mock
private DAO dao;
#InjectMocks
LoginController controller;
private MockMvc mockMvc;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() throws Exception{
mockMvc.perform(get("username")).andExpect(status().isOk());
}
}
And I get "404". Same thing if I test example "forwardedUrl("/PATH/login.jsp")" the result is null.
My Spring-Context.xml has
mvc:annotation-driven />
My console error is:
org.springframework.web.servlet.DispatcherServlet noHandlerFound
WARNING: No mapping found for HTTP request with URI [username] in DispatcherServlet with name ''
I don't really get that, because after all my application is working like it should so there is no problems with my mappings.
I am trying to send parameters from UI to Spring MVC controller. My parameter looks like
caseId=23951910&serviceProvided%5B0%5D.id=25989&serviceProvided%5B0%5D.desc=24-Hour+Service&serviceProvided%5B0%5D.duration=1&serviceProvided%5B0%5D.pages=--&serviceProvided%5B1%5D.id=25988&serviceProvided%5B1%5D.desc=3rd+Party+Contact&serviceProvided%5B1%5D.duration=2&serviceProvided%5B1%5D.pages=--&serviceProvided%5B2%5D.id=25980&serviceProvided%5B2%5D.desc=Advice&serviceProvided%5B2%5D.duration=3&serviceProvided%5B2%5D.pages=--&serviceProvided%5B3%5D.id=25982&serviceProvided%5B3%5D.desc=Document+Preparation&serviceProvided%5B3%5D.duration=4&serviceProvided%5B3%5D.pages=--&serviceProvided%5B4%5D.id=DOCREVIEW&serviceProvided%5B4%5D.desc=Document+Review&serviceProvided%5B4%5D.duration=5&serviceProvided%5B4%5D.pages=6
To match this parameter I am using custom class as
Class MyDto {
private Long caseId;
private List<ServiceProvided> serviceProvided;
//getter and setter
}
Class ServiceProvided {
private String id;
private String desc;
private Long duration;
private Long pages;
//getter and setter
}
I have controller as
#RequestMapping(value = "/cases/resolveClaim.htm", method = RequestMethod.POST)
public ModelAndView createClaim(#ModelAttribute("claimInfo") MyDto myDto, BindingResult result) { ... }
I am getting 404 error so I am guessing "serviceProvided" list couldn't match to myDto. So my questions are:
Is this a really a reason I am getting 404 error?
If yes I guess I have to solve with PropertyEditor or Converter? Am I correct?
Is there any example code that I can refer to?
Thanks
I have the following:
#Controller
#RequestMapping("/admin")
public class AdminController extends BaseHtmlController{
#Autowired
protected DeviceCustomerMap deviceCustomerMap;
#Autowired
protected CustomerDao customerDao;
String layout = "template/admin";
#RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model) {
model.addAttribute("meta", meta);
String view = "login";
return view;
}
}
public class AdminCustomerController extends AdminController{
#RequestMapping(value="/customer/mapping", method = RequestMethod.GET)
public String customerMapping(ModelMap model, #RequestParam(required=false) boolean refresh) throws Exception {
if (refresh){
deviceCustomerMap.initCustomerUrlMap();
}
model.addAttribute("meta", meta);
model.addAttribute("view", "customer/mapping");
model.addAttribute("customers", deviceCustomerMap.getCustomerMap());
return layout;
}
}
However, the extended controller doesn't resolve the requests, but when they're in the base controller, they're resolved just fine, I've poked around several threads but couldn't find a solution, any idea?
Is the problem that you are able to get a response when executing a request to the /admin/login resource, but not to /admin/customer/mapping resource, unless you move the customerMapping() method to the AdminController class?
The solution is to annotate the AdminCustomerController class with the #Controller annotation. Without a stereotype annotation (and appropriate component scanning), Spring will not recognise the class as a Spring bean.