Spring-data-rest "Failed to convert" valid hateoas URI to Entity - spring-boot

We're using spring-data-rest 2.6.8 with spring-boot 1.5.8 and it's awesome! We found some strange behavior, nonetheless.
When we do a GET to /rest/students/search/findByTeacher?teacher=/rest/teachers/1 everything runs smoothly. SDR converts the teacher URI into a teacher Entity and we get a list of students.
When we provide a different URI (that resolves to the same object), the system can't do the conversion: /rest/students/search/findByTeacher?teacher=/rest/class/2/teacher
Currently we are doing this in two steps. First we GET the /rest/class/2/teacher and then we use the _links.self.href (/rest/teachers/1) to do our search.
Is there a way to configure SDR to avoid this 2-step process?

I do not think it is possible. Spring Data Rest when resolving the links works in a way that basically it grabs the url, removes baseUri from the beginning, then it tries to match next part of the URL to the repository {teachers} and then queries the repository using findOne method. In this case the url /rest/teachers/1 is simply identifier of the resource (without hateoas it would be sth like teacherID=1)
The problem with querying /rest/class/2/teacher is that you do not know to what it will be resolved - it might be single element, it might be a list, it might be a null etc. because this is not an identifier to the resource, but a link to another one.

Related

Is it possible to configure Spring Data Rest to use id as references instead of URI?

The rest of my project was designed without hypermedia in mind. So association between entity is done via database unique id.
For example, an association is done via a
post /api/user/1/address/1
Whereas I think SDR wants me to do
post /api/user/1/address
Content-Type:text/uri-list
/api/address/1
But the problem is that I don't have URI for my old entity so I have to rewrite most of the project. Or don't use SDR. But I want to use SDR because going forward it saves me a lot of boiler plate code for new entity where they just need simple CRUD.
As far as I remember, SDR only checks the last part of the link after the last slash. So the next request is enough:
post /api/user/1/address
Content-Type:text/uri-list
/1
(It definetely worked this way in v2.x, maybe there are stricter rules in v3.x, I didn't test it)

HAL clients or examples of accessing HAL API

Question: Any HAL clients or examples of accessing HAL API with admin-on-rest ?
I got started because HAL was mentioned in the first paragraph of the introduction, but now I'm having trouble finding any examples or anyone else using HAL rest client, so I am winding up for now just writing a bunch of simple findAll repositories on top of the already robust existing HAL API.
Adding a more concise answer here that isn't polluted with my thought process now that I've got it all figured out (for anyone's future reference)... Again assuming the HAL API was made with Spring Data Rest.
The four major keys to this integration are:
Exposing foreign key attributes in your JPA entities, which is required in several places by admin-on-rest #Column(name="parentEntity", updatable=false, insertable=false) private Integer parentEntityId;
Exposing all your entity IDs using RepositoryRestConfiguration.exposeIdsFor( MyEntity.class )
Annotate your repositories as #RepositoryRestResource and have them extend PagingAndSortingRepository<MyEntity, Integer>, QueryDslPredicateExecutor<MyEntity> to expose extremely useful search filters by attribute name (e.g. /api/myEntitys?field1=foo&field2=bar).
When submitting create and save requests with foreign keys make sure to adjust your params.data to include the linked resource (e.g. 'http://myserver.com/api/myEntitys/19') on top of (or in place of, HAL has no use for it) the foreign key you exposed in 1. (e.g. myEntityId=19)
Other small items of note:
use PATCH instead of PUT when updating (you may be able to use PUT if you are more of a hibernate expert and can map your entities better than I can but I had trouble getting it mapped perfectly and HAL's PATCH will take partial entities)
When submitting GET_LIST and GET_MANY_REFERENCE you get the total number of items and pagination parameters from the 'page' section of the response, and you use 'size' and 'page' query params in your API requests. (so, no need for headers and stuff)
To change the default 'equals' filter for any string entries (from 3. above) to a 'contains' filter, you will have to also extend QuerydslBinderCustomizer<QMyEntity> and provide your own customize method in each of your repositories. For example:
default void customize( QuerydslBindings bindings, QChampion champion )
{
bindings.bind( String.class ).first( ( StringPath path, String value ) -> path.contains( value ) );
}
We don't have any examples for HAL specifically. However, the point of this introduction was that admin-on-rest is backend agnostic.
You can create your own custom rest client by following the documentation. Read the code of existing ones for inspiration.
For anyone referencing this in the future, if you happen to be in control of your API through Spring Data Rest you can consider the use of an excerptProjection on every one of your existing repositories that shows an inline version of your entity. This would work if there were absolutely nothing besides admin-on-rest accessing your API.
For my case I am planning on writing a custom projection for every rest resource that has entities and naming it the same thing: "inline". Then in the admin-on-rest restClient, just always asking for the inline projection on every GET_MANY or GET_MANY_REFERENCE request.
This is the best I have at the moment. It's not perfect but for the amount of entities I have it's still many weeks faster than building a CRUD interface from scratch so I highly recommend admin-on-rest.

What is the most ideal way to add validation to Spring REST Service Request Parameters

I have a Spring-REST service that has support for GET, POST, PUT requests and they all have been mapped in a #Controller (Sorry for stating the obvious, just new to the technology)
Now each method (RequestMapping) has its own parameters like one takes in id
other takes in name and third one takes in secretKey
I want to validate these request parameters in my own custom manner
Now tried looking up as many tutorials online as possible but did not come across any solution that would best serve my situation.
Here is what I mean:
I saw a tutorial for POST request parametes by using #RequestParam or #Valid but that does not work for GET requests (That's what I read)
I saw most people recommending JS303 but that does not suit my need as I need to validate the secretKey against the DB (id and name may be I can use JSR #Size but even the id and name would need further validation)
I also saw some recommending #Validator but that would mean I will need a validator class for each and and every parameter like IdValidator, NameValidator etc
Here is something that I am hoping to accomplish:
One Validator (Can be either something that implements Validator or COnstraintValidator) however its implementation should cater to validation of all kinds of requests (can definitely have multiple methods inside it based on what request its validating) and should throw a CustomException that I created
I am not posting what I have tried because it is actually too much code that I just copy pasted from what I searched online. If you want I can post the links that I copied the code from.
P.S. I am not an expert on Spring, but trying to learn

Spring Data Rest: pass Collection<Entity> as query String parameter

First off, this is related to Spring Data Rest: How to search by another object's key? which appears to be resolved in https://jira.spring.io/browse/DATAREST-502
My issue is (I believe) and extension of this. I'm seeing the following behavior:
I have two repository queries defined e.g.
Person findByAccount(#Param("account") Account account));
Collection<Person> findByAccountIn(#Param("accounts") Collection<Account> accounts));
Both search methods are exposed via spring-data-rest. I can access the first using a url such as http://localhost:8080/people/search/findByAccount?account=http://localhost:8080/accounts/1
I can access the second method using a url such as http://localhost:8080/people/search/findByAccountIn?accounts=http://localhost:8080/accounts/1, but if I try to pass in MULTIPLE accounts, such as
http://localhost:8080/people/search/findByAccountIn?accounts=http://localhost:8080/accounts/1,http://localhost:8080/accounts/2,
It will run the query except will IGNORE the first account (http://localhost:8080/accounts/1) and only search based on the second (http://localhost:8080/accounts/2)
What is the proper technique for passing in a Collection of entities to a repository argument over the REST API? I find it works great for a single entity, but not for a Collection. Note that both of these repository methods are working as expected when directly accessing the JpaRepository.
Also note that these queries seem to work if the collection is of some primitive type, for example findByAccountIdIn(#Param("accountIds") Collection<Long> accountIds) is accessible with intended functionality via http://localhost:8080/people/search/findByAccountIdIn?accountIds=1,2. This leads me to believe it's possibly an error in the way a list of URIs is passed into a query method which expects a Collection of corresponding entities.
Thanks in advance for any help!
Try repeat the query parameter as most servers will interpret this as a list. It might not be the prettiest solution but should work.
http://localhost:8080/people/search/findByAccountIn?accounts=http://localhost:8080/accounts/1&accounts=http://localhost:8080/accounts/2
I know that this is forever old, but I found the answer.
I will put this here for anyone else who should wander this way:
How to use List in endpoint exported by Spring Data REST?
List<Person> findByAccountIn(#Param("accounts") Account... accounts);
the request would look like this:
http://localhost:8080/people/search/findByAccountIn?accounts=http://localhost:8080/accounts/1&accounts=http://localhost:8080/accounts/2&accounts=http://localhost/accounts/anotheraccountid

Using Spring Data REST to handle complex aggregate roots

Right now I can't get the concept behind Spring Data REST if it comes to complex aggregate roots. If I understand Domain Driven Design correctly (which is AFAIK the base principle for spring data?), you only expose aggregate roots through repositories.
Let's say I have two classes Post and Comment. Both are entities and Post has a #OneToMany List<Comment> comments.
Since Post is obviously the aggregate root I'd like to access it through a PostRepository. If I create #RepositoryRestResource public interface PostRepository extends CrudRepository<Post, Long> REST access to Post works fine.
Now comments is renderd inline and is not exposed as a sub resource like /posts/{post}/comments. This happens only if I introduce a CommentRepository (which I shouldn't do if I want to stick to DDD).
So how do you use Spring Data REST properly with complex domain objects? Let's say you have to check that all comments does not contain more than X characters alltogether. This would clearly be some invariant handled by the Post aggregate root. Where would you place the logic for Post.addComment()? How do you expose other classes as sub resources so I can access /posts/{post}/comments/{comment} without introducing unnecessary repositories?
For starters, if there is some constraint on Comment, then I would put that constraint in the constructor call. That way, you don't depend on any external validation frameworks or mechanisms to enforce your requirements. If you are driven to setter-based solutions (such as via Jackson), then you can ALSO put those constraints in the setter.
This way, Post doesn't have to worry about enforcing constraints on Comment.
Additionally, if you use Spring Data REST and only define a PostRepository, since the lifecycle of the comments are jointly linked to the aggregate root Post, the flow should be:
Get a Post and its collection of Comment objects.
Append your new Comment to the collection.
PUT the new Post and its updated collection of Comment objects to that resource.
Worried about collisions? That's what conditional operations are for, using standard HTTP headers. If you add a #Version based attribute to your Post domain object, then every time a given Post is updated with a new Comment, the version will increase.
When you GET the resource, Spring Data REST will include an E-Tag header.
That way, your PUT can be conditionalized with an HTTP If-Match: <etag> header. If someone else has updated the entity, you'll get back a 412 Status code, indicating you should refresh and try again.
NOTE: These conditional operations work for PUT, PATCH, and DELETE calls.

Resources