The problem
I have three entities (taken from the Spring Data REST Exporter Example): Person, Address and Profile. A Person can have addresses and profiles.
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
@Version
private Long version;
@OneToMany
private List<Address> addresses;
@OneToMany
private Map<String, Profile> profiles;
// getters and setters
}
In the client side I use Spring's RestTemplate. I added the Jackson2HalModule to the ObjectMapper used by the MappingJackson2HttpMessageConverter used by my RestTemplate.
Since Address and Profile do not have references to other entities I can POST them to my Spring Data REST server, and they are successfully saved:
final ResponseEntity<Resource<Address>> response = restTemplate.postForEntity("http://localhost:8080/addresses",
addressInstance, AddressResource.class);
where AddressResource extends org.springframework.hateoas.Resource<Address>
.
But when I try to POST a Person instance
final ResponseEntity<Resource<Person>> response = restTemplate.postForEntity("http://localhost:8080/people",
personInstance, PersonResource.class);
I get a org.springframework.web.client.HttpClientErrorException: 400 Bad Request
and I think the cause is the associated Address
es and Profile
s
are serialized as normal POJOs instead as their resource URIs.
Here is the actual body of the POST request:
{
"id":null,
"name":"Jongjin Han",
"version":null,
"addresses":[
{
"id":1,
"lines":[
"1111",
"coder's street"
],
"city":"San Diego",
"province":"California",
"postalCode":"60707"
},
{
"id":2,
"lines":[
"1111",
"coder's street"
],
"city":"San Diego",
"province":"California",
"postalCode":"60707"
}
],
"profiles":{
"key1":{
"type":"a type of profile",
"url":"http://www.profileurl.com"
},
"key2":{
"type":"a type of profile",
"url":"http://www.profileurl.com"
}
}
}
I think it should be --> EDIT: It should be
{
"id":null,
"name":"Jongjin Han",
"version":null,
"addresses":[
"http://localhost:8080/addresses/1",
"http://localhost:8080/addresses/2"
],
"profiles":{
"key1":"http://localhost:8080/profiles/1",
"key2":"http://localhost:8080/profiles/2"
}
}
in fact the response body from the server is
{
"cause" : {
"cause" : {
"cause" : {
"cause" : null,
"message" : "Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable."
},
"message" : "Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable."
},
"message" : "Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable. (through reference chain: org.springframework.data.rest.example.model.Person[\"addresses\"]->java.util.ArrayList[1])"
},
"message" : "Could not read document: Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable. (through reference chain: org.springframework.data.rest.example.model.Person[\"addresses\"]->java.util.ArrayList[1]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable. (through reference chain: org.springframework.data.rest.example.model.Person[\"addresses\"]->java.util.ArrayList[1])"
}
The possible solution I'd like to implement
Since I can access the REST repositories from the client side I am looking for a way to customize the Jackson Json Serializer in order to:
- check if the object I am serializing is a REST exported entity (easy with reflection, if only I could know where to put the code) and
- If I am serializing an entity, serialize the non-association fields as usual (e.g. person's name) and the association fields as their Resource URI (e.g. person's addresses) (with reflection it should be easy to convert from an entity to its resource URI, but I do not know where to put the code once again)
I tried with Jackson's JsonSerializer and PropertyFilters for Address and Profile, but I want a serializer which serialize them as resource URI only when they are in an association.
Any hint or aternative solution will be helpful.
Something is not configured correctly.
You should not have to POST HAL formatted data to get this to work, a plain old POJO serialized to JSON should work fine with the default configuration.
I would suggest using a proxy to intercept the request and confirm the structure.
I faced the same problem and tried to solve it with several techniques. Implemented working solution - it's a dirty workaround so don't blame me for the quality of the code, probably I will clean it up later :) I wanted to test Spring Data REST API and realized that MappingJackson2HttpMessageConverter ignores @Entity relations. Setting serializer modifier didn't work correctly: null-value serializer didn't work and relations serialized with deep property serialization.
The idea of workaround is to provide CustomSerializerModifier which returns CustomSerializer for project @Entities (inherited from BaseEntity in this example). CustomSerializer performs following actions:
I don't like this monster, but it works, and sadly I didn't find any solution :/
Working solution:
BasicRestTest
CustomSerializerModifier
CustomSerializer
CustomIgnorePropertyFilter
VideoStreamRestTest
AdminApiTest