Spring #RepositoryRestController causes #PageableDefault to stop working - java

I have a custom controller that is using the #PageableDefault annotation. I was using #Controller annotation on my class, however, I wanted to make this controller respond with the HATEOAS response. I added the #RepositoryRestController changed my method to
public HttpEntity<PagedResources<Resource<Books>>> search(#RequestParam(value = "q", required = false) String query, #PageableDefault(page = 0, size = 20) Pageable pageable)
and then the return to
return new ResponseEntity<PagedResources<Resource<Books>>>(booksAssembler.toResource(queryResult), HttpStatus.OK);
Now my #PageableDefault doesn't work. But when a client make a request (e.g. explicitly adding (or without) &page=0&size=20 to the URL) the endpoint from the controller, pageable is always null. I don't get why it stop working after changing the annotation? Is there any way to fix it other than changing back to #Controller?

I found the solution.
I was running into this bug. https://jira.spring.io/browse/DATAREST-906
I was using Spring Boot 1.4.1, upgrading to 1.4.2 solves the problem.

Related

Is there a way to clear a "consumes = MediaType" in Spring Boot RestController?

Let's say I have a controller that has a variety of endpoints (GET/POST/PUT/DELETE) and generally they both produce and consume JSON, so I do:
#RestController
#RequestMapping(value=["/some/base/path"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE], consumes = [MediaType.APPLICATION_JSON_UTF8_VALUE])
public class SomeController {
...
}
But it turns out that my #GetMapping does not consume JSON (and I don't want to force callers to set Content-Type: application/json for GET requests. Is there a way, on the #GetMapping, to clear/empty the consumes = value that was set at the class level? Or is there another way to avoid repeating the consumes attribute on all methods in the class?
I've already tried setting the #GetMapping(value=["/some/path"], consumes = []) without any luck. For context, I'm converting from Jersey annotations to Spring REST controller style annotations and I'm finding this to be an annoying difference in behavior (setting a class-level #Consumes annotation doesn't get enforced against #GETs). And just looking for an elegant way to mirror existing behavior without cloning the consumes attribute all over the place.
I got your problem now .Try use the below solution and refer to this link that might help
https://github.com/spring-projects/spring-framework/pull/1257/commits/00e6ca412dffeb8a7a596f9312db19eb6cc49525
#GetMapping(value = "/get", consumes = MediaType.ALL_VALUE)
For your case , you need to delete the consumes part.I mean just use the Produces only.For example :
#GET
#Produces("application/json")
#Path("/{oid}")
public Book getBook(#PathParam("oid") String oid) {
return bookService.getBook(oid);
}
or check this url :
https://dzone.com/articles/spring-boot-building-restful-web-services-with-jersey

Redirect just displaying link instead of taking user to link

Here is the controller method which i'm trying to redirect from:
#GetMapping("/opensignup/{id}")
public String openSignUp(#PathVariable(value = "id") Long Id) {
Tournament comp = tournamentRepository.findOne(Id);
boolean b = true;
comp.setSignUpOpen(b);
return "redirect:/comp/" + Id;
}
I have another method (which is a post, if thats relevant) which successfully redirects to the correct page/controller with the same line:
return "redirect:/comp/" + Id;
Instead of redirecting, it simply prints that link on the browser. (It prints it with the correct id.) Like this:
redirect:/comp/5
How do I get it to redirect instead of print?
As stated in the comments, your second method is annotated with #RestController. While #Controller is meant to return views in the Spring context, #RestController is meant to return something that will be written to the response body directly (JSON in most cases).
#RestController is basically a combination of the #Controller AND a #ResponseBody annotation, whereby the second will try to map the POJO to JSON.
Further reading:
https://www.genuitec.com/spring-frameworkrestcontroller-vs-controller/
Difference between spring #Controller and #RestController annotation
This was happening because the top of my method had an #RestController annotation instead of #Controller. Since I don't need it to be restful, I changed it and my problem is solved. If anyone wants to shed some light on why this is and/or how you would do it while keeping the #RestController annotation (if possible) then feel free.

Conflict in Using #RepositoryRestController and #RepositoryRestResource For a Single Resource in Spring Boot

I have a Payment entity in my spring boot application. Considering all possible CRUD operations, I'm using spring data rest for read and want to implement a custom create operation. Also delete and update are not allowed for this entity.
So this is my desired URLs and resoponsible component for each one:
GET /payments : PaymentRepository
GET /payments/{id} : PaymentRepository
POST /payments : PaymentController
This is my repository:
#RepositoryRestResource
public interface PaymentRepository extends PagingAndSortingRepository<Payment, Long> {
// disable create and update
#Override
#RestResource(exported = false)
Payment save(Payment entity);
// disable delete
#Override
#RestResource(exported = false)
void delete(Payment entity);
}
And this is my controller:
#RepositoryRestController
#RequestMapping("/payments")
public class PaymentController {
#PostMapping("")
#ResponseBody
public Payment create() {
// some code...
}
}
If I map create operation to a url like POST /payments/create, everything works fine, but If I use the above code and map create to POST /payments, the GET /payments url does not work any more and I get 405 Method Not Allowed error. (GET /payments/{id} is still working)
It seems in this case presence of #PostMapping("") annotation, cause the PaymentController to responsd the GET /payments request and it fails.
I hope my explanations were clear. How can I solve this problem?
The Spring Data REST reference states that:
Sometimes you may want to write a custom handler for a specific resource. To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the #RepositoryRestController annotation instead of a standard Spring MVC #Controller or #RestController.
It is not explicitly mentionned, but annotating your controller with #RepositoryRestController also allows you to define a custom behavior for one endpoint while keeping all the other endpoints that Spring automatically generates... On one condition: the #RequestMapping annotation can only be used at the method level (this is actually what is done in the example of the reference documentation).
Your example becomes:
#RepositoryRestController
public class PaymentController {
#PostMapping("/payments")
#ResponseBody
public Payment create() {
// some code...
}
}
With this, you get your custom endpoint mapped to POST /payments requests, plus all endpoints automatically generated by Spring, minus the ones annotated with #RestResource(exported = false).
#BasePathAwareController
#RepositoryRestController
public class PaymentController {
#PostMapping("/payments")
#ResponseBody
public Payment create() {
// some code...
}
}
You should modify your controller in the above way. #BasePathAwareController enables the custom REST URI's to get registered under your base URI.
With the above modification : both API's can work fine.

AspectJ with Spring : intercepted methods lose their parameters annotations

I recently added AOP with aspectJ and spring-aop to my existent spring project. The goal was to actually intercept controller calls to modify the response they send back, in order to bind some values to this response I didn't want to add manually to each and everyone of my controllers, for example the expiration date of the actual token used by the end-user (which I wasn't even able to showcase within my controller in any case). I actually managed to get it working until I started my unit tests :
In my unit tests I call directly my controller methods using Reflection feature from java, then replicate usual process (calling the filter chain, pre handler and post handlers, and the controller method itself which is first manually validated using spring validator when annotation #Valid is present on one of my parameters. All this process works fine and gets executed properly). The problem is that now that the controller method is intercepted by spring-aop, it's mentionned as coming from the proxy controller created, and all of my parameters annotations disapear. Here is a controller example :
#Override
public ResponseEntity<Object> editPassword(#Valid #RequestBody PasswordEditForm passwordEditForm, HttpServletRequest request) {
return factorizedUserBaseController.editPassword(passwordEditForm, request, User.class);
}
the parameter PasswordEditForm has the annotation #Valid so in my test cases it was first validated before any other step, but now as I double checked it, the #Valid annotation is not present on the proxy method, and therefore the parameter doesn't get validated, any clue for how to fix this and make my parameters annotation still understandable from my test point of view?
Note : when running the spring through mvn spring-boot:run, parameters with #Valid annotation gets correctly validated and then goes to my error handler method properly.
Problem Solved : from several other stackoverflow posts I understand that CGLIB (aop proxy lib used by Spring) doesn't support annotations. ( see Retain annotations on CGLIB proxies?). But my problem wasn't here, I was literally sure I was finding the method using the controller class itself (the one I coded) but what I was wrong about is that I was giving the controller instance as a parameter to some other parts of my code which in turn would use this controller class to find the method which of course wasn't working because thanks to Spring proxies, it wasn't anymore my controller itself but a proxy class extending my own controller class. Instead, I just had to replace :
Class<?> controllerClass = controllerInstanciationContainer
.getController()
.getClass();
with
Class<?> controllerClass = controllerInstanciationContainer
.getController()
.getClass()
.getSuperclass();

Using #RequestBody and #ModelAttribute together?

I'm trying to get at the body of a POST, and I'd like the parameters of my method to bind to an object.
Is this possible?
My current declaration doesn't ever get hit:
#RequestMapping(method = RequestMethod.POST)
public void doStuff(#RequestBody byte[] bodyData, #ModelAttribute Form form, Model model ) {
Looks like I'm getting this exception:
- 2011-02-25 16:57:30,354 - ERROR - http-8080-3 - org.springframework.web.portle
t.DispatcherPortlet - Could not complete request
java.lang.UnsupportedOperationException: #RequestBody not supported
For this to work correctly, you have to be sure you're using AnnotationMethodHandlerAdapter. This overrides HandlerMethodInvoker's createHttpInputMessage (which is throwing the exception you're seeing). (It does this in a private class.)
I believe you can just include the following in your *-servlet.xml
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
WARNING: The below answer is for the case of needing #RequestBody and #RequestParam in the same handler method. It does not answer this question, but could be of use to someone.
I've tested this out using Spring 3.0.1. This is possible, but it's somewhat precarious. You MUST have your #RequestBody method argument before your #RequestParam argument. I'm guessing this is because HandlerMethodInvoker reads the request body (along with the GET parameters) when retrieving parameters (and the request body can only be read once).
Here's an example (WARNING: I code in Scala, so I've not compiled this Java code)
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(
#RequestBody String body,
#RequestParam("param1") String parma1,
Map<Object, Object> model: Map[AnyRef, AnyRef])
{
model.put("test", test)
model.put("body", body)
return "Layout"
}
An alternative is to use #PathVariable. I've confirmed that this works.
Unfortunately that is kind of impossible. If you are using portlet version of Spring MVC (and it looks like from the logs) then you might be interested in this JIRA issue.
AnnotationMethodHandlerAdapter uses PortletHandlerMethodInvoker internally and the second is a inner subclass of HandlerMethodInvoker - the place where you can configure HttpMessageConverter-s. But they're set to null. And the property is final.
That even would be to workaround if you could substitute HandlerMethodInvoker, but you can not.. it's constructor-created ;)
One thing to notice is that Servlet version of Spring MVC fully supports HttpMessageConverter-s and does not suffer this issue.

Categories