Adding a resource to a collection using spring-data-rest - spring-data

My situation:
I have an Organization that can have many workers. I am trying to add a new worker to an organization following this example by the major contributing author of the SDR project and I get various errors (including 204 but nothing happens).
Here are my entities and rest calls:
#Entity
public class Organization {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany
#JoinTable(name = "OrganizationWorker", joinColumns = {#JoinColumn(name = "OrganizationID")},
inverseJoinColumns = {#JoinColumn(name = "WorkerID")})
private Set<Worker> workers = new LinkedHashSet<>();
}
public class Worker {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#NotNull
private String givenName;
#NotNull
private String familyName;
#NotNull
private LocalDate dob;
#NotNull
private String nationalId;
private byte[] photo;
}
GET http://localhost:8080/hal
{
"_links": {
"workers": {
"href": "http://localhost:8080/hal/workers{?page,size,sort}",
"templated": true
}
}
}
POST http://localhost:8080/hal/workers
{
"givenName": "James",
"familyName": "Bond",
"dob": "1970-01-01",
"nationalId": "XXX-60-XXXX",
"photo": null,
}
Response:
Location: http://localhost:8080/hal/workers/8
Date: Mon, 02 May 2016 16:53:02 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application
Content-Type: application/hal+json;charset=UTF-8
{
"givenName": "James",
"familyName": "Bond",
"dob": "1970-01-01",
"nationalId": "XXX-60-XXXX",
"photo": null,
"_links": {
"self": {
"href": "http://localhost:8080/hal/workers/8"
},
"worker": {
"href": "http://localhost:8080/hal/workers/8"
}
}
}
Final step, as per link in description:
curl -X PUT -H "ContentType: text/uri-list" http://localhost:8080/hal/organizations/2 -d 'http://localhost:8080/hal/workers/8'
{
"cause": null,
"message": "Target bean is not of type of the persistent entity!"
}
Doing some debug it's pretty obvious what the specific complaint is. The stack trace leads here:
#Override
public PersistentPropertyAccessor getPropertyAccessor(Object bean) {
Assert.notNull(bean, "Target bean must not be null!");
Assert.isTrue(getType().isInstance(bean), "Target bean is not of type of the persistent entity!");
return new BeanWrapper<Object>(bean);
}
getType() -> Organization
isInstance(bean) -> bean instance of org.springframework.hateoas.Resource
Any input on this? I followed the instructions to the letter.

Here is the answer (it took going out for a walk to clear my head and hit this).
You have to post to the association resource
http://localhost:8080/hal/organizations/1/workers
That nugget occurred to me and then I went and re-read the post.
For dumb mistakes like mine, a 400 error would have been much more useful.

Related

NullPointerException accessing entity by ID when #CreatedBy auditing is used

I just added a relation with #CreatedBy to one of my entites and since then, I receive a NullPointerException accessing it via ID. But first things first:
The entity. I am leaving out some fields but left the "owner" in place, since the stack trace (see below) refers to that. The "creator" is the relation I added.
#Entity
#Data
#NoArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class Invitation implements BaseEntity<Long>, OwnedEntity {
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne
private Company owner;
#CreatedBy
#OneToOne
private Account creator;
...
}
The "creator" field is set by my implementation of AuditorAware which looks like this:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class AuditorProvider implements AuditorAware<Account> {
private static final Log LOG = LogFactory.getLog(AuditorProvider.class);
private final #NonNull AccountRepo accountRepo;
#Override
public Optional<Account> getCurrentAuditor() {
Optional<Account> opt = accountRepo.findMe();
if (opt.isPresent()) {
LOG.debug("Found auditor: " + opt.get());
} else {
LOG.debug("No auditor found.");
}
return opt;
}
}
The method accountRepo.findMe() finds the current instance of Account based on the security context.
With this in place, when I POST an Invitation entity like
curl -XPOST -H "Authorization: Bearer $TOKEN" -H "Content-type: application/hal+json" localhost:8081/invitations -d '{"email":"k#lo.de","role":"http://localhost:8081/roles/139"}'
the response body looks good:
{
"email" : "k#lo.de",
"_links" : {
"self" : {
"href" : "http://localhost:8081/invitations/144"
},
"invitation" : {
"href" : "http://localhost:8081/invitations/144"
},
"creator" : {
"href" : "http://localhost:8081/invitations/144/creator"
},
"role" : {
"href" : "http://localhost:8081/invitations/144/role"
},
"owner" : {
"href" : "http://localhost:8081/invitations/144/owner"
}
}
}
The database table for Invitations and the logs show that the "creator" has successfully been set.
Fetching all invitations works perfectly fine without any errors:
curl -v -H "Authorization: Bearer $TOKEN" -H "Content-type: application/hal+json" http://localhost:8081/invitations
Fetching that Invitation with ID 144 gives me an HTTP 500 error:
curl -v -H "Authorization: Bearer $TOKEN" -H "Content-type: application/hal+json" http://localhost:8081/invitations/144
Looking into the logs, I see this stack trace: https://pastebin.com/mVzHHddU
The reason I left the "owner" relation in the snippet above is this line:
at training.edit.identity.model.Company.hashCode(Company.java:22) ~[classes/:na]
Other than that, none of the lines are familiar to me and I cannot make any sense out of the error.
Any ideas would be highly appreciated!
Edit: The company entity
#Data
#NoArgsConstructor
#Entity
public class Company implements BaseEntity<Long>, OwnedEntity {
#Id
#GeneratedValue
private Long id;
#NotNull
private String name;
#NotNull
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Address> addresses = new HashSet<Address>();
public boolean addAddress(Address address) {
return this.addresses.add(address);
}
#JsonIgnore
#Override
public ScopedEntity getParent() {
return null;
}
#JsonIgnore
#Override
public Set<Company> getTenants() {
return Sets.newHashSet(this);
}
#Override
public void configureTenant(Company tenant) {
throw new RuntimeException("Cannot configure tenant on Company.");
}
}
Edit: Because of the lombok related comment below, I removed the #Data annotation from Company and created the getters and setters manually. Like this, fetching an Invitation by ID works.
Does that make sense to anyone?

EmbbedId null in spring boot

I'm trying to model a business paying dividends and exposing a REST API to interact with such model.
I'm using spring-boot 2.2.0
Here is my Dividend entity:
#Entity
public class Dividend {
#EmbeddedId
DividendId dividendId;
#ManyToOne(fetch = FetchType.LAZY)
public Stock stock;
public Short period;
public Float amount;
#OneToOne
public Currency currency;
public static class DividendId implements Serializable
{
private DividendId() {}
public DividendId(String stockId, String payDay)
{
this.stockId = stockId;
this.payDay = payDay;
}
public String stockId;
public String payDay;
}
}
Doing a POST with the following body:
{
"amount": 0.6120,
"currency": {
"currencyId": "EUR"
},
"period": 2018,
"stock": {
"stockId": "BME:ENG"
},
"dividendId": {
"stockId": "BME:ENG",
"payDay": "2018-12-19"
}
}
dividendId is set to null and the other properties are not null
Why is dividendId set to null?
and How may I avoid repeating the stockId two times?

issue writing children in a #OneToMany JPA relationship from JHipster

I am tryng to do a workaround for this issue, that I have entered:
https://github.com/jhipster/generator-jhipster/issues/9639
I did some work (I added findAllWithEagerRelationships in the repository), and the GET method works fine: I get all the master and children.
What is not working, and I need your help, is the POST method:
when I post a parent with some children (pets), the children are not posted with the parent, so children are lost.
so summarizing this is the get result, correct:
[
{
"id": 1002,
"name": "Piera",
"pets": [
{
"id": 1051,
"name": "fido",
"species": "barboncino",
"owner": {
"id": 1002,
"name": "Piera"
}
}
]
}
]
but the post does not work correctly:
{
"name": "newName",
"pets": [
{
"id": 1051
}
]
}
newName is created, but pet 1051 is not attached to it
I am working on a app generated with Jhipster:
entity Owner {
name String required
}
entity Pet {
name String required,
species String required
}
relationship OneToMany {
Owner{pet} to Pet{owner}
}
for the get, I added those two methods that I copied form a manytomany relationship, and they worked:
#Query(value = "select distinct owner from Owner owner left join fetch owner.pets")
List<Owner> findAllWithEagerRelationships();
#Query("select owner from Owner owner left join fetch owner.pets where owner.id =:id")
Optional<Owner> findOneWithEagerRelationships(#Param("id") Long id);
Public class Owner implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "owner")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Pet> pets = new HashSet<>();
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public class Pet implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#NotNull
#Column(name = "species", nullable = false)
private String species;
#ManyToOne
#JsonIgnoreProperties("pets")
private Owner owner;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
I expect that when I do a post on a parent with some children, the children (pets) are also posted.
In your Owner service interface and implementation layer (it should be under com.yourpackage.service and com.yourpackage.service.impl) create a new method like
public Optional<Owner> findOneWithChildren(long id) {
Optional<Owner> owner = ownerRepository.findById(id);
owner.ifPresent(o -> {
o.getPets().addAll(petRepository.getByParentId(id);
}
return owner.map(ownerMapper::toDto);
}
And in your Pet repository, create a new method, like
List<Pet> getByParentId(long id);
in the bug report, I could find a workaround without touching the JPA. I still don't know how we can put a new owner associating to it some pets with JPA as I did receive a resolving answer, anyway my workaround works. thanks :)

Object API binding : only fetch subobject id

Is it possible to only store the id of subobject as a String attribute when retrieve an Object from database with database.load("objectId") ?
see documentation here : https://orientdb.com/docs/3.0.x/java/Object-DB-Attach.html
More informations
What I see possible with the documentation but that's not enough for me :
The POJO
class Person {
#Id
private String id;
private Address address;
}
class Address {
#Id
private String id;
// Not loaded using lazy loading
private String city;
}
The corresponding built object :
{
"id": "#10:10",
"address": {
"id": "#15:2"
}
}
What I want
POJOs are identical except from address field which is a String now.
class Person {
#Id
private String id;
private String address;
}
// I didn't add again the code for Address POJO
The sought JSON :
{
"id": "#10:10",
"address": "#15:2"
}

Apache Camel Swagger - using JPA entity as rest type

i have this Rest-DSL:
// this api creates new user
rest("/user")
.post()
.type(User.class).to("jpa://com.project.User")
This is my entities:
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user")
private List<User> users;
}
my problem is in my swagger in the Body value parameter example. It contains like this:
{
"id": "string",
"role": {
"id": "string",
"users": [
{
"id": "string",
"roles": [
{}
]
}
]
}
}
quite complicated, although i need only id and id_role parameters to create (POST) new user. I hope the body example shows like this:
{
"id": "string",
"id_role": "string"
}
I realized that my entities are not created properly. These was i learned:
Configure CascadeType in associated JPA entities
#Entity
public class User {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<User> users;
}
to make class not recursive, set #JsonIgnore
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
#JsonIgnore
// this attribute will not appear inside Role class
private List<User> users;
}