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 {...}
Related
I am using spring boot to develop an small rest api that returns informations about an object named Chapter in HAL representation and using pagination.
A normal HAL representation would require the following controller
#RestController
#RequestMapping("/chapters")
public class ChapterController {
#Autowired ChapterRepository chapterRepo;
#RequestMapping(value="/slice", produces="application/hal+json")
public PagedResources<Chapter> slice(Pageable p, PagedResourcesAssembler assembler){
Page<Chapter> page = chapterRepo.findAll(p);
return assembler.toResource(page);
}
}
wich will return the following
{
"_embedded":{
"chapterList":[
//list of chapters here
]
},
"_links":{
"self":{
"href":"http://localhost:8090/chapters/slice?page=0&size=20"
}
},
"page":{
"size":20,
"totalElements":4,
"totalPages":1,
"number":0
}
}
But there is one thing i want to change which is the "chapterList" nomination.
Is there a way to do it
I have a REST service endpoint that accepts JSON in the following format:
{
"name": "example",
"properties": {
"key1": "val1",
"key2": "val2"
}
}
Then I have this class
class Document {
private String name;
private Map<String, String> properties;
... setters/getters
}
And my REST service method
logger = Logger.getLogger("logger");
#POST
#Consumes(MediaType.APPLICATION_JSON)
Response addDocument(Document document) {
logger.info(document);
return Response.ok(200).build();
}
But whenever I post the JSON above it's not unmarshalling my properties. It is always null. The name property is mapped correctly..
I would gladly accept any help/clues.Thanks!
EDIT: I did not mention that I am using glassfish 4.1 and what comes with it. I don't have any lib dependency for marshal/unmarshal.
I just added Gson and created custom entity provider for JSON. It works now as expected, but I would've been more glad to not handle it myself but let the container do it with whatever is configured for JAXB.
Your JSON, the Java class the JSON will be parsed into and your JAX-RS resource method look fine:
{
"name": "example",
"properties": {
"key1": "val1",
"key2": "val2"
}
}
public class Document {
private String name;
private Map<String, String> properties;
// Getters and setters omitted
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response addDocument(Document document) {
...
return Response.ok(200).build();
}
Instead of using Gson as you mentioned in your answer, you could consider using a JSON provider which integrates with Jersey. Jackson is a good choice and Maps should work fine.
For details on how to use Jackson with Jersey, refer to this answer.
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?
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?
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