Save entity containing gson atribute to ElasticSearch in Spring - java

My Spring application uses Spring-data-elasticsearch (https://github.com/spring-projects/spring-data-elasticsearch).
I would like to save following document to the elasticsearch database:
#Document(indexName = "documents", type = "customEntity", replicas = 0, shards = 5)
public class CustomEntity implements Serializable{
#Id
private String Id;
#Field(type = FieldType.String)
private String Field1;
#Field(type = FieldType.Integer)
private int Field2;
#Field(type = FieldType.Object) //not sure which annotation I should use
private JsonObject exportJSON; //gson object
...getters and setters...
}
using this way:
public class CustomEntityDao {
#Resource
ElasticsearchTemplate elasticsearchTemplate;
public void insertCustomEntity(CustomEntity entity){
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(entity.getCustomEntityId());
indexQuery.setObject(entity);
elasticsearchTemplate.index(indexQuery); //exception thrown
}
}
but I'm getting this error:
com.fasterxml.jackson.databind.JsonMappingException: JsonObject
(through reference chain: data.nosql.entities.CustomEntity
["exportJSON"]->com.google.gson.JsonObject["asString"])
I understand the problem but I don't have clue how to solve it. Any ideas please?

My guess is that jackson tries to covert your gson object to a string to be indexable by ES and it doesn't know how to do that. If you post your full stack trace it'll be more helpful, but if i have to take a guess you need to use a #JsonSerializer annotation on your "exportJSON" object.

Related

Define deserialization type with GSON

I'm using Google Gson to de/serialize my app database to/from JSON. The app uses Room database with a custom class which has more than 20 fields, so for semplicity let's consider an example one. I recently changed the type of a field from String to JSON; Room managed the migration fine - just by creating a converter class and adding a void version migration - but I'm not sure how to correclty handle Gson.
Here is the object class, respectively the old and the new one:
Old exClass.java
#Entity(tableName = "example_class")
public class exClass {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "name")
public String name;
#ColumnInfo(name = "json")
public String json;
}
New exClass.java
#Entity(tableName = "example_class")
#TypeConverters({Converters.class})
public class exClass {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "name")
public String name;
#ColumnInfo(name = "json")
public JSONObject json;
}
Now when trying to restore old database backups (created with the old version of the class) Gson throws an exception, because it's expecting a JSON object for the field "json" but finds a String.
Is there a way to override the type Gson expects without having to create a custom JsonSerializer<exClass>?
Note that the JSONObject is still fine as far as Room is concerned: the converter converts "json" to a String before inserting it into the database and viceversa, so it's only a matter of having Gson correcly parse the "json" field.
Thanks in advance

Using ObjectId as String in Java (Manual reference) with spring-data mongodb

In MongoDB documentation they suggest to use ObjecId for manual references.
please see https://docs.mongodb.com/manual/reference/database-references/#document-references
original_id = ObjectId()
db.places.insert({
"_id": original_id,
"name": "Broadway Center",
"url": "bc.example.net"
})
db.people.insert({
"name": "Erin",
"places_id": original_id,
"url": "bc.example.net/Erin"
})
I'm using spring-data-mongodb and what I'm looking for is to have a People class defined like this:
#Document
public class People {
private String name;
#Reference // or any Annotation to convert an ObjectId to a String
private String placesId;
private String url;
}
How to have a "places_id" as ObjectId in mongoDB but mapped to a String in our POJO ?
I was expecting to have an annotation like #Reference but it seems to not be implemented.
I don't understand why we don't have this kind of annotation in spring-data-mongodb. I don't want to implement an explicit converter like suggested in spring documentation for all documents that use manual references.
Maybe it's not the right approach.
Did I miss something ?
UPDATE :
I like the idea to have a POJO using only String instead of ObjectId. Let's say I've got a class Place like this :
#Document
public class Place {
#Id
private String id;
private String name;
}
place.getId() will be a String but people.getPlaceId() will be an ObjectId. I want to avoid this unnecessary mapping.
The solution would be:
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
public class People {
#Field(targetType = FieldType.OBJECT_ID)
private String placesId;
}
This will map POJO string to ObjectId in MongoDB.
Why don't you leave the field as ObjectId?
#Document
public class People {
private String name;
private ObjectId placesId;
private String url;
}
If you want to query by this field you can do this:
For lists
List<String> ids // the ids as strings
List<ObjectId> objIds = ids .stream()
.map(i -> new ObjectId(i))
.collect(Collectors.toList());
For single String
String id // single id
ObjectId objId = new ObjectId(id);
If you want to make a real reference to an other object in your database, use the #DBRef annotation which is provided by Spring Data.
Your updated code could look like the following:
#Document
public class People {
private String name;
#DBRef
private Place place;
private String url;
}
Spring Data will then automatically map a Place object to your People object. Internally this is done with a reference to the unique ObjectId. Try this code and have a look at your mongo database.
For more information have a look at: MongoDb with java foreign key
I have a solution very simple:
#JsonSerialize(using= ToStringSerializer.class)
private ObjectId brandId;
...
put that on the attribute that is Object Id, and the ObjectId gets and inserts like string

Jackson - deserialization fails on circular dependencies

Ok, so I'm trying to test some stuffs with jackson json converter.
I'm trying to simulate a graph behaviour, so these are my POJO entities
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ParentEntity implements java.io.Serializable
{
private String id;
private String description;
private ParentEntity parentEntity;
private List<ParentEntity> parentEntities = new ArrayList<ParentEntity>(0);
private List<ChildEntity> children = new ArrayList<ChildEntity>(0);
// ... getters and setters
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ChildEntity implements java.io.Serializable
{
private String id;
private String description;
private ParentEntity parent;
// ... getters and setters
}
The tags are required in order to avoid exception on serialization.
When I try to serialize an object (both on a file or on a simple string) all works fine. However, when I try to deserialize the object, it throws an exception. This is the simple test method (try/catch omitted for simplicity)
{
// create some entities, assigning them some values
ParentEntity pe = new ParentEntity();
pe.setId("1");
pe.setDescription("first parent");
ChildEntity ce1 = new ChildEntity();
ce1.setId("1");
ce1.setDescription("first child");
ce1.setParent(pe);
ChildEntity ce2 = new ChildEntity();
ce2.setId("2");
ce2.setDescription("second child");
ce2.setParent(pe);
pe.getChildren().add(ce1);
pe.getChildren().add(ce2);
ParentEntity pe2 = new ParentEntity();
pe2.setId("2");
pe2.setDescription("second parent");
pe2.setParentEntity(pe);
pe.getParentEntities().add(pe2);
// serialization
ObjectMapper mapper = new ObjectMapper();
File f = new File("parent_entity.json");
// write to file
mapper.writeValue(f, pe);
// write to string
String s = mapper.writeValueAsString(pe);
// deserialization
// read from file
ParentEntity pe3 = mapper.readValue(f,ParentEntity.class);
// read from string
ParentEntity pe4 = mapper.readValue(s, ParentEntity.class);
}
and this is the exception thrown (of course, repeated twice)
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f]
...stacktrace...
So, what is the cause of the problem? How can I fix it? Do I need some other annotation?
Actually, it seems that the problem was with the "id" property. Because the name of the property is equal for the two different entities, there were some problems during deserialization. Don't know if it makes sense at all, but I solved the problem changing the JsonIdentityInfo tag of ParentEntity to
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ParentEntity.class))
Of course, I also changed the scope of ChildEntity with scope=ChildEntity.class
as suggested here
I'm open to new answer and suggestions by the way.

#Indexed on nested property not working in Spring-data for mongo

I have the following object structure:
#Document(collection = "user")
#TypeAlias("user")
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
and here is the Contact pojo:
public class Contact {
#Indexed(unique = true)
private String mail;
}
But for some reasons not known to me, I don't see Spring-data creating a unique index for the property info.mail
To summarize, I have this json structure of user object:
{_id:xxxxx,info:{mail:"abc#xyz.shoes"}}
And I want to create a unique index on info.mail using Spring data with the above pojo structure. Please help.
As far as I remember, annotating embedded fields with #Indexed will not work. #CompoundIndex is the way to go:
#Document(collection = "user")
#TypeAlias("user")
#CompoundIndexes({
#CompoundIndex(name = "contact_email", def = "{ 'contact.mail': 1 }", unique = true)
})
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
In my case I had a fresh spring boot application 2.3.0 with just #Document, #Id and #Indexed annotations. I was able to retrieve and insert documents but it refused to create the index other than the PK. Finally I figured that there is a property that you need to enable.
spring.data.mongodb.auto-index-creation = true
As a matter of fact it even works on nested objects without #Document annotation.
Hope this helps :)
Obsolete answer, this was with and older version of mongodb 1.x.
Had the same issue, it seems that your Contact class is missing the #Document annotation i.e.
#Document
public class Contact {
#Indexed(unique = true)
private String mail;
}
Should work, quote from the spring mongodb reference
Automatic index creation is only done for types annotated with #Document.
Extending #Xenobius's answer:
If any configuration extending AbstractMongoClientConfiguration is set, MongoMappingContext will back off. The result is:
spring.data.mongodb.auto-index-creation = true will not be effective
You will need add this into your own configuration:
#Override
protected boolean autoIndexCreation() {
return true;
}
ref: https://github.com/spring-projects/spring-boot/issues/28478#issuecomment-954627106

Jersey ClientResponse Get List of Composite Entities

I am trying to get a Result of a List, basically a list of entities using Jersey RESTful API (Server and Client)
UserRESTClient client = new UserRESTClient();
ClientResponse response = client.getUsersByType(ClientResponse.class, String.valueOf(userType));
List<User> participants = response.getEntity(new GenericType<List<User>>() {
});
However, the above code does not work if Entity User has a Composite Object, if for instance,
public class User {
private UserId userId;
}
public class UserId {
private int id;
private int categoryId;
}
In this case, the JSON is deserialized by Jersey and returned null for the field type UserId inside Class User. I inspected the JSON returned and everything seems good at the RESTful Server end, but the nested JSON response is not clearly processed at the Client.
Any help would be greatly appreciated. I am not sure if it because of the Jackson preprocessor.
Following is the actual Code Snippet. It involves two classes Participant and ParticipantPK (primary for each Participant).
#Entity
#Table(name = "conference_participant")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Participant.findAll", query = "SELECT p FROM Participant p"),
public class Participant implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected ParticipantPK participantPK;
}
#Embeddable
public class ParticipantPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "conference_id")
private int conferenceId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 150)
#Column(name = "participant_sip_uri")
private String participantSipUri;
public ParticipantPK() {
}
public ParticipantPK(int conferenceId, String participantSipUri) {
this.conferenceId = conferenceId;
this.participantSipUri = participantSipUri;
}
And the Code for retrieving ClientResponse,
List<Participant> participants = response.getEntity(new GenericType<List<Participant>>() {
});
However, the ParticipantPK (Composite PK) is null.
You only pasted a code snippet so I don't know if this part is excluded, but in my code I didn't have setters for the fields. I had getters, but no setters.
Without the setters, my composite objects themselves were non-null, but the members of those objects were themselves null.
I tried to reproduce it, but using the same data structures worked for me. What version of Jersey are you using? Is User class annotated with #XmlRootElement or are you using the POJO mapping feature?

Categories