Cant understand why RepositoryRestController does not work? - java

I use Spring Data Rest and I can not understand why my RepositoryRestController does not work. Its code:
#RepositoryRestController
public class Cntrl {
#Autowired
private UserDao userDao;
#RequestMapping(name = "/users/{id}/nameOne",method =
RequestMethod.GET)
#ResponseBody
public PersistentEntityResource setNameOne(#PathVariable("id") Long id, PersistentEntityResourceAssembler persistentEntityResourceAssembler){
User user = userDao.findById(id).orElseThrow(()->{
throw new ServerException("Wrong id");
});
user.setLogin("One");
userDao.save(user);
return persistentEntityResourceAssembler.toFullResource(user);
}
}
And Spring Boot start class:
#SpringBootApplication
#EnableWebMvc
#EnableScheduling
#EnableJpaRepositories
#EnableSpringDataWebSupport
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
When i go to base path (localhost:8080/api) everything is fine, but when send GET to request to localhost:8080/api/users/1/nameOne I get empty response, i dont have other controllers and I have user with id 1, so why it is not working ?

It doesn't work because the URL structure you are using has already a meaning in Spring Data Rest context.
/{repository}/{id}/{column} URL is handled at RepositoryPropertyReferenceController.followPropertyReference method.
/api/users/1/nameOne means: get the nameOne column of the user with the id of 1. An important note is that: this column should reference another #Entity. This means if you have a String column named "surname" and you hit the URL /api/users/1/name you will get 404 because this column is not referencing another entity. If you have a column named school which references to a School entity and you hit the URL /api/users/1/school you will get the referenced school entity for that user. If the user does not have a school then you will get 404 again.
Also, #RepositoryRestController can be used for #RequestMapping if the URL you are giving isn't colliding with Spring Data Rest.
You can test that with the following example:
#RepositoryRestController
public class CustomRepositoryRestController {
#RequestMapping(path = "/repositoryRestControllerTest", method = RequestMethod.GET)
#ResponseBody
public String nameOne() {
return "test";
}
}
Visit http://localhost:8080/repositoryRestControllerTest
I hope this explanation clarifies things for you.

If localhost:8080/api is your root context, then localhost:8080/api/users/1/nameOne should be the url you are using for the user GET.

Related

How can I override a Spring Data REST method without disabling default implementations

I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.

Accessing controlleradvice objects from another controller

I've got a #ControllerAdvice class what I use to set user profile information all across the application. In this way, I get the user profile in every single JSP. However, I'm trying to access to that object in a #Controller like this with no success:
#ControllerAdvice
public class CommonControllerAdvice {
#ModelAttribute("PROFILE")
public Profile populateUserProfile(HttpSession session){
return (Profile) session.getAttribute("PROFILE");
}
}
#Controller
public class ActivityController {
#GetMapping("/view/activity/{id}")
public ModelAndView getActivity(ModelAndView modelAndView, #PathVariable Integer id) {
Profile profile = (Profile) modelAndView.getModel().get("PROFILE");
... ...
}
}
But I only get a NullPointerException because the profile is null. However, I know that it is not null because I can use it in the related JSP.
I found a solution. Just pass as a parameter the #ModelAttribute defined in the #ControllerAdvice class, instead of trying to obtain it from the ModelAndView.
#Controller
public class ActivityController{
#GetMapping("/view/activity/{id}")
public ModelAndView getActivity (
ModelAndView modelAndView,
#ModelAttribute Profile profile,
#PathVariable Integer id) {
//Profile object is well populated
//However I don't understand why this model is empty
ModelMap model = modelAndView.getModelMap();
...
}
}
It solved my problem, and I am happy with the solution, nevertheless, I expected to be able to access to this information directly in the ModelMap, and it is empty. I would like to know why.

Spring controller gets invoked but returns 404

I am writing a Spring Boot application. I have written a simple controller that gets invoked whenever the endpoint is hit, but it still returns status 404 and not the specified return value.
HelloController
#Controller
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
Now whenever I call localhost:8080/hello, I see the console log "Hit me!", but "Hello, you!" is never returned. Postman outputs:
{
"timestamp": 1516953147719,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/hello"
}
Application.java
#SpringBootApplication
#ComponentScan({"com.sergialmar.wschat"}) // this is the root package of everything
#EntityScan("com.sergialmar.wschat")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Change your method return a ResponseEntity<T>
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public ResponseEntity<String> hello() {
System.out.println("Hit me!");
return new ResponseEntity<String>("Hello, you!", HttpStatus.OK);
}
or change the controller to RestController
#RestController
public class MessageRequestController {...}
CURL
ubuntu:~$ curl -X GET localhost:8080/hello
Hello, you!
Short version:
Annotate your endpoint method with ResponseBody to bind the return value to the response body.
#Controller
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
#ResponseBody
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
You can instead annotate your class with RestController instead of Controller to apply ResponseBody to each method of the class.
#RestController
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
With #Controller, you use the default model-view from Spring Web MVC, and you're actually telling spring to render the view called Hello, you!.tml from your resources directory (src/main/resources/templates for a Spring Boot project, if I remember correctly).
You can read this article for more information about the Spring MVC REST Workflow.
Once you're more familiar with those concepts, you can even further customize your endpoint method using ResponseEntity.
As you see the "hit me", there's no mapping issue, but in your #RequestMapping annotation you specifie a produce type to "application/json" and you return a simple poor String not formatted and without any header('Content-Type: application/json').
Add the header and format the outpout.
When everything seems ok but receive 404, check this answer:
As you know:
In the Spring MVC you can return view as a String or ModelAndView object.
IMPORTANT NOTE:
In both cases you have to pay attention to relative/absolute path:
If you declare / in the beginning of the view name, you are using absolute path.
Namely it does not matter class level #RequestMapping and directly introduce itself as the final view name.
If you do not declare / in the beginning of the view name, you are using relative path (relative to the class path) and therefore it appends to the class level #RequestMapping to construct final view name.
So, you have to consider the above notes when use the Spring MVC.
Example:
1. create two HTML file test1.html and test2.html in the static folder of spring (boot) structure:
Please note that the class level #RequestMapping behaves as a folder path in the case of relative path.
--- resources
--- static
--- classLevelPath //behaves as a folder when we use relative path scenario in view names
--- test2.html //this will be used for relative path [case (2)]
--- test1.html //this will be used for absolute path [case (1)]
create a controller class like as the below. This example shows different cases with return String and ModelAndView in both relative and absolute path.
#Controller
#RequestMapping("/classLevelPath")
public class TestController {
//case(1)
#RequestMapping("/methodLevelAbsolutePath1")
public String absolutePath1(Model model){
//model.addAttribute();
//...
return "/test1.html";
}
//case(1)
#RequestMapping("/methodLevelAbsolutePath2")
public ModelAndView absolutePath2(Model model){
ModelAndView modelAndView = new ModelAndView("/test1.html");
//modelAndView.addObject()
//....
return modelAndView;
}
//case(2)
#RequestMapping("/methodLevelRelativePath1")
public String relativePath1(Model model){
//model.addAttribute();
//...
return "test2.html";
}
//case(2)
#RequestMapping("/methodLevelRelativePath2")
public ModelAndView relativePath2(Model model){
ModelAndView modelAndView = new ModelAndView("test2.html");
//modelAndView.addObject()
//....
return modelAndView;
}
}
Note:
You can specify the suffix of your view files by a ViewResolver (for example InternalResourceViewResolver or spring.mvc.view.suffix=.html in the appliction.properties file of Spring Boot and do not declare .html suffix in the above code.
Best Regard

Save a Spring Data Rest JPA entity in a custom controller and return a HAL representation

In my Spring Boot web application I have a JPA entity Medium that records information about uploaded files.
I have a basic Spring Data Rest repository to handle the generic operations:
#RepositoryRestResource(path = "/media")
public interface MediumRepository extends CrudRepository<Medium, Long> {
}
However, I need the client to upload a file using HTTP multipart upload, then create a Medium record and return it in the response. The structure of the response should be the same as calling repository.save(). What I cannot figure out is how to have the HATEOAS metadata added. Obviously, if I just return
return mediumRepository.save(medium);
it will return a basic JSON representation of the entity, no HATEOAS. I already learned that I should probably use a PersistentEntityResourceAssembler.
So, my current controller code is:
#RestController
#RequestMapping("/upload")
public class MediaEndpoint {
#Autowired
private MediumRepository mediumRepository;
#RequestMapping(method = POST)
public PersistentEntityResource uploadMedium(
#RequestPart MultipartFile data,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Medium medium = new Medium();
// setup of the medium instance
Medium savedMedium = mediumRepository.save(medium);
return persistentEntityResourceAssembler.toResource(savedMedium);
}
}
However, I cannot get the persistentEntityResourceAssembler injected into the method - I'm getting
Failed to instantiate [org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.<init>()
How can I implement this?
Following Cepr0's answer, I changed my controller to a #RepositoryRestController, but I got an exception
Circular view path [upload]: would dispatch back to the current handler URL [/upload] again.
Check your ViewResolver setup! (Hint: This may be the result of an unspecified view,
due to default view name generation.)
I found that RepositoryRestControllers are not annotated with #ResponseBody and should return a ResponseEntity, so I changed my code to the following:
#RepositoryRestController
#RequestMapping("/upload")
public class MediaEndpoint {
#Autowired
private MediumRepository mediumRepository;
#RequestMapping(method = POST)
public ResponseEntity<PersistentEntityResource> uploadMedium(
#RequestPart MultipartFile data,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Medium medium = new Medium();
// setup of the medium instance
Medium savedMedium = mediumRepository.save(medium);
return ResponseEntity.ok(persistentEntityResourceAssembler.toResource(savedMedium));
}
}
This gives me a nice JSON response with HATEOAS metadata.
Alternatively, annotating the method or the controller with #ResponseBody works the same way.
Try to use #RepositoryRestController instead of #RestController.

How to create a default method in SpringMVC using annotations?

I can't find a solution to this, and it's driving me crazy. I have #Controller mapped that responds to several methods using #RequestMapping. I'd like to tag one of those methods as default when nothing more specific is specified. For example:
#Controller
#RequestMapping("/user/*")
public class UserController {
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
So /user/login -> login method, /user/logout -> logout, etc. I'd like to make it so that if someone goes to /user then it routes to one of these methods. However, I don't see anything on #RequestMapping that would allow me to specify one of these methods as a default handler. I also don't see any other annotations that might be used on the class either to do this. I'm beginning to suspect it doesn't exist.
I'm using Spring 2.5.6. Is this solved in 3.0.0? I might just hack Spring to make it work because it's tremendously annoying this isn't more straightforward.
Thanks in Advance.
Take a look at this answer:
Spring MVC and annotated controllers issue
What if you annotate a method with:
#RequestMapping(method = RequestMethod.GET)
You can see an example here:
Spring 3.0 MVC + Hibernate : Simplified with Annotations – Tutorial
The same behavior can be seen here:
Spring Framework 3.0 MVC by Aaron Schram (look at page 21)
Short answer: I do not know how to simply specify one method as default with a simple tag.
But there is this ...
I do not know in which version of Spring this was implemented, but you can provide multiple values to #RequestMapping in 3.1.2. So I do this:
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(value = {"", "/", "/list"}, method = RequestMethod.GET)
public String listUsers(ModelMap model) { }
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView add(HttpServletRequest request, ModelMap model) { }
}
The following URLs then map to listUsers():
http://example.com/user
http://example.com/user/
http://example.com/user/list
I would create one default method without RequestMapping's value in there. Please see method defaultCall() below. You can then simply call them with URL: [yourhostname]/user
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String defaultCall( MapModel model ) {
//Call the default method directly, or use the 'forward' String. Example:
return authenticate( model );
}
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
Ref: Spring Framework Request Mapping
Simply using #RequestMapping("**") on your default method should work. Any more specific mappings should still pick up their requests. I use this method for setting up default methods sometimes. Currently using Spring v4.3.8.RELEASE.

Categories

Resources