I am trying to write a Java program to consume the output from a REST-based web service I wrote utilizing the tutorials at spring.io. When I run SpringBoot bootRun the JSON output is built nicely with embedded data and links, and looks like this:
{
"_embedded": {
"invoiceList": [
{
"id": 4,
"seqNum": 1,
"fileId": null,
"fileName": null,
"invoiceNumber": "10080",
"invoiceDate": "2018-06-18T05:00:00.000+0000",
"invoiceTotal": 1000,
"sourceLastModified": "2018-11-30T16:22:23.000+0000",
"lastModified": "2018-11-30T16:22:23.000+0000",
"validFrom": "2018-11-30T16:22:23.000+0000",
"validTo": "9999-12-31T06:00:00.000+0000",
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/4"
},
"invoices": {
"href": "http://localhost:8086/vadir/dental/invoices"
},
"claims": {
"href": "http://localhost:8086/vadir/dental/claims?invoice=10080"
}
}
},
{
"id": 5,
"seqNum": 1,
"fileId": null,
"fileName": null,
"invoiceNumber": "10080",
"invoiceDate": "2018-06-18T05:00:00.000+0000",
"invoiceTotal": 500,
"sourceLastModified": "2018-11-30T16:22:23.000+0000",
"lastModified": "2018-11-30T16:22:23.000+0000",
"validFrom": "2018-11-30T16:22:23.000+0000",
"validTo": "9999-12-31T06:00:00.000+0000",
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/5"
},
"invoices": {
"href": "http://localhost:8086/vadir/dental/invoices"
},
"claims": {
"href": "http://localhost:8086/vadir/dental/claims?invoice=10080"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/last"
}
}
}
I have found the documentation for Traverson and the SO question at Consuming HAL-based REST. However, when I use
ParameterizedTypeReference<Resources<Resource<DentalInvoice>>> parameterizedTypeReference =
new ParameterizedTypeReference<Resources<Resource<DentalInvoice>>> () {
};
Traverson traverson =
new Traverson (new URI ("http://localhost:8086/vadir/dental/invoices/last"), MediaTypes.HAL_JSON);
Resources<Resource<DentalInvoice>> invoiceResources = traverson
//.follow ((String) null)
.follow ("$._embedded.invoiceList")
.toObject (parameterizedTypeReference);
I get an error: Illegal character in scheme name at index 0: [{"id":4,...
It looks like Traverson is expecting the content of the follow reference to be a link rather than an embedded object. How can I get Traverson to parse the embedded object into a list of Resources of DentalInvoices?
Do I have to create a controller method that only outputs the links to the possible actions just so Traverson has a link to follow?
Related
"_embedded": {
"employees": [
{
"id": "1",
"name": "Some Employee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
}
}
},
{
"id": "2",
"name": "Some Employee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/2"
}
}
},
{
"id": "3",
"name": "Some Employee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
}
}
}
]
},
{
"_links": {
"self": {
"href": "http://localhost:8080/employees{?page,size,sort}"
},
"search": {
"href": "http://localhost:8080/employees/search"
}
},
"page": {
"size": 20,
"totalElements": 3,
"totalPages": 1,
"number": 0
}
}
I'm reading a third-party Rest service that returns json content with the above structure or similar (content-type is application/json).
Currently, I created a custom json converter to a generic object that has custom objects for links,pages and a generic type for the embedded object. So that, I read it in some sort of generic way. However, I would like to do it via hateoas objects. I tried several approaches but any of them are working.
Following others questions I tried many things like configuring the RestTamplate
#Bean
public OAuth2RestTemplate getOAuth2Rest() {
[...]
OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails, clientContext);
List<HttpMessageConverter<?>> existingConverters = template.getMessageConverters();
List<HttpMessageConverter<?>> newConverters = new ArrayList<>();
newConverters.add(getHalMessageConverter());
newConverters.addAll(existingConverters);
template.setMessageConverters(newConverters);
return template;
}
private HttpMessageConverter getHalMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter halConverter =
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
halConverter.setObjectMapper(objectMapper);
return halConverter;
}
Then
ResponseEntity<Resources<MyDto>> entity = restTemplate.exchange(
myUri,
HttpMethod.GET, null,
new ParameterizedTypeReference<Resources<MyDto>>() {}, Collections.emptyMap());
There are similar questions like Consuming Spring Hateoas Pageable but they are consuming json+hal content. I think here is my problem because the deserializer uses the json converter and not the hal converter.
I tried
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
Didn't work and interferes with my REST configuration.
I tried this blog
I am using spring data rest for my application.
I have disabled to hal json type by using the following:-
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.useHalAsDefaultJsonMediaType(false);
}
My response looks like the following for empty object:-
{
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": [
{
"relTargetType": "com.sample.User",
"collectionValue": true,
"value": [ ]
}
]
}
When I have data for user, It looks like the following:-
links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": [
{
"id": 2,
"key": "wire",
"name": "Wireless",
"content": [ ],
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users/2"
},
{
"rel": "user",
"href": "http://localhost:8081/users/2"
}
]
}..
My concern is to have empty content collection like as below:-
{
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": []
}
How can I handle empty collection in spring data rest ? Kindly help me
We are trying to parse the below JSON to get a list of people.
JSON Response:
{
"_embedded": {
"people": [
{
"id": 35356,
"name": "Jon",
"description": "Test",
"type": "person",
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people/35356"
},
"items": {
"href": "http://localhost/api/v1/50452/items?person_id=35356"
},
"enabled_services": [
{
"title": "Water Company",
"href": "http://localhost/api/v1/50452/services/103890"
}
]
}
},
{
"id": 46363,
"name": "Kevin",
"description": "",
"type": "person",
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people/46363"
},
"items": {
"href": "http://localhost/api/v1/50452/items?person_id=46363"
},
"enabled_services": [
{
"title": "Water Company",
"href": "http://localhost/api/v1/50452/services/103890"
}
]
}
}
]
},
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people"
}
}
}
Our code:
ParameterizedTypeReference<Resources<Person>> resource = new ParameterizedTypeReference<Resources<Person>>() {};
Traverson traverson = new Traverson(new URI("http://localhost/api/v1/people"), MediaType.APPLICATION_JSON_UTF8);
// Create our own LinkDiscoverer as our service returns application/json instead of application/json+hal
List<LinkDiscoverer> linkDiscoverers = new ArrayList<>();
linkDiscoverers.add(new JsonPathLinkDiscoverer("$._links..['%s']..href", MediaType.APPLICATION_JSON_UTF8));
traverson.setLinkDiscoverers(linkDiscoverers);
HttpHeaders headers = new HttpHeaders();
headers.add("App-Key", Globals.Appkey);
headers.add("App-Id", Globals.AppId);
Resources<Person> personResources = traverson.follow("people").withHeaders(headers).toObject(resource);
However we are getting the following error:
java.lang.IllegalStateException: Expected to find link with rel 'people' in response {"_embedded":{"people":[{"id":31350,"name":"Jon Blue","description":"Developer","type":"person","deleted":false,"disabled":false,"company_id":50452,"order":31350,"phone_prefix":"44","_links":{"self":{"href":"http://localhost/api/v1/50452/people/31350"},"items":{"href":"http://localhost/api/v1/50452/items?person_id=31350"},"enabled_services":[{"title":"Water ...
Based on the (very limited) client docs this seems to be the correct way to do things. Does anyone know what we might be missing here?
Thanks
The Traverson is meant to be used to follow links:
Component to ease traversing hypermedia APIs by following links with
relation types. Highly inspired by the equally named JavaScript
library.
There is no link with rel people in your response, so it cannot be followed.
I could imagine you wanted to call the top-level resource that has a link to people:
Traverson traverson = new Traverson(new URI("http://localhost/api/v1/"), MediaType.APPLICATION_JSON_UTF8);
Otherwise I would suggest to use a RestTemplate to get the Resource:
restTemplate.exchange(
URI.create("http://localhost/api/v1/people"),
HttpMethod.GET,
new HttpEntity<Void>(headers),
resource).getBody();
I'm calling a url like this: /job/My-Job/710/api/json and it's returning some json like this:
{
"actions": [
{
"parameters": [
{
"name": "DEPLOY_HOST",
"value": ""
}
]
},
{
"causes": [
{
"shortDescription": "Started by user Hudson Admin",
"userId": "username",
"userName": "Hudson Admin"
}
]
},
{},
{},
{}
],
"artifacts": [],
"building": true,
"description": null,
"duration": 0,
"estimatedDuration": 390011,
"executor": {},
"fullDisplayName": "My-App #711",
"id": "2013-08-30_12-50-14",
"keepLog": false,
"number": 711,
"result": "SUCCESS",
"timestamp": 1377892214231,
"url": "http://hudsonurl:8081/job/My-App/711/",
"builtOn": "",
"changeSet": {
"items": [
{}
],
"kind": "svn",
"revisions": [
{
"module": "https://oursvn",
"revision": 27498
}
]
},
"culprits": [
{
"absoluteUrl": "http://hudsonsurl:8081/user/handsomeg",
"fullName": "handsome guy"
}
],
"mavenArtifacts": null,
"mavenVersionUsed": "3.0.4"
}
This build is actually in progress right now, but I can't see a way to know that. You'd think that the value of result should be in progress, but it's not. Is this a bug, or is there some other way to check? I'm using Jersey version 1.523
I just noticed there's a "building": true.
Makes me wonder what result is for. Maybe builds are considered SUCCESS until proven otherwise.
Part of REST best practice is to make use of links in the responses to allow clients to navigate from one entity to another.
For example if I had a customer object type which has child account. If I was to request a customer using /customers/1 then I might provide the following response
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button",
"accounts": [
{
"self": "http://localhost:43002/rest/v1/accounts/1",
"id": 1,
"name": "Main Account",
"number": "000001",
"currency": "GBP",
"fromDate": "2013-01-01",
"toDate": "9999-01-01",
"createdDttm": "2013-01-01T00:00:00.000"
}
]
}
Note the self property holds the links.
However let's say I didn't want to return the accounts in the customer query, perhaps the number of accounts might be very large so I don't want to return them by default.
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button"
}
A resource URL for a customer's accounts could be /customers/1/accounts
However with the customer response above the client would be unable to discover the /customers/1/accounts link.
Is there a best practice for providing hyperlinks in a response that point to "child" collections of the returned resource?
One practice is to use a links element like this:
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button",
"links" : [
{
"rel" : "http://www.yourapi.com/rels/accounts",
"href" : "http://localhost:43002/rest/v1/customers/1/accounts"
},
{
"rel" : "http://www.yourapi.com/rels/someOtherCollection",
"href" : "http://localhost:43002/rest/v1/customers/1/someOtherCollection",
}
]
}
Or, if you find easier to construct/read the response, you can put the same links as Link http headers.
provide a links attribute like in this example http://caines.ca/blog/programming/json-is-under-defined-for-rest/
{
"links": {
"self" : { "href": "{id}" },
"up" : { "href": "{upId}" },
"children" : { "href": "{id}/children" }
}
}