Deserializing interfaces with Spring Data MongoDB - spring

i've currently a problem with the serialize/deserialize in MongoDB of an object that contains an attribute defined by a marker interface. The implementations are Enum.
My versions are Spring 4.3.7 and Spring-data-mongodb 1.10.1.
My code sounds like:
public interface EventType {
String getName();
}
public interface DomainEvent extends Serializable {
UUID getId();
LocalDateTime getOccurredOn();
EventType getEventType();
String getEventName();
}
public abstract class AbstractDomainEvent implements DomainEvent {
private UUID id;
private LocalDateTime occurredOn;
private EventType eventType;
protected AbstractDomainEvent(EventType eventType) {
this.id = UUID.randomUUID();
this.occurredOn = LocalDateTime.now();
this.eventType = eventType;
}
}
public class MyEventOne extends AbstractDomainEvent {
private Object myConcreteData;
public MyEventOne(Object data) {
super(MyEventType.EVENT_ONE);
this.myConcreteData = data;
}
}
public enum MyEventType implements EventType {
EVENT_ONE,
EVENT_N;
#Override
public String getName() {
return this.name();
}
}
Ok, well.
My problem is when I try to deserialize an event persisted in mongoDB.
When I persist MyEventOne, Spring data mongo persist the object as:
{
"_class" : "xxx.xxx.xxx.MyEventOne",
"_id" : LUUID("d74478e7-258c-52c4-4fc5-aba20a30d4b6"),
"occurredOn" : ISODate("2018-02-21T14:39:53.549Z"),
"eventType" : "EVENT_ONE"
}
}
Note the eventType is a String.
When I try to read this document, I have this exception:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [xxx.xxx.xxx.EventType]
Any idea? there a some solution like a insert metadata information about the concrete Enum instance, like a "_class" field?
I try insert #JsonTypeInfo annotation in EventType attribute at AbstractDomainEvent but it doesnt works.
Thank you!!!

Related

Spring data elasticsearch embedded field mapping

I'm struggling with mappings some field. I looked for an answer but couldn't find anything solving my case. Let's cut to the chase.
I have my document class
#Doucment
public class DocumentClass {
#Field(type = FieldType.Nested)
private EmployeeId employeeId;
}
An EmployeeId is wrapper for my uuid identifier. This object has nothing but just getters and setters and jackson annotations. The thing is that object extends some base class so such objects like EmployeeId can inherit this object. This super class has field id and this causes the problem. When I post some data to elasticsearch then it looks like this:
{
"employeeId": {
"id": "someUUID"
}
}
But I want to map this to be like:
{
"employeeId": "someUUID"
}
I wonder if there is a way to flatten this object.
If I get it right, you want to convert your EmployeeId class to a String and back. You have 2 possibilities to do that:
Using a property converter
If you only want to convert an EmployeeId in this entity and might keep it as it is in another, you should use a property converter that is only registered for this property:
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
public class EmployeeIdConverter implements PropertyValueConverter {
#Override
public Object write(Object value) {
return value instanceof EmployeeId employeeId ? employeeId.getId() : value.toString();
}
#Override
public Object read(Object value) {
return new EmployeeId(value.toString());
}
}
This converter must be registered on the property, notice that the field type is set to Keyword as it probably should not be analysed:
import org.springframework.data.elasticsearch.annotations.ValueConverter;
#Document
public class DocumentClass {
#Field(type = FieldType.Keyword)
#ValueConverter(EmployeeIdConverter.class)
private EmployeeId employeeId;
}
Using a global converter
If you are using this EmployeeId at several places you might want register globally 2 converters for the two conversion directions:
#WritingConverter
public class EmployeeIdToString implements Converter<EmployeeId, String>{
#Nullable
#Override
public String convert(EmployeeId employeeId) {
return employeeId.getId();
}
}
#ReadingConverter
public class StringToEmployeeId implements Converter<String, EmployeeId>{
#Nullable
#Override
public EmployeeId convert(String id) {
return new EmployeeId(id);
}
}
To register these, you need to provide a custom client configuration (see the documentation):
#Configuration
public class MyClientConfig extends ElasticsearchConfiguration {
#Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
}
#Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
Collection<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new EmployeeIdToString());
converters.add(new StringToEmployeeId());
return new ElasticsearchCustomConversions(converters);
}
}
In this case, only the field type needs to be adjusted
#Document
public class DocumentClass {
#Field(type = FieldType.Keyword)
private EmployeeId employeeId;
}
I am guessing you have an Id field which is a string.
You need to put a #JsonValue annotation on that field to make jackson serialize it the way you want.
Field annotated by JsonValue will be used to serialize your pojo into json.
If your Id field is private, then add the annotation on the getter of that field.
https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonValue.html

enforce enum serialization with a spring jpa projection?

Say I have the following JPA entity, with an enum field ON/OFF mapped to an SQL enum("on", "off").
#Entity
public class Process {
#Id
private Long Id;
#Convert(converter = StatusConverter.class)
private Status status;
// getter/setter omitted
}
public enum Status {
ON("on"),
OFF("off");
private final String status;
Status(String status) {
this.status = status;
}
// JSON (de)serialization
#JsonCreator
public static Status decode(String status) {
return valueOf(status.toUpperCase());
}
#JsonValue
public getStatus() {
return status;
}
// DAO layer conversion
public String toDatabaseColumn() {
return this.name().toLowerCase();
}
}
#Converter
public class StatusConverter implements AttributeConverter<Status, String> {
#Override
public String convertToDatabaseColumn(Status attribute) {
return attribute.toDatabaseColumn();
}
#Override
public Status convertToEntityAttribute(String dbData) {
return Status.decode(dbData);
}
}
// Spring JPA projection
public interface ProcessSummary {
String getStatus();
}
// a minimalist JPA repository
public interface ProcessRepository extends Repository<Process, Long> {
<T> T findById(Long id, Class<T> type;
}
If I use repository.findById(1L, Process.class) in a REST controller, both the DAO layer conversion and the JSON serialization work as expected :
my database record has its status set to on
it is mapped to the Java Status.ON
the entity is serialized as
{
"status" : "on"
}
But if I use repository.findById(1L, ProcessSummary.class) instead, the entity is serialized as
{
"status" : "ON"
}
How can I get the same result when using a projection as target type? Is it possible with a projection, or should I try something else (a DTO class maybe)?
Sorry folks, it was just me and a textbook case of of PEBKAC :)
The getStatus() method in the interface MUST return a Status, not a String.
public interface ProcessSummary {
String getStatus();
}
does what it's asked: converts the enum to a String, hence Status.ON is serialized as "ON", while
public interface ProcessSummary {
Status getStatus();
}
indeed uses the #JsonValue annotated method and serializes Status.ON as "on".

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

Spring #Requestbody not mapping to inner class

I am doing Spring Rest Api project with Spring 4.x
This Works:
Controller.java
#PostMapping("newTransaction")
TransactionRequestModel insertNewTransaction(#RequestBody TransactionRequestModel model){
//do something
}
TransactionRequestModel.java
public class TransactionRequestModel {
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
KeyValue.java
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
Request Body Json
{
"id": 1
"keyValueList": [
{
"key": "dummy",
"value": "dummy"
}
]
}
Spring message converter using jackson is working fine.
This Won't:
When i change TransactionRequestModel.java to following (and delete KeyValue.java)
public class TransactionRequestModel {
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
means, making KeyValue an inner class, got following error.
org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document: No suitable constructor found for type
[simple type, class
com.example.model.TransactionRequestModel$KeyValue]: can not
instantiate from JSON object (missing default constructor or creator,
or perhaps need to add/enable type information?)
Why?
All the related post in SO mentions the first scenario. I would like to know why this wont work. Please help.
You have to make your inner class static.
public class TransactionRequestModel {
public static class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Resources