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?
Related
I'm learning MongoDB and Mongo Spring, and I have somethings to wonder.
I have a UserInfo document, a user can have multiple roles (document Roles). I insert as a Set, work good. But for the time being, I can insert a role, which not exist in document Roles.
For the example below, I insert roles as ADMIN2, while Document Roles only having "ADMIN", "USER" and "MOD"
I have a document named User:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User implements UserDetails,Serializable {
#Id
private String id;
private final static String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+#[a-zA-Z0-9-]+(?:\\\\.[a-zA-Z0-9-]+)*)|(?>[_.#A-Za-z0-9-]+)$";
#NotNull
#Field
#Size(min = 1, max = 50)
private String username;
#NotNull
#Field
//#Pattern(regexp = LOGIN_REGEX)
private String password;
#JsonIgnore
private Set<Roles> roles;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
Set<Roles> role = this.roles;
for(Roles roleItem : roles) {
authorities.add(new SimpleGrantedAuthority(roleItem.getCode()));
}
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
the UserInfo extends User
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserInfo extends User implements Serializable {
public UserInfo(String id, #NonNull String username, #NonNull String password, Set<Roles> roles) {
super(id, username, password, roles);
}
#Field
#NotNull
private String firstName;
#NotNull
#Field
private String lastName;
#Transient
#Getter(AccessLevel.NONE)
private String fullname;
#Field
#NotNull
#Email
private String email;
#Field
private String description;
#Field
private String title;
#Field
private String phoneNumber;
#Field
private String lang;
public String getFullname() {
if(StringUtils.isBlank(lang))
if(lang.equals("VI"))
return lastName + " " + firstName;
else
return firstName + " " + lastName;
return null;
}
}
My idea is, a User can have a lot roles (for now, i have "ADMIN", "MOD" and "USER")
Here is document Roles
#Document("roles")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Roles implements Serializable{
#Id
private String id;
#Field
private String code;
#Field
private String name;
}
Here how the document UserInfo look like:
{
"_id": {
"$oid": "638b72f79c9b3a78b623ea50"
},
"firstName": "truong",
"lastName": "hoang ngoc nghia",
"email": "truonghnn23232",
"title": "Dev",
"lang": "VI",
"username": "truonghnn",
"password": "$2a$10$Jsn1wPMdr.TlS8yyjQ6mF.OBTdrxTuaaDQ8Xz30hSWsKFRuB2bfKW",
"roles": [
{
"code": "ADMIN"
}
],
"_class": "com.example.Auth_ToDo.Domain.UserInfo"
}
So, the issue is, when I input the roles to UserInfo document, I use JSON like this:
"roles" : [
{"code":"ADMIN2"}
]
Everythings is running fine. But as you can see, roles here having value of ADMIN2, which not exist in document Roles (Document Roles having ADMIN, USER and MOD as roles.code)
How can I implement my code, so when I insert into UserInfo document, it check if my role code exist in document Roles? If not, it will return an exception? (I want it to check by default)
I tried to use Embbed Document, as using #DocumentRef, but not working. The result is
=
"roles": [
null
],
"_class": "com.example.Auth_ToDo.Domain.UserInfo"
}
The role returned as null when i input code as "ADMIN"
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"
}
I try to start with a simple spring data rest application and struggle around with multiple OneToMany relations. I have a person which has many items and many addresses. To get the application up and running I have to set the #RestResource annotation in items and address to create the correct links. But unfortunately the link doesn't work (I get an empty result) and why can't I set the #RestResource annotation in the person entity. Here is my code. I hope you can help me.
Person:
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int id;
private String name;
//bi-directional many-to-one association to Item
#OneToMany
#JoinColumn(name="id")
private List<Item> items;
//bi-directional many-to-one association to Address
#OneToMany
#JoinColumn(name="id")
private List<Address> addresses;
public Person() {
}
// getter setter
Address
#Entity
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int id;
private String address;
//bi-directional many-to-one association to Person
#ManyToOne
#JoinColumn(name="person_id")
#RestResource(rel="address", path="address",exported=true)
private Person person;
public Address() {
}
// getter setter
Item
#Entity
#Table(name="items")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int id;
#Column(name="item_name")
private String itemName;
//bi-directional many-to-one association to Person
#ManyToOne
#JoinColumn(name="person_id")
#RestResource(rel="item", path="item",exported=true)
private Person person;
public Item() {
}
// getter setter
And I get this result
{
"name" : "dave",
"items" : [ {
"itemName" : "ite,m"
} ],
"addresses" : [ {
"address" : "myaddress1"
} ],
"_links" : {
"self" : {
"href" : "http://localhost:8090/person/1"
},
"person" : {
"href" : "http://localhost:8090/person/1"
},
"address" : {
"href" : "http://localhost:8090/person/1/address"
},
"item" : {
"href" : "http://localhost:8090/person/1/item"
}
}
}%
I have been successful in sending a simple JSON object over to a spring framework Controller
curl -v -H "Content-Type: application/json" -H "Accept: application/json" -d '{"lastName":"Smith","firstName":"John"}' http://localhost:8080/WebServices02/aura/testJsonArray
However, when I send over a JSON object with an embedded array
curl -v -H "Content-Type: application/json" -H "Accept: application/json" -d '{"lastName":"Smith","pals":[{"name":"Billy"}],"firstName":"John"}' http://localhost:8080/WebServices02/aura/testJsonArray
I get the error 400 'The request sent by the client was syntactically incorrect ().'
My controller is
#Controller
#RequestMapping(value="/aura")
public class AuraController {
#RequestMapping(value = "/testJsonArray", method = RequestMethod.POST, headers = {"content-type=application/json"})
#ResponseBody
public void testJsonArray(#RequestBody Aura aura){
System.err.println("Called testJsonArray(): " + "Aura is " + aura.toString());
}
}
My Aura class is defined as
import java.io.Serializable;
import java.util.List;
import org.json.JSONObject;
public class Aura implements Serializable {
public class Pal{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
private List<Pal> pals;
public String toString(){
return new JSONObject(this).toString();
}
public List<Pal> getPals() {
return pals;
}
public void setPals(List<Pal> pals) {
this.pals = pals;
}
}
My bean (under mvc:message-converters) is
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
If someone can help me on this... thanks
You have to include Jackson JSON Mapper dependency for jackson-core-asl and jackson-mapper-asl in your project.
This is necessary for mapping json with java pojo class fields.
I'm trying to update a number of fields at the same time in my "User" document. However, I only want to update some of the fields and not replace the entire document and it's the latter that I cannot seem to avoid. The method I have for doing this looks like so:
public void mergeUser(User user) {
Update mergeUserUpdate = new Update();
mergeUserUpdate.set("firstName", user.getFirstName());
mergeUserUpdate.set("lastName", user.getLastName());
mergeUserUpdate.set("username", user.getUsername());
mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(user.getId())), mergeUserUpdate, User.class);
}
My user object does contain other fields - a password field being one of them - but if this was set to a value before it is promptly replaced with an empty string or removed entirely. So in the database, this:
{
"_id" : ObjectId("4fc34563c3276c69248271d8"),
"_class" : "com.test.User",
"password" : "d26b7f5c0ed888e46889dd1e3d217816d070510596f495e156e9efe4b035fec5a1fe1be643955359",
"username" : "john#gmail.com",
"alias" : "john"
}
gets replaced by this after I call the mergeUser method:
{
"_id" : ObjectId("4fc34563c3276c69248271d8"),
"_class" : "com.test.User",
"username" : "john#gmail.com",
"firstName" : "John",
"lastName" : "Doe",
"address" : {
"addressLine1" : ""
}
}
If I look at the Update object I see it contains the following:
{$set={firstName=John, lastName=Doe, username=john#gmail.com}}
This looks correct to me and from my understanding of the MongoDB $set function, this should only set the values that are specified. I was therefore expecting the password field to remain unchanged and the other fields added or altered accordingly.
As a general discussion point, I'm ultimately trying to achieve some kind of "merge" functionality whereby Spring will auto-magically check which fields are present in the supplied User object and only update the database with the values that are filled in, not all the fields. That should be theoretically possible I would have thought. Anyone know of a nice way to do this?
Here's my user object just in case:
/**
* Represents an application user.
*/
#Document(collection = "users")
public class User {
#Id
private String id;
#NotEmpty( groups={ChangePasswordValidationGroup.class} )
private String password;
#Indexed
#NotEmpty
#Email
private String username;
private String firstName;
private String lastName;
private Date dob;
private Gender gender;
private Address address;
public enum Gender {
MALE, FEMALE
}
// /////// GETTERS AND SETTERS ///////////
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getDob() {
return dob;
}
public void setDob(Date dob) {
this.dob = dob;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
The code above does work just fine. I made a silly mistake whereby after updating correctly I proceed to save the user object again, which replaces it with a new document.
Having another solution to update an Entity without some fields using Spring MongoTemplate:
DBObject userDBObject = (DBObject) mongoTemplate.getConverter().convertToMongoType(user);
//remove unnecessary fields
userDBObject.removeField("_id");
userDBObject.removeField("password");
//Create setUpdate & query
Update setUpdate = Update.fromDBObject(new BasicDBObject("$set", userDBObject));
mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(user.getId())), setUpdate , User.class);
//Or use native mongo
//mongoTemplate.getDb().getCollection("user").update(new BasicDBObject("_id",user.getId())
, new BasicDBObject("$set", userDBObject), false, false);
Because it uses auto converter so is is very helpful when your entity has many fields.