Spring Data REST Controllers vs Custom Controllers - spring-mvc

I have a 2 domain classes one with reference to another like this:
#Document
public class Dummy {
#Id
private UUID id;
private String name;
#Reference
private DummyAttribute dummyAttribute;
// getters and setters omitted.
}
#Document
public class DummyAttribute {
#Id
private UUID id;
private String name;
// getters and setters omitted.
}
I also have 2 repositories corresponding to Dummy and DummyAttribute.
public interface DummyRepository extends CrudRepository<Dummy, UUID> {
}
public interface DummyAttributeRepository extends
CrudRepository<DummyAttribute, UUID> {
}
I want to create a Dummy with a DummyAttribute. So, I create a dummyAttribute by posting to /dummyattributes. I get the response body with a self link to dummyAttribute back. This self link that I get back is used during the creation of Dummy. My JSON payload to the /dummies looks like :
{
"name" : "dummy",
"dummyAttribute" : <self link of dummyAttribute generated during POST>
}
When I do a GET on the association URL generated after POST, I correctly get
the dummyAttribute that was used. So far works well in Spring Data REST.
I want to do the same using my custom controllers. So, I created controllers
for both Dummy and DummyAttribute.
#RestController
public class DummyController {
#Autowired
private DummyRepository dummyRepository;
#Autowired
private DummyResourceProcessor processor;
#RequestMapping(value = "/dummies", method = RequestMethod.POST)
public HttpEntity<Resource<Dummy>> createTenant(#RequestBody Dummy dummy)
{
Dummy save = dummyRepository.save(dummy);
Resource<Dummy> dummyr = new Resource<Dummy>(save);
return new ResponseEntity<Resource<Dummy>>(processor.process(dummyr),
HttpStatus.CREATED);
}
#RequestMapping(value = "/dummies/{id}", method = RequestMethod.GET)
public HttpEntity<Resource<Dummy>> getDummy(#PathVariable("id") Dummy
dummy) {
Resource<Dummy> dummyr = new Resource<Dummy>(dummy);
return new ResponseEntity<Resource<Dummy>>(processor.process(dummyr),
HttpStatus.OK);
}
#RestController
public class DummyAttributeController {
#Autowired
private DummyRepository dummyRepository;
#Autowired
private DummyAttributeRepository dummyAttributeRepository;
#Autowired
private DummyAttributeResourceProcessor processor;
#RequestMapping(value = "/dummyAttributes", method =
RequestMethod.POST)
public HttpEntity<Resource<DummyAttribute>> createDummyAttribute(
#RequestBody DummyAttribute dummyAttribute) {
DummyAttribute save = dummyAttributeRepository.save(dummyAttribute);
Resource<DummyAttribute> dummyr = new Resource<DummyAttribute>(save);
return new ResponseEntity<Resource<DummyAttribute>>
(processor.process(dummyr),createHeaders(request,save.getId()),
HttpStatus.CREATED);
}
#RequestMapping(value = "/dummyAttributes/{id}", method =
RequestMethod.GET)
public HttpEntity<Resource<DummyAttribute>> getDummyAttribute(
#PathVariable("id") DummyAttribute dummyAttribute) {
Resource<DummyAttribute> dummyr = new Resource<DummyAttribute>
(dummyAttribute);
return new ResponseEntity<Resource<DummyAttribute>>
(processor.process(dummyr), HttpStatus.OK);
}
I followed the same sequence of step as above. I did a POST todummyAttribute.
Using this self link , I tried to create a dummy.
This time things are not so smooth. I get this exception back.
Can not instantiate value of type [simple type,
class com.sudo.DummyAttribute] from String value
('http://localhost:8080/dummyAttributes/3fa67f88-f3f9-4efa-a502-
bbeffd3f6025'); no single-String constructor/factory method at
[Source: java.io.PushbackInputStream#224c018a; line: 2, column: 19]
(through reference chain: com.sudo.Dummy["dummyAttribute"])
When I create a constructor inside DummyAttribute, and I parse the id from the url and assign it to the id.
public DummyAttribute(String url) {
String attrId = // parse the URL to get the id;
this.id = attrId;
}
Now things are work as expected.The dummyAttribute is assigned to dummy.
What I would like to know is why are things different when I write my custom-controller ? What am I missing ? How is it that when I use Spring Data REST, the reference URL to the dummyAttribute was automatically resolved to the corresponding dummyAttribute object and in the custom controller, I had to parse it manually and assign the id value explicitly to domainAttribute id?
Also, in the constructor I believe, the dummyAttribute is not resolved by finding it from repository by doing a findOne but a new dummyAttribute is being created. Is this correct?
How do I make my POSTs to my custom controller work exactly like how it works in Spring Data REST ? Do I need a custom serializer/deserializer for this ? Do I need to register some components manually and invoked it ?
I found that when I have customer controllers and #EnableWebMvc is used, the associated resource does not get resolved. That results in the error above. If no #EnableWebMvc is present, then the associated resource gets resolved properly. Not sure how #EnableWebMvc gets in between....
The spring versions that I use are : spring-boot-starter-1.2.5, spring-boot-starter-data-rest-1.2.5, spring-data-commons-1.9.3. spring-hateoas-0.16.0, spring-data-rest-core-2.2.3, spring-data-rest-webmvc-2.2.3.

Related

spring mvc rest validation on all method parameters

I would like to find a a way to validate a rest method based on all parameters outside the Controller.
First Question: Is there already a way to do it?
Second Question: If not - how can I hook the validation into spring mvc binding prozess.
A way how it could look like. It would be nice to mark the method with a new #MethodValidation Annotation:
#Validate
#MethodValidation(MyValidator.class)
public Response doSomthing(String param1, Integer param2, Something param3){}
Annotation
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MethodValidation{
Class<? extends MethodValidator<?, ?>>[] value();
}
Implement a Validator
public class MyValidator implements MethodValidator{
public void validate(Object[] params, Errors errors){
String param1 = (String ) params[0];
Integer param2 = (Integer) params[1];
Something param3 = (Something)params[3];
// .... do some validations
if(error)
errors.reject("Some.error.done");
}
}
what kind of parameters exactly? a lot of spring stuff is actually available in ThreadLocals, if you dare to dig into it.
you CAN inject stuff into the binding process:
#ControllerAdvice
public class FooControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
Date.class,
new CustomFooEditor()
);
}
}
and the actual editor:
public class CustomFooEditor extends PropertyEditorSupport {
}
but this doesn't give you that much of an edge over regular validation.
or you can use spring aop triggered by an annotation, then annotate your methods, with the config:
#EnableAspectJAutoProxy(proxyTargetClass=true)
an aspect:
#Aspect
#Component
public class ValidationAspect {
#Pointcut("execution(public * * (..))")
private void anyPublicMethod() {}
#Around("anyPublicMethod() && #annotation(foo)")
public Object all(
ProceedingJoinPoint proceedingJoinPoint,
Foo ann) throws Throwable {
}
[...]
}
an annotation:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Foo {
}
public String value();
and then annotating your method:
#RequestMapping...
#Foo(value="foo.bar.ValidatorClassname")
public Response x() {
}
... so you see, there's a lot of ways you can go. i'd really like to know what keeps you from using standard validation?
.rm
thanx for the answer.
I hope I am right: The standard validation outside the controller just allows me to to validate each method parameter separately.
I actually get into problems when the validation depends on 2 or more method parameter. This could be in following situation: Some thing is a part of an Object hierarchy:
public class Parent{
private Integer id;
private List<Something> childs;
...
}
public class Something{
private Integer id;
private String name;
...
}
The Constrain: it is not allowed that a Parent has 2 somethings in the list with the same name. For saving a new some thing I am calling the method.
#RequestMapping(
value = "/chargingstation/{parentId}",
method = RequestMethod.Post)
public Response doSomthing(
#PathVariable("parentId") Integer parentId,
Something param3)
Add the parentId to the Something-ModelOject was not an option.
So is there a way to handle this situation with the standard validation?

Mapping custom data type from URL to Spring MVC controller using #ModelAttribute

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

Spring MVC - PropertyEditor not called during ModelAttribute type conversion

Using Spring 3.2.3, I'm trying to implement a simple CRUD controller that handles REST-ful URLs. It relies on a PropertyEditor to convert a path variable to a BusinessService entity by loading it from an application service. Code is as follows:
#Controller
public class BusinessServiceController {
#Autowired
private BusinessServiceService businessSvcService;
public BusinessServiceController() {
}
#InitBinder
public void initBinder(final WebDataBinder binder) {
binder.registerCustomEditor(BusinessService.class, new BusinessServicePropertyEditor(businessSvcService));
}
#RequestMapping(value = "/ui/account/business-services/{businessSvc}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView update(#ModelAttribute("businessSvc") #Valid final BusinessService businessSvc, final BindingResult result,
final RedirectAttributes redirectAttribs) throws UnknownBusinessServiceException {
ModelAndView mav;
if (result.hasErrors()) {
mav = new ModelAndView("/business-service/edit");
}
else {
businessSvcService.updateBusinessService(XSecurity.principal().getId(), businessSvc);
mav = new ModelAndView("redirect:/ui/account/business-services");
redirectAttribs.addFlashAttribute("message", Message.info("businessService.updated", businessSvc.getTitle()));
}
return mav;
}
}
public class BusinessServicePropertyEditor extends PropertyEditorSupport {
private final BusinessServiceService businessSvcService;
public BusinessServicePropertyEditor(final BusinessServiceService businessSvcService) {
this.businessSvcService = businessSvcService;
}
#Override
public String getAsText() {
final BusinessService svc = (BusinessService) getValue();
return Long.toString(svc.getId());
}
#Override
public void setAsText(final String text) {
final BusinessService svc = businessSvcService.getBusinessService(Long.parseLong(text));
setValue(svc);
}
}
According to SPR-7608, starting from Spring 3.2, #ModelAttribute method argument resolution checks if a path variable by the same name exists (it does here), in which case it tries to convert that path variable's value to the target parameter type through registered Converters and PropertyEditors. This is not what I'm experiencing. When I inspect what ServletModelAttributeMethodProcessor does, it clearly uses the request DataBinder's ConversionService to perform type conversion, which does not consider registered PropertyEditors, and hence BusinessServicePropertyEditor#setAsText is never called.
Is this a configuration problem or an actual bug?
Thanks for your help!
Spring's ConversionService and Converters are replacement for standard Java Beans PropertyEditors.
You need to implement Converter instead of PropertyEditor if this feature is based purely on conversion service.
To register your custom converters in WebDataBinder you might use ConfigurableWebBindingInitializer or #InitBinder method.

map subset of request params to an object in spring mvc

In our web app, using Spring MVC 3.2 we display many paginated lists of different objects, and the links to other pages in the list are constructed like this:
/servlet/path?pageNum=4&resultsPerPage=10&sortOrder=ASC&sortBy=name
although there might be additional request parameters in the URL as well (e.g., search filters).
So we have controller methods like this:
#RequestMapping(method = RequestMethod.GET, value="/ajax/admin/list")
public String ajaxlistGroups(Model model,
#RequestParam(value="pageNumber",required=false,defaultValue="0") Long pageNumber,
#RequestParam(value="resultsPerPage",required=false,defaultValue="10") int resultsPerPage,
#RequestParam(value="sortOrder",required=false,defaultValue="DESC") String sortOrder,
#RequestParam(value="orderBy",required=false,defaultValue="modificationDate")String orderBy) {
// create a PaginationCriteria object to hold this information for passing to Service layer
// do Database search
// return a JSP view name
}
so we end up with this clumsy method signature, repeated several times in the app, and each method needs to create a PaginationCriteria object to hold the pagination information, and validate the input.
Is there a way to create our PaginationCriteria object automatically, if these request params are present? E.g., replace the above with:
#RequestMapping(method = RequestMethod.GET, value="/ajax/admin/list")
public String ajaxlistGroups(Model model, #SomeAnnotation? PaginationCriteria criteria,
) {
...
}
I.e., is there a way in Spring to take a defined subset of requestParams from a regular GET request, and convert them to an object automatically, so it's available for use in the Controller handler method? I've only used #ModelAttribute before, and that doesn't seem the right thing here.
Thanks!
Spring 3.2 should automatically map request parameters to a custom java bean.
#RequestMapping(method = RequestMethod.GET, value="/ajax/admin/list")
public String ajaxlistGroups(Model model, PaginationCriteriaBean criteriaBean,
) {
//if PaginationCriteriaBean should be populated as long as the field name is same as
//request parameter names.
}
I'm not sure how Spring magically achieve this(without #ModelAttribute), but the code above works for me.
There is another way to achieve the same goal, you can actually achieve more, that is spring AOP.
<bean id="aspectBean" class="au.net.test.aspect.MyAspect"></bean>
<aop:config>
<aop:aspect id="myAspect" ref="aspectBean">
<aop:pointcut id="myPointcut"
expression="execution(* au.net.test.web.*.*(..)) and args(request,bean,..)" />
<aop:before pointcut-ref="myPointcut" method="monitor" />
</aop:aspect>
</aop:config>
in application context, we declare Aspect bean as well as Pointcut along with advice, which in your case is before advice
the following is source code
public class PaginationCriteriaBean {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//custom Aspect
public class MyAspect {
public void monitor( HttpServletRequest request,PaginationCriteriaBean bean){
//populate your pagination bean
bean.setId(request.getParameter("id"));
bean.setName("my new name");
}
}
#RequestMapping(value="/app")
public String appRoot(HttpServletRequest request,PaginationCriteriaBean bean){
System.out.println(bean.getId());
System.out.println(bean.getName());
return "app";
}
by doing so, the aspect will intercept spring controller and populate PaginationCriteriaBean based on request parameters, and you can even change the original value in request. With this AOP implementation you are empowered to apply more logic against Pagination, such as logging and validation and etc.

ASMX Web Service (ASP.net 2.0) - Serializable Dictionary not serialized

All,
I have an instance of ProjectBudget class returned from a web method.
Ex:
[WebMethod()]
public ProjectBudget LoadBudget(int id)
{
ProjectBudget budget = BudgetManager.LoadBudget(id);
return budget;
}
The ProjectBudget class contains the following defintion:
public class ProjectBudget
{
public int Id = -1;
public long VersionNumber = -1;
public string QuoteNumber = "";
public string CurrencyCode = "";
public ProjectInfo Project;
public ClientInfo Client;
public readonly List<InventoryItem> Inventory = new List<InventoryItem>();
public readonly List<Staff> Staff = new List<Staff>();
public readonly List<CodeType> Departments = new List<CodeType>();
public readonly SerializableDictionary<string, string> Tasks = new SerializableDictionary<string, string>();
public ProjectBudget()
{
}
}
All public fields you see are serialized just fine with the exception of Tasks field, which is completely ignored by XML serializer. Since we all know by now that Dictionaries cannot be handled by XML serializer, I use a serializable dictionary (which is just a dictionary that implements IXmlSerializable) here but XML serializer decides to ignore it completely, i.e. the XML output does not contain any tasks and the generated proxy class doesn't have this field.
I need to figure out how to tell the XML serializer not to omit this field.
Btw, what is interesting is that a web method that returns SerializableDictionary works fine!
A very similar question as yours appears to have been asked already: Link.
Use DataContractSerializer or try explicitly implementing your getter (and setter), as per this link.

Resources