Can't POST a collection - spring-data-rest

I have a simple Entity with a single collection mapped.
#Entity
public class Appointment Identifiable<Integer> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private Integer id;
#Column(name="TRAK_NBR")
private String trackNumber;
#OneToMany(fetch =FetchType.EAGER, cascade= CascadeType.ALL)
#JoinColumn(name="CNSM_APT_VER_WRK_I", nullable = false)
private Set<Product> products = new HashSet<Product>();
}
#Entity
public class Product implements Identifiable<Integer> {
#Id
#Column(name = "CNSM_PRD_VER_WRK_I")
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private Integer id;
#Column(name = "PRD_MDL_NBR")
private String model;
#Column(name = "PRD_SPEC_DSC")
private String description;
}
In my application when I only include a PagingAndSortingRepository for Appointment. I can call the POST command with the following payload.
{
"trackNumber" : "XYZ123",
"products": [
{"model" : "MODEL",
"description" : "NAME"
}]
}
When I add a PagingAndSortingRepository for Product and try the same POST I get the following error message.
{
"cause" : {
"cause" : {
"cause" : null,
"message" : null
},
"message" : "(was java.lang.NullPointerException) (through reference chain: com..model.Appointment[\"products\"])"
},
"message" : "Could not read JSON: (was java.lang.NullPointerException) (through reference chain: com.model.Appointment[\"products\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.model.AppointmentVerification[\"products\"])"
}
My GET payload with both Repositories returns this. This is my desired format. The link to products should be included
{
"trackNumber" : "XYZ123",
"_links" : {
"self" : {
"href" : "http://localhost:8080/consumerappointment/appointments/70"
},
"products" : {
"href" : "http://localhost:8080/consumerappointment/appointments/70/products"
}
}
With only the Appointment repository I get the following payload and can post the list of products.
{
"trackNumber" : "XYZ123",
"products" : [ {
"model" : "MODEL",
"description" : "NAME",
} ],
"_links" : {
"self" : {
"href" : "http://localhost:8080/consumerappointment/appointments/1"
}
}
}

Let's take a step back and make sure you understand what's happening here: if a repository is detected, Spring Data REST exposes a dedicated set of resources for it to manage the aggregates handled by the repository via HTTP. Thus, if you have repositories for multiple entities related to each other, the relationship is represented as a link. This is why you see the products inlined with only the AppointmentRepository in place and the products link in place once you create a ProductRepository.
If you want to expose both repositories as resources, you need to hand the URIs of the Product instances in the payload for the POST to create an Appointment. That means, instead of posting this:
{ "trackNumber" : "XYZ123",
"products": [
{ "model" : "MODEL",
"description" : "NAME"
}
]
}
you'd create a Product first:
POST /products
{ "model" : "MODEL",
"description" : "NAME" }
201 Created
Location: …/products/4711
And then hand the ID of the product to the Appointment payload:
{ "trackNumber" : "XYZ123",
"products": [ "…/products/4711" ]}
In case you don't want any of this (no resources exposed for Product in the first place, use #RepositoryRestResource(exported = false) on PersonRepository. That would still leave you with the bean instance created for the repo, but no resources exported and the resource exposed for Appointments back to inlining related Products.

Related

Spring Hateoas: EntityModel _links rendered before content

This is a weird problem to describe since it's no actually a problem in the technical sense but still makes me curious enough to ask about it:
I created a #RestController that returns ResponseEntity<EntityModel<?>>. I build the EntityModel and attach a self link built with linkTo and methodOn. Now for some reason, the output looks like this:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/points/knx/office_light"
}
},
"labels" : {
"name" : "Light",
"room" : "Office"
},
"access" : [ "READ", "WRITE" ],
"type" : "SwitchPoint",
"state" : "OFF"
}
Contrary to other rest services I have build, the "_link" gets rendered at the top not at the bottom. Any ideas why?
#GetMapping("{ext}/{id}")
public ResponseEntity<EntityModel<Map<String, Object>>> oneByExt(#PathVariable String ext,
#PathVariable String id) {
EntityModel<Map<String, Object>> point = client.getPoint(ext, id);
return new ResponseEntity<>(localToGlobal(ext, point), HttpStatus.OK);
}
private <T> EntityModel<T> localToGlobal(String ext, EntityModel<T> model) {
ComposedId id = ComposedId.fromEntityModel(ext, model);
Link newSelfLink = linkTo(methodOn(PointController.class).oneByExt(id.getExtension(), id.getIdentifier()))
.withSelfRel();
EntityModel<T> newModel = EntityModel.of(model.getContent());
newModel.add(newSelfLink);
return newModel;
}
It's probably due to the Map, I'm assuming you using something like HashMap which has no guarantee of iteration order. Try change it to a LinkedHashMap and see what happens (should print the values in the order they were added to the map)

Spring JPA Projection not working with UUID types?

I have a project with Kotlin and Springboot.
In this project an entity have this following fields: id, name and parent.
What i want is to only get the id and the name. So i made a projection interface view for that entity.
This is my entity:
#Entity
data class Keyword(
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(columnDefinition = "BINARY(16)")
var id: UUID? = null,
#Column(columnDefinition = "BINARY(16)")
var parent: UUID? = null,
#NotBlank
var name: String? = null
)
Repository:
#Repository
interface KeywordRepository: JpaRepository<Keyword, UUID> {
#Query(value = "SELECT keyword.id, keyword.parent, keyword.name FROM keyword LEFT JOIN project_keyword ON project_keyword.keyword_id = keyword.id WHERE project_keyword.project_id LIKE :id", nativeQuery = true)
fun findAllKeywordsByProjectId(id: UUID): MutableList<KeyWordView>
}
Service:
#Service
class KeywordService (
private val projectService: ProjectService,
private val keywordRepository: KeywordRepository
) {
fun getKeywordsByProjectId(id: UUID): MutableList<KeyWordView> {
projectService.checkIfProjectExistsById(id)
return keywordRepository.findAllKeywordsByProjectId(id).toMutableList()
}
}
My projection interface class:
interface KeyWordView {
val id: UUID
val name: String?
}
When i call this endpoint via controller class. I get this output:
"list": [
{
"name": "MARINE MICROBIOTA",
"id": "0,31,-36,77,29,123,66,-25,-127,-43,-31,83,104,-90,47,10"
}
]
But if i change the val id: String to val id: UUID in my KeywordView interface, i get this following error:
{
"code": 500,
"data": null,
"message": "Could not write JSON: Projection type must be an interface!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Projection type must be an interface! (through reference chain: no.inmeta.ris.util.response.ResponseDto[\"data\"]->no.inmeta.ris.util.pagination.PageDto[\"list\"]->java.util.ArrayList[0]->com.sun.proxy.$Proxy199[\"id\"])",
"status": "FAIL"
}
Anyone know how to solve this problem? I want to receive the UUID as UUID not with the strange format.
Thank you!
I guess the problem is the UUID Class. As your entity states you defined dir hibernate that the column in db is a binary(16). And i suppose using this UUID Type in the Interface projection does not Work since there is no valid mapping information (how to transform the data coming as binary with a length of 16 bytes to the UUID Class). So I assume you have to change the Type of your column or you have to use string and write a function to Transform that String.
Another possibility is to create a second entity for that table with Just the two cols you want. And Just use that second dao as your are using yiur projection.

How to deserialize JSON having references to abstract types in Jackson

I have an issue with object references to abstract classes and JSON serialization and deserialization. The abstracted issue looks like this:
I have a graph consisting of nodes and edges. Each edge connects two nodes. Nodes can be of flavor red and green. Therefore, there is an abstract class Node and two derived classes RedNode and GreenNode. A Node takes an id (#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")):
#JsonSubTypes({
#JsonSubTypes.Type(value = GreenNode.class, name = "GreenNode"),
#JsonSubTypes.Type(value = RedNode.class, name = "RedNode")
})
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public abstract class Node {
public String id;
}
public class RedNode extends Node {
// ...
}
public class GreenNode extends Node {
// ...
}
An Edge has a source and a target of type Node, which are serialized as references (#JsonIdentityReference(alwaysAsId = true)):
public class Edge {
#JsonIdentityReference(alwaysAsId = true)
public Node source;
#JsonIdentityReference(alwaysAsId = true)
public Node target;
}
The graph is defined as follows:
public class Graph {
public List<GreenNode> greenNodes = new ArrayList();
public List<RedNode> redNodes = new ArrayList();
public List<Edge> edges = new ArrayList();
}
An example JSON looks as follows:
{
"greenNodes" : [ {
"id" : "g",
"content" : "green g",
"greenProperty" : "green"
} ],
"redNodes" : [ {
"id" : "r",
"content" : "red r",
"redProperty" : "red"
} ],
"edges" : [ {
"source" : "g",
"target" : "r"
} ]
}
Using an ObjectMapper cannot read this:
Can not construct instance of com.github.koppor.jsonidentityissue.model.Node: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
The error location is "line: 13, column: 16". Thus, it is hit at the id of the edge. The nodes themselves are properly serialized.
A workaround is to add type information in the json:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
public abstract class Node {
Then, everything works:
{
"greenNodes" : [ {
"id" : "g",
"type" : "GreenNode",
"content" : "green g",
"greenProperty" : "green"
} ],
"redNodes" : [ {
"id" : "r",
"type" : "RedNode",
"content" : "red r",
"redProperty" : "red"
} ],
"edges" : [ {
"source" : "g",
"target" : "r"
} ]
}
Then, everything works.
Is it really necessary to include type information in the referenced objects to have the reference working? Without the type information, a graph with red and green nodes (and no edges) can be loaded. After an edge comes in, it can't. However, the JSON of an edge contains an id only. The referenced objects are already parsed.
I really like to get rid off the #JsonTypeInfo annotation. Is there a way to have a clean JSON?
The full example is made available at https://github.com/koppor/jackson-jsonidentityreference-issue/tree/issue.
The current solution is to include fake type information. Full code at https://github.com/koppor/jackson-jsonidentityreference-issue.
The Node gets an existing property type, which is not written:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
public abstract class Node {
#JsonIgnore
public abstract String getType();
}
Each subclass specifies itself as defaultImpl and provides an implementation of getType:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
defaultImpl=GreenNode.class)
public class GreenNode extends Node {
#Override
public String getType() {
return "GreeNode";
}
}
This way, the JSON remains clean, but Jackson can resolve the id reference without any issues.

Jackson HttpMessageNotReadableException : Already had POJO for id

I dont understand what is wrong in birectional relationship deserializing :
Serialized Registration is :
{
uuid: "d3372f25-ac70-4735-b756-7894977142b2",
payment: [{
"uuid":"15363b3f-7057-4658-3f63-3f3f695d7019",
"type":0,
"amount":10
"registration":"d3372f25-ac70-4735-b756-7894977142b2"
},
{
"uuid":"ee9fb135-2b21-48f4-a1ee-2f71f09f44ab",
"type":0,
"amount":5
"registration":"d3372f25-ac70-4735-b756-7894977142b2"
}]
}
Java is :
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "uuid", scope = Registration.class)
public class Registration implements Serializable {}
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "uuid", scope = Payment.class)
public class Payment implements Serializable {}
Could not read document: Already had POJO for id (java.util.UUID) [[ObjectId: key=d3372f25-ac70-4735-b756-7894977142b2, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=com.mezoo.tdc.model.Registration]] (through reference chain: com.mezoo.tdc.model.Registration["payment"]->java.util.ArrayList[0]->com.mezoo.tdc.model.Payment["registration"]->com.mezoo.tdc.model.Registration["uuid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.util.UUID) [[ObjectId: key=d3372f25-ac70-4735-b756-7894977142b2, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=com.mezoo.tdc.model.Registration]] (through reference chain: com.mezoo.tdc.model.Registration["payment"]->java.util.ArrayList[0]->com.mezoo.tdc.model.Payment["registration"]->com.mezoo.tdc.model.Registration["uuid"])"
Somebody can explain to me why deserializing doesnt works please ?

jsog-jackson: Serializing object graphs

I am trying to serialize an object graph with parent/child references, essentially I have an entity that looks like this:
#Entity (name = "Container")
#JsonIdentityInfo(generator=JSOGGenerator.class)
public class Container {
public String type = "parent";
#JsonManagedReference ("child")
#OneToMany (mappedBy = "parent", cascade = CascadeType.PERSIST)
public List<Child> children;
}
#Entity (name = "Child")
#JsonIdentityInfo(generator=JSOGGenerator.class)
public class Child {
public String type = "child";
#JsonBackReference ("child")
#ManyToOne
public Parent parent;
}
when I try to serialize this to the client, then this is what I get:
{
"type": "parent",
#id: 1
"children": [
{
"type": "child",
#id: 2
},
{ ... }
]
}
I see #id properties on all objects but there is no sight of any #ref properties. If I have understood jsog and jsog-jackson right, then this is what should actually be serialized:
{
"type": "parent",
#id: 1
"children": [
{
"type": "child",
#id: 2
#ref: 1
},
{ ... }
]
}
What I would really like to have is a way of restoring the original back reference to the parent after restoring the serialized JSOG in the browser, so that instead of #ref I get the parent property in each child object back.
Your using two conflicting approaches to managing circular relationships. You can either use the JSOGGenerator OR the #JsonManagedReference and #JsonBackReference annotations.
The JSOGGenerator will include the #id and #ref properties in the JSON serialized format which is useful for deserializing the object in another language such as JavaScript.
The #JsonManagedReference and #JsonBackReference use the Java class information to identify the circular reference and subsequently that information is excluded from the JSON serialized format so another language like JavaScript can't deserialize the object because the required information is missing.
Another benefit to the JSOGGenerator is that it can handle deeply nested circular relationships as opposed to a limited parent-child relationship.