Spring Data REST Controller under configured base path - java

I'm using Spring Boot 1.3.1.RELEASE and trying to create a custom Repository + Controller. I configured the basePath to be /api and cannot figure out how to put the custom Controller's URI to automatically be relative to basePath. Is there some piece of magic I'm missing?
Here's the controller. I've tried every combination of the attributes below as well. I left in the commented out ones so you can see what I've tried.
#RepositoryRestController
// #Controller
#ExposesResourceFor(AnalystSummaryModel.class)
// #RequestMapping("/analystsummaries")
public class AnalystSummaryController {
#Autowired
AnalystSummaryRepository repository;
#Autowired
private AnalystSummaryResourceAssembler resourceAssembler;
#ResponseBody
#RequestMapping(method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public PagedResources<AnalystSummaryModel> getAnalystSummaries(
final Pageable pageable,
final PagedResourcesAssembler assembler) {
final Page<AnalystSummaryModel> analystSummaries = repository.findAll(pageable);
return assembler.toResource(analystSummaries, resourceAssembler);
}
}
I also create a ResourceProcessor based on another question.
When I view the /api endpoint, I see the following:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080"
},
"profile" : {
"href" : "http://localhost:8080/api/profile"
}
}
}
When I uncomment the #RequestMapping, I then get:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/analystsummaries"
},
"profile" : {
"href" : "http://localhost:8080/api/profile"
}
}
}
What am I missing to have the mapping be relative to basePath, which I set in application.yml to the following?
spring.data.rest.base-path: /api
Some more information:
Using #BasePathAware actually results in this controller service two different URIs! It shows up at / as well as /api/analystSummaries (because of the auto-pluralization, etc). Then when using ControllerLinkBuilder it uses the path of the first. So my updated question is: Why is this exposed twice? How can I eliminate the implicit root (since there is no #RequestMapping) and keep the one that is under /api?

Get rid of spring.data.rest.base-path: /api and add this:
server.servlet-path: /api

This can be done with a specific configuration since Spring boot 1.4.0.RC1.
See my answer at How to configure a default #RestController URI prefix for all controllers?

Related

Cant understand why RepositoryRestController does not work?

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.

HAL-style rendering issues with spring-hateoas

This time I'm really confused and stuck...
I'm working on a spring-boot (1.5.3) application exposing a REST API with HAL style using spring-hateoas library. Some of the endpoints already work correctly and return HAL-links. Currently I'm trying to change another endpoint to also return the response with HAL, but the result is serialized in the "collection+json" style.
What I found so far:
There are 3 ObjectMapper instances in the spring context:
objectMapper
_halObjectMapper
halObjectMapper
I converted the response with all three of these and found that the first two produce "collection+json" output ("links" with "rel") and the third one produces the expected HAL style ("_links" without "rel").
Why are there three and how can I inspect and control which of these objectMappers get used for rendering the response? How is it possible that my other endpoints return the expected format?
Here's my simplified code, in case it's of any interest:
#RestController
public class SearchController{
#RequestMapping(value = "/search", produces = { "application/hal+json" }, method = RequestMethod.GET)
public ResponseEntity<SearchResponse> search(...){
SearchResponse searchResponse = ...; // search & build response
return new ResponseEntity<>(searchResponse, HttpStatus.OK);
}
}
public class SearchResponse {
#JsonProperty("_meta")
private PageSortInfo meta;
#JsonUnwrapped
private Resources<Resource<SearchResultItem>> resultlist;
...// getters & setters
}
The response is supposed to look like this:
{
"_meta" : {
...
},
"_embedded" : {
"searchResultItemList" : [ {
...,
"_links" : {
"self" : {
"href" : "..."
}
}
},
...
]
},
"_links" : {
"self" : {
"href" : "http://localhost:8882/api/search"
}
}
}
Well, after another day of digging through google search results, sprint-boot code and a lot of trial & error I finally found the answer, which is of course trivial:
Spring will choose the MessageConverter used to convert the response object to the appropriate format by matching the registered converters to the type of the object (in my case SearchResponse) and the response type ("application/hal+json" for me). The halJacksonHttpMessageConverter will only be chosen if the response object is of type ResourceSupport. SO all I had to do is make my SearchResponse extend ResourceSupport and I got the expected response format.
public class SearchResponse extends ResourceSupport {...}

Spring Data Rest does not fetch nested objects from Neo4j

Following "_links" from an Object the return Json object is empty. I suppose this is because of the lack of automatic lazy fetching in SDN.
Is there an easy way to tell SDR to fetch objects before returning them?
Example:
.../questions/1131 returns the following JSON:
{
//...
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/questions/1131"
},
"askedBy" : {
"href" : "http://localhost:8080/api/questions/1131/askedBy"
}
//...
}
}
Clicking on .../questions/1131/askedBy should return a User, but it returns a User object, where all the properties are null, except the links, which are correct.
How can I tell SDR to fetch these embedded objects before converting them to JSON?
It feels a bit hackish, but I have found a working solution.
#Bean
#Transactional
public ResourceProcessor<Resource> fetchProcessor() {
return new ResourceProcessor<Resource>() {
#Autowired
Neo4jTemplate template;
#Override
public Resource process(Resource resource) {
final Object content = resource.getContent();
if (content != null) {
template.fetch(content);
}
return resource;
}
};
}
Also, I think it is a bit overkill, as it calls template.fetch() even if the object is already populated.
A better idea, anyone?

How to mix Spring Data Repositories and Spring Rest Controllers

Currently I am exposing a few Spring Data Repositories as RESTful services by annotating them with #RepositoryRestResource like this:
#RepositoryRestResource(collectionResourceRel = "thing1", path = "thing1")
public interface Thing1Repository extends PagingAndSortingRepository<Thing1, String> {}
#RepositoryRestResource(collectionResourceRel = "thing2", path = "thing2")
public interface Thing2Repository extends CrudRepository<Thing2, String> {}
This all works great. When you hit my first endpoint is also shows all the Spring Data Repositories I have exposed, like this:
{
_links: {
thing1: {
href: "http://localhost:8080/thing1{?page,size,sort}",
templated: true
},
thing2: {
href: "http://localhost:8080/thing2"
}
}
}
Now I have some endpoints I want to expose that cannot be represented by Spring Data Repositories, so I am using a RestController.
Here is a simple example:
#RestController
#ExposesResourceFor(Thing3.class)
#RequestMapping("/thing3")
public class Thing3Controller {
#Autowired
EntityLinks entityLinks;
#Autowired
Thing3DAO thing3DAO;
//just assume Thing3.class extends ResourceSupport. I know this is wrong, but it makes the example shorter
#RequestMapping(value = "/{id}", produces = "application/json")
Thing3 thing3(#PathVariable("id") String id)
{
Thing3 thing3 = thing3DAO.findOne(id);
Link link = entityLinks.linkToSingleResource(Thing3.class, id);
thing3.add(link);
return thing3;
}
}
Now if I run this app and go to:
http://localhost:8080/thing3/{id}
I do get a JSON representation of the Thing3 with a link to itself, that works as expected.
What I want to figure out how to do is have the first endpoint also describe this controller. I basically want this:
{
_links: {
thing1: {
href: "http://localhost:8080/thing1{?page,size,sort}",
templated: true
},
thing2: {
href: "http://localhost:8080/thing2"
},
thing3: {
href: "http://localhost:8080/thing3"
}
}
}
What do I need to do to get my base endpoint to have a link to this controller?
You could override RepositoryLinkResource, and add a resource pointing to your thing3:
resource.add(ControllerLinkBuilder.linkTo(Thing3Controller.class).withRel("thing3"));
Check this question: Custom response for root request int the Spring REST HATEOAS with both RepositoryRestResource-s and regular controllers

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