Exchanging Spring Hypermedia Resources with HAL+JSON CURIEs via RestTemplate - spring

I am using Spring Framework 4.1.0 and Spring HATEOAS 0.16.0 to develop both a Spring web application and a Spring test client for that application.
The test client has a statement like:
ResponseEntity<Resource<Calculation>> response = restTemplate.exchange(
calculationsUri,
HttpMethod.POST,
new HttpEntity<Calculation>(calculation),
new ParameterizedTypeReference<Resource<Calculation>>()
);
...wherein Calculation is a POJO with Jackson annotations (for example, #JsonProperty).
Without CURIEs, that RestTemplate.exchange() invocation succeeds: response.getBody().getLinks() returns a non-null non-empty instance of List<Link>.
My web application has non-standard link relations, for example, "sub-calculations". I want to use CURIEs.
With CURIEs, that RestTemplate.exchange() invocation fails: The response-deserialization code throws org.springframework.http.converter.HttpMessageNotReadableException, caused by com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
'Could not read JSON: Unrecognized field "name" (class org.springframework.hateoas.Link), not marked as ignorable (one known property "href"])'
In particular, Jackson fails to deserialize the CURIE(s) from the _links map in the response JSON to the List<Link>-typed field org.springframework.hateoas.ResourceSupport.links. The response JSON looks like:
{
"_links" : {
"self" : {
"href" : "..."
},
"myNamespace:sub-calculations" : [ {
"href" : "..."
}, {
"href" : "..."
} ],
"curies" : [ {
"href" : ".../{rel}",
"name" : "myNamespace",
"templated" : true
} ]
}
}
How may I use RestTemplate.exchange() to obtain a resource whose HAL+JSON ("application/hal+json") representation uses CURIEs?

looks like the library simply doesn't support the name field of HAL link object https://datatracker.ietf.org/doc/html/draft-kelly-json-hal-06#section-5.5 ...doesn't really have anything to do with CURIE's. You should open an issue with that library to support all the fields of HAL link objects.
As far as CURIE #CCCV in your example they key is AWALYS myNamespace:sub-calculations no matter if CURIE is present or not. CURIE just lets you dereference to a URL that SHOULD link to documentation. It's kinda weird, and i see implementations get it wrong all the time thinking the URI is what matters. see https://groups.google.com/d/msg/hal-discuss/lt0CnC3eev4/YinN1Us54KcJ i'm not saying i agree with it..but that's how it's supposed to be

Related

Java swagger-ui doesn't render allowableValues for parameter

I have a SpringBoot, JAX-RS, and Maven app. I'm using Swagger annotations to provide info on the REST service interface. It basically works, but I'm having trouble with some parameters that I expect a limited set of values. I believe I'm specifying the "#Api..." annotations correctly, and I can see the expected results in the swagger.json file, but the swagger-ui doesn't appear to do anything with that information.
My pom.xml appears to specify version 1.5.20 of the swagger artifacts.
The following is a heavily elided excerpt from the Java interface:
#GET
#Path("...")
#ApiOperation("...")
#ApiImplicitParams({
...
#ApiImplicitParam(name = "poi_types", value = "Types of locations to include",
allowableValues = "pos, wifi, country",
dataType = "string", paramType = "query"),
...
})
public Object ...(#QueryParam(...)
#ApiParam(name = ..., value = "...")
String ...) {
In the swagger.json, I see the following for that entry:
{
"name" : "poi_types",
"in" : "query",
"description" : "Types of locations to include",
"required" : false,
"type" : "string",
"enum" : [ "pos", "wifi", "country" ]
}
In the generated UI, I see the following:
I've seen somewhere some mentions of possible disconnects between the required schema and what swagger-ui renders, like perhaps requiring a "schema" element in the parameter definition that includes the "type"and "enum" properties. I tried manually changing the swagger.json to include that, but it made no difference.
Can anyone provide any background here?
Update:
I upgraded to swagger-core and swagger-annotations v1.6.2. I also tried putting "allowableValues" into an "#ApiParam" instead of just an "#ApiImplicitParam". Neither of these changes made any difference. I don't see any indication in the UI of the allowable values.
This is the changed element from the #ApiParam change:
{
"name" : "isocc2",
"in" : "query",
"description" : "Country code",
"required" : false,
"type" : "string",
"enum" : [ "en", "es" ]
}
This is how this displays in the swagger UI:
I also verified from the browser the swagger.json that it loaded, and it matches what I expected.
Just in case, I tested it in Chrome in addition to Firefox.
What else could be wrong here?
Have you tried using ?
public Object ...(#QueryParam(...)
#ApiParam(name = ..., value = "...",
allowableValues = "pos, wifi, country",)
String poi_types) {
allowableValues property works well on #ApiParam.

How to describe standard Spring error response in springdoc Schema?

The default server response of a SpringBoot app in case of unhandled errors is
{
"timestamp": 1594232435849,
"path": "/my/path",
"status": 500,
"error": "Internal Server Error",
"message": "this request is failed because of ...",
"requestId": "ba5058f3-4"
}
I want to describe it in a Springdoc annotation for routes of an application.
Assuming that there is a standard class DefaultErrorResponse (just a mock name), it could look like the following:
#Operation(
// ... other details
responses = {
// ... other possible responses
#ApiResponse(
responseCode = "500",
content = #Content(schema = #Schema(implementation = DefaultErrorResponse.class)))
}
)
In a worse case scenario such class does not exists and Spring uses just a Map under the hood for this response creation. Then this annotation will be more verbose, including explicit mention of every field contained in the response.
Obviously for most of the routes this part #ApiResponse(responseCode="500",... is the same, and it would be good to reduce duplication.
What is the proper way to introduce description of the default error response in the documentation?
For error Handling, you use #RestControllerAdvice in combination #ExceptionHandler, in order to refactor the error handling.
These spring annotations are scanned automatically by springdoc-openapi. Without the need to add any additional swagger annotation.

How to access one element of a REST collection through HATEOAS links?

I'm trying to build an architecture of RESTful services, and to build a gateway service for all of those, with Java Spring. In order to make the latter, I need to implement a client for the other services, which me and my colleagues tried to design around the HATEOAS principle, by providing links to related resources through spring-hateoas module.
Let's say I have a service running on localhost, listening on 8080 port, which returns a collection of resources with a GET operation on /resources. For example:
{
"_embedded" : {
"resources" : [ {
"label" : "My first resource!",
"resourceId" : 3,
"_links" : {
"self" : {
"href" : "http://localhost:8080/resources/3"
},
"meals" : {
"href" : "http://localhost:8080/resources",
"templated" : true
}
}
}, {
"label" : "Another resource!",
"resourceId" : 4,
"_links" : {
"self" : {
"href" : "http://localhost:8080/resources/4"
},
"meals" : {
"href" : "http://localhost:8080/resources",
"templated" : true
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/resources",
"templated" : true
}
}
}
I'm trying to use a HATEOAS client such as Traverson. How could I follow a resource element simply by following HATEOAS links? My solution so far has been to add a link to item on my collection, such as follow:
"_links" : {
"self" : {
"href" : "http://localhost:8080/resources",
"templated" : true
},
"item" : {
"href" : "http://localhost:8080/resources/{id}",
"templated" : true
}
}
So then I can replace the id directly in the template with Traverson and follow the result. But is it a good practice? Should I proceed another way?
Simply put, Traverson is meant to find a link.
In the simplest cases, each link has a unique name (rel). By simply providing the name of the rel to Traverson's follow(...) function, it will use the proper LinkDiscoverer and navigate to the corresponding URI of that rel.
This is a Hop.
Since the goal is to navigate the API kind of like following links on a webpage, you must define a chain of hops.
In your case, it's a little more complication, since you have an embedded with multiple items. Asking for the self link isn't straightforward, since you can easily see three on the root document.
Hence Traverson's support for JSON-Path. If you check the reference documentation, it's easy to see that a JSON-Path expression can be supplied to help pick which link you want.
As long as the attribute being selected is the URI, then Traverson will "hop" to it.
NOTE: When simply using rels, you can supply multiple rels as strings in follow(...). When using anything else, like a JSON-Path expression or rel(...), then use one follow(...) per hop. Thankfully, this isn't hard to read of you put each hop on a separate line (again, see ref docs for examples).

Spring Cloud Config Server - Response Description

Is there any documentation which provides description of all the elements in Spring Cloud Config Server's response
{
"name":"myapp",
"profiles":[
"default"
],
"label":null,
"version":null,
"state":null,
"propertySources":[
{
"name":"vault:myapp",
"source":{
"foo":"myappsbar"
}
},
{
"name":"vault:application",
"source":{
"baz":"bam",
"foo":"bar"
}
}
]
}
Based on source code it's:
Simple plain text serializable encapsulation of a list of property sources. Basically a DTO for {#link org.springframework.core.env.Environment}, but also applicable outside the domain of a Spring application.
See: https://github.com/spring-cloud/spring-cloud-config/blob/master/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/environment/Environment.java
and
https://github.com/spring-cloud/spring-cloud-config/blob/master/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/environment/PropertySource.java
Please refer https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_server.html#vault-backend It has a response object which is the same as your question and the documentation can help you understand each and every parameter in that object.

Accessing and updating association resources with Spring Data REST and MongoDB

I don't fully understand how creating and accessing association resources works with Spring Data REST and MongoDB. I have noticed a few strange behaviours, and I am hoping to get some clarification here.
I have a simple object model where one resource type (Request) contains a list of objects of another resource type (Point). Both resources are top-level resources, i.e. have their own Repository interfaces.
I would like to be able to add a new Point to a Request by POSTing to /requests/{id}/points with a link to an existing point (as I know from this question that I can't just POST a point as a JSON payload).
Furthermore, I would like to be able to GET /requests/{id}/points to retrieve all the points associated with a request.
Here is my object model (getters/setters omitted):
Request.java
public class Request {
#Id
private String id;
private String name;
#DBRef
private List<Point> points = new ArrayList<>();
}
RequestRepository.java
public interface RequestRepository extends MongoRepository<Request, String> {
}
Point.java
#Data
public class Point {
#Id
private String id;
private String name;
}
PointRepository.java
public interface PointRepository extends MongoRepository<Point, String> {
}
Let's say that I POST a new Request. Then doing a GET on /requests gives me this:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/requests",
"templated" : false
}
},
"_embedded" : {
"requests" : [ {
"name" : "request 1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/requests/55e5dc47b7605d55c7c361ba",
"templated" : false
},
"request" : {
"href" : "http://localhost:8080/requests/55e5dc47b7605d55c7c361ba",
"templated" : false
},
"points" : {
"href" : "http://localhost:8080/requests/55e5dc47b7605d55c7c361ba/points",
"templated" : false
}
}
} ]
}
}
So I have a points link in there. Good. Now, if I do a GET on /requests/55e5dc47b7605d55c7c361ba, I notice strange thing #1. There is no points link anymore:
{
"name" : "request 1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/requests/55e5dc47b7605d55c7c361ba",
"templated" : false
},
"request" : {
"href" : "http://localhost:8080/requests/55e5dc47b7605d55c7c361ba",
"templated" : false
}
}
}
But anyway forget about that for now, let's POST a point (which gets ID 55e5e225b76012c2e08f1e3e) and link it to the request:
curl -X PUT -H "ContentType: text/uri-list" http://localhost:8080/requests/55e5dc47b7605d55c7c361ba/points
-d "http://localhost:8080/points/55e5e225b76012c2e08f1e3e"
204 No Content
That seemed to work. Ok, so now if I do a GET on http://localhost:8080/requests/55e5dc47b7605d55c7c361ba/points, I expect to see the point I just added, right? Enter strange thing #2, I get the following response:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/requests/55e5dc47b7605d55c7c361ba/points",
"templated" : false
}
}
}
So at this point I realise I've done something wrong. Could somebody please explain to me what's happening here? Do I have a fundamental misunderstanding of the way SDR is working? Should I even be using DBRefs? Or is this simply not possible with MongoDB?
Thanks in advance, and sorry for the long question!
Edit
The Points are in fact being added as DBRefs (verified by looking at the db manually), but it appears that SDR is not giving them back. If I explicitly ask for http://localhost:8080/requests/55e5dc47b7605d55c7c361ba/points/55e5e225b76012c2e08f1e3e, then I get the following response:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/points/55e5e225b76012c2e08f1e3e",
"templated" : false
},
"point" : {
"href" : "http://localhost:8080/points/55e5e225b76012c2e08f1e3e",
"templated" : false
}
}
}
Which is also strange... where is my Point.name?
Both aspects are caused by a glitch in Spring Framework 4.2.0, which Spring Boot 1.3 M4 has upgraded to which causes Spring Data RESTs custom serializers not applied in some cases. This is already fixed in Spring 4.1.2. To upgrade your Gradle project to that version, upgrade to the Boot plugin to a 1.3 and then fix the Spring version to 4.2.1 using the following declaration:
ext['spring.version'] = '4.2.1.RELEASE'
Make sure you properly initialize the collection with an empty one as a null value will cause 404 Not Found being returned. An empty list will produce correct HAL output.
Generally speaking association resources should be used with already existing resources, esp. with Spring Data MongoDB there's no other choice as it doesn't have persistence by reachability. I.e. you'd need to persist the related instance first (aka. POST to it's collection resource and get a Location header back) and the assign the just created resource (aka. PUT or POST the just obtained Location to the association resource), which seems to be what you're doing.

Resources