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.
Related
I have an ApiController that returns OkNegotiatedContentResult<T> with a resource collection, paginated. Naturally, there will be more methods that return paginated collections.
The content in the result of all those actions looks roughly like this:
A Data property with the current items
A PagingMeta property with paging metadata such as total pages and total items
A PagingLinks property that contain links to first/prev/next/last pages
My backend returns a custom PagedList<T> object and I have another PagedListResponse<T> that contains the above properties. It's a simple transformation from one to the other.
Now, in Web API, the ApiController provides a couple of convenient methods for returning IHttpActionResult, such as:
Ok<T>(T value)
Content<T>(HttpStatusCode statusCode, T value)
I was hoping to make it just as easy for the controller to return a paged result just as easily.
For the moment I have an extension method that provides it, with the following signature:
public static OkNegotiatedContentResult<PagedListResult<T>> OkPaged<T>(this ApiController controller, /* other parameters */)
The "ugly" is that you can only call an extension method by including the this keyword:
return this.OkPaged(myResult);
the only other option I can see is to implement a base controller class, but I generally try to avoid such inheritance structures because they tend to get troublesome later down the road.
What does ASP.NET provide in terms of extension points to do what I want to do?
You can always create a class that inherits from IHttpActionResult and use that as your return value, e.g
public class HttpStatusCodeResultWithContent<T> : IHttpActionResult
{
private readonly HttpRequestMessage httpRequestMessage;
private readonly HttpStatusCode statusCode;
public HttpStatusCodeResultWithContent(HttpRequestMessage httpRequestMessage, HttpStatusCode statusCode, T model)
{
this.httpRequestMessage = httpRequestMessage;
this.statusCode = statusCode;
ResponseModel = model;
}
public T ResponseModel { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = httpRequestMessage.CreateResponse(statusCode, ResponseModel);
return Task.FromResult(response);
}
}
That gives you an easily testable class in HttpStatusCodeResultWithContent, that allows you to return any content with any HTTP Status Code.
Then from your controller code,
var pagedListResult = someObject.GetPagedList();
return new HttpStatusCodeResultWithContent<PagedListResult<T>(Request, HttpStatusCode.OK, pagedListResult);
You can then use extension methods if you like on ApiController to wrap up access to this - that's pretty much all that's going on inside ApiController, e.g. here's what the 'Ok' method on ApiController looks like.
protected internal virtual OkNegotiatedContentResult<T> Ok<T>(T content)
{
return new OkNegotiatedContentResult<T>(content, this);
}
In order to retrieve a list in a Spring MVC application I would like to write something like:
public String myMethod(#RequestParam("foo") List<FooUi> foos)
But the only solution I've found so far is the following :
public String myMethod(FooListWrapperUi fooListWrapperUi)
I don't like this solution because I have to write a wrapper each time I need to retrieve a list. In this example, the wrapper is the following :
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class FooListWrapperUi
{
private ArrayList<FooUi> fooList;
}
So my question is, is it possible to use something like the first solution or is it impossible and I need to write a wrapper?
Thanks.
You can accommodate your use case by creating your own HandlerMethodArgumentResolver:
public class FooUiResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return (methodParameter.getParameterType().equals(FooUi.class) ||
(methodParameter instanceof Collection<?> && ((ParameterizedType) methodParameter.getParameterType().getGenericSuperclass()).getActualTypeArguments()[0] == FooUi.class));
}
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
// Create instances of FooUi by accessing requests parameters in nativeWebRequest.getParameterMap()
}
}
The actual implementation will depend on how you would create one or more FooUi instances from the request parameters or body. You then need to register FooUiResolver in your servlet config:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){
argumentResolvers.add(new FooUiResolver());
super.addArgumentResolvers(argumentResolvers);
}
Once registered, you can use FooUi in your controller method arguments without RequestParam or any other annotation:
#RequestMapping(value = "/foo")
public String myMethod(List<FooUi> foos){}
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.
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
Problem
I have a Spring MVC application that requires me to translate the id's and names of a list of a certain entity to an array of JSON objects with specific formatting, and output that on a certain request. That is, I need an array of JSON objects like this:
{
label: Subject.getId()
value: Subject.getName()
}
For easy use with the jQuery Autocomplete plugin.
So in my controller, I wrote the following:
#RequestMapping(value = "/autocomplete.json", method = RequestMethod.GET)
#JsonSerialize(contentUsing=SubjectAutocompleteSerializer.class)
public #ResponseBody List<Subject> autocompleteJson() {
return Subject.findAllSubjects();
}
// Internal class
public class SubjectAutocompleteSerializer extends JsonSerializer<Subject> {
#Override
public void serialize(Subject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("label", value.getId().toString());
jgen.writeStringField("value", value.getName());
jgen.writeEndObject();
}
}
The JSON I get back however, is the default serialization inferred by Jackson. My custom serializer seems to be completely ignored. Obviously the problem is incorrect usage of #JsonSerialize or JsonSerializer, but I could not find proper usage of these within context anywhere.
Question
What is the proper way to use Jackson to achieve the serialization I want? Please note that it's important that the entities are only serialized this way in this context, and open to other serialization elsewhere
#JsonSerialize should be set on the class that's being serialized not the controller.
#JsonSerialize should be set on the class that's being serialized not the controller.
I'd like to add my two cents (a use case example) to the above answer... You can't always specify a json serializer for a particular type especially if this is a generic type (erasure doesn't allow to pick the the serializer for a particular generic at runtime), however you can always create a new type (you can extend the generalized type or create a wrapper if the serialized type is final and can't be extended) and custom JsonSerializer for that type. For example you can do something like this to serialize different org.springframework.data.domain.Page types:
#JsonComponent
public class PageOfMyDtosSerializer
extends JsonSerializer<Page<MyDto>> {
#Override
public void serialize(Page<MyDto> page,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException {
//...serialization logic for Page<MyDto> type
}
}
#JsonSerialize(using = PageOfMyDtosSerializer.class)
public class PageOfMyDtos extends PageImpl<MyDto> {
public PageOfMyDtos(List<MyDto> content, Pageable pageable, long total) {
super(content, pageable, total);
}
}
And then you can return your type from methods of your services - the necessary serializer will be utilized unambiguously:
#Service
#Transactional
public class MyServiceImpl implements MyService {
...
#Override
public Page<UserProfileDto> searchForUsers(
Pageable pageable,
SearchCriteriaDto criteriaDto) {
//...some business logic
/*here you pass the necessary search Specification or something else...*/
final Page<Entity> entities = myEntityRepository.findAll(...);
/*here you goes the conversion logic of your choice...*/
final List<MyDto> content = modelMapper.map(entieis.getContent(), new TypeToken<List<MyDto>>(){}.getType());
/*and finally return your the your new type so it will be serialized with the jsonSerializer we have specified*/
return new PageOfMyDtos(content, pageable, entities.getTotalElements());
}
}