Accessing and updating association resources with Spring Data REST and MongoDB - spring

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.

Related

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).

How to remove unique index on nested Object in Mongo

I am using Spring Boot 2.0.0.M3. And I have the following object structure:
#Document(collections="note")
public class Note {
String id;
#Indexed(background=true,unique=true)
String requestid;
}
#Document(collection="noteExpression")
public class NoteExpression {
public static class Error {
private DateTime dateTime = DateTime.now();
private Note note;
private String exception;
}
String id;
//Some other fields
Error error;
}
In the object of NoteExpression, I need to store the error information when something unexpected occurs. Everything seems to be fine. But the problem is Mongo will create a unique index for the nested property Note in NoteExpression.Error.
See the result of the Mongo command below:
>db.noteExpression.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.noteExpression"
},
{
"v" : 2,
"unique" : true,
"key" : {
"errors.note.requestid" : 1
},
"name" : "errors.note.requestid",
"ns" : "test.noteExpression",
"background" : true
}
]
Unique index for requestid on the documents of Note is necessary in my system, but I don't want the unique index on the documents of NoteExpression. Is there any way to avoid creating the index on NoteExpression?
MongoDB indexes include the nested (and of course also the referenced) documents by design. It is often a headache and the only solution that I have found until now is to implement the uniqueness constraint by coding it in the save/insert operations, so you have to query the database first, check the preconditions, and then save/insert if the preconditions are met. I will investigate it further to see if Spring-Data provides some workarround for this.

Pagingandsortingrepositrory in spring data , primary key in the entity is not present in the response

I have declared pagingandSortingRepository for a JPA entity and the response to findall() is below
"_embedded" : {
"assetDashboardCustomers" : [ {
"utilization" : "80",
"_links" : {
"self" : {
"href" : "http://localhost:8080/utilbycustomer/Customer01"
},
"assetDashboardCustomer" : {
"href" : "http://localhost:8080/utilbycustomer/Customer01"
}
}
},
I do not want _links and self [HATEOAS] details, but just the plain JSON.
What is the property I should set so that I get a plain JSON in the response
The first comment answers your first question - you cannot turn off
the links. The second comment answers you 2nd question which is how to
have the ID in the JSON body.
--- By Alan hay in the comments

Spring Data Rest and excerpt Projections

I am using the inlineAddress sample of the Spring Data Rest documentation.
/persons return the address inline as expected.
Now I add a projection to the AddressRepository
#RepositoryRestResource(excerptProjection = AddressProjection.class)
Which is as below
#Projection(name = "AddressesProjection", types = Address.class)
public interface AddressProjection {
public String getStreet();
}
This is causing the /persons call to have an address projection as _embedded
{
"_embedded" : {
"persons" : [ {
"firstName" : "dfdf",
"lastName" : "2",
"addresses" : [ {
"street" : "tx",
"state" : "tx",
"country" : "dfd"
} ],
"_embedded" : {
"addresses" : [ {
"street" : "tx",
"_links" : {
"self" : {
"href" : "/api/addresses/1{?projection}",
"templated" : true
}
}
} ]
},
"_links" : {
"self" : {
"href" : " api/persons/1{?projection}",
"templated" : true
},
"addresses" : {
"href" : " /api/persons/1/addresses"
}
}
} ]
}
}
I dont know if this is expected. This behaviour is causing repeated information when I have a oneToMany relation like order/Comments and have projection on both order and comments and when I access order/1/comments I see the order also embedded for each comments.
I have a similar issue with spring-data-rest 2.5.6. So I'd like to add this.
If :
an A entity has a #OneToMany relationship to a B entity
the B entity's repository #RepositoryRestResource contains an excerptProjection
Then spring-data-rest will embed the B entity's list in any A entity (in _embedded).
If there is no excerptProjection, the list won't be embedded.
I'd like to be able to choose what I want to be embedded, but at moment, I found no solution to do so.
For anybody that is looking for the answer to this issue, I actually did find a solution.
Based on the example in Spring-RestBucks you will need to have a custom RepresentationModelProcessor on A entity.
Also consider the official Spring HATEOAS Documentation on RepresentationModelProcessor.
Applying to the above example, you would do:
public class PersonRepresentationProcessor implements RepresentationModelProcessor<EntityModel<Person>> {
private final EntityLinks entityLinks;
#Override
public EntityModel<Person> process(EntityModel<Person> personModel) {
// create new EntityModel without the embedded collection
TypedEntityLinks<Person> typedPersonLink = entityLinks.forType(Person::getId);
EntityModel<Person> newPerson = new EntityModel<>(personModel.getContent(),
personModel.getLinks());
// add more links or other modifications
return newPerson;
}
}

Exchanging Spring Hypermedia Resources with HAL+JSON CURIEs via RestTemplate

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

Resources