For my model I have problem with reading enum property. After reading from mongoDB, the property 'value' has type String instead of MyEnum.
My model:
public class Record {
#Id
public String id;
public Wrapper<MyEnum> wrappedEnum;
...
}
public class Wrapper<T extends Enum<?>> extends BaseWrapper<T>{
...
}
public class BaseWrapper<T> {
public T value;
...
}
Code:
Record record = new Record();
Wrapper<MyEnum> wrapper = new Wrapper<>();
wrapper.setValue(MyEnum.A);
record.setWrappedEnum(wrapper);
repository.save(record);
repository.findAll().forEach(rec -> {
Object value = rec.getWrappedEnum().value;
System.out.println("rec.getWrappedEnum().value: " + value); // rec.getWrappedEnum().value: A
System.out.println("rec.getWrappedEnum().getValue(): " + value.getClass()); // rec.getWrappedEnum().getValue(): class java.lang.String
MyEnum valEnum = rec.getWrappedEnum().getValue(); // throws Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class com.example.MyEnum (java.lang.String is in module java.base of loader 'bootstrap'; com.example.MyEnum is in unnamed module of loader 'app')
});
Is any option to configure mongodb to read this model correctly?
The reason why you are getting a ClassCastException is because you didn't specify how the persistence layer should actually persist your wrapped enum.
I suppose you are using JPA as the persistence API. There are actually multiple ways to achieve what you want. The more convenient way for your case would be to register a converter, so that JPA knows how to persist your Enum and map it back. A basic example could look like this (Note that there are setter for the value field in BaseWrapper):
#Converter
public class WrapperConverter implements AttributeConverter<Wrapper, String> {
#Override
public String convertToDatabaseColumn(Wrapper attribute) {
return attribute.getValue().toString();
}
#Override
public Wrapper convertToEntityAttribute(String dbData) {
MyEnum myEnum = MyEnum.valueOf(dbData);
Wrapper<MyEnum> wrappedEnum = new Wrapper<>();
wrappedEnum.setValue(myEnum);
return wrappedEnum;
}
}
Register the converter:
#Entity
public class Record {
#Id
private String id;
#Convert(converter = WrapperConverter.class)
private Wrapper<MyEnum> wrappedEnum;
Related
I have two different enum classes and one of them use them as a value like
public enum Type{
TEST1{
#Override
public Type convertToINT() {
return Type.INTEGRATION1;
}
},
TEST2{
#Override
public Type convertToINT() {
return Type.INTEGRATION2;
}
},
TEST3{
#Override
public TypeIntegration convertToINT(){
return Type.INTEGRATION3;
}
};
public abstract TypeIntegration convertToINT();
}
public enum TypeIntegration {
INTEGRATION1,
INTEGRATION2,
INTEGRATION3
}
This enums uses in different classes for example ;
#Getter
#Setter
public class typeSaveReqDto{
private blabla;
private blabla;
private Type type;
}
#Getter
#Setter
public class typeIntegrationObject{
private blabla;
private blabla
private TypeIntegration type;
}
I want to use mapstructs and convert via auto generated classes, but mapstruct throws me exception like "The following constants from the property "Type type" enum have no corresponding constant in the "TypeIntegration type" enum and must be be mapped via adding additional mappings: TEST1, TEST2, TEST3"
I want to create EnumMapper classes for converting enums, How can i write generic classes for this with mapStructs java ?
Edit :
I generated EnumMapper
#Mapper
public class EnumMapper {
EnumMapper INSTANCE = Mappers.getMapper(EnumMapper.class);
#Named("enumToIntEnum")
public static <T extends EnumConverter<INT>,INT> INT convertToINT(T enums, #TargetType INT enumClass){
INT convertObj = ((T)enums).convertToINT();
return convertObj;
}
}
And Mapper interfaces like below
#Mapper(componentModel = "spring",uses = EnumMapper.class)
public interface TypeReqMapper {
#Mapping(source = "type" , target = "type",qualifiedByName = "enumToIntEnum")
public TypeIntegrationObject typeSaveReqDtoTotypeIntegrationObject(TypeSaveReqDto typeSaveReqDto);
}
But I got a fail like 'Can't map property "Type type" to "TypeIntegration type". Consider to declare/implement a mapping method: "TypeIntegration map(Type value)".'
Type T isn't know and doesn't contain a convertToINT method as it can be anything. So keep it simple and just write a dedicated mapper instead of trying to build one that does everything.
#Mapper(componentModel="spring")
public class TypeToTypeIntegrationMapper {
#Mapping
public TypeIntegration map(Type from) {
return from.convertToINT();
}
}
Don't make it more complex.
You could even ditch the mapper and write an expression instead.
#Mapper(componentModel = "spring",uses = EnumMapper.class)
public interface TypeReqMapper {
#Mapping(source = "type" , target = "type", expression = "java(type.convertToINT())")
public TypeIntegrationObject typeSaveReqDtoTotypeIntegrationObject(TypeSaveReqDto typeSaveReqDto);
}
MapStruct will now use the expression to generate the mapping code instead of you having to write a mapper yourself.
I have the following two classes:
#Entity
#Table(name = "criteria")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = AbstractCriteria.TYPE, discriminatorType = DiscriminatorType.STRING)
public abstract class AbstractCriteria<T> implements Serializable {
public static final String TYPE = "type";
...
#Column(name = "clazz") private Class<T> clazz;
...
protected AbstractCriteria(Class<T> clazz) {
this.clazz = Preconditions.checkNotNull(clazz);
}
...
public abstract Predicate<T> evaluate(T value);
}
#Entity
#DiscriminatorValue(CriteriaType.Values.DATETIME_IS_BETWEEN)
public class DateTimeIsBetweenCriteria extends AbstractCriteria<DateTime> {
...
public DateTimeIsBetweenCriteria() {
super(DateTime.class);
}
...
#Override
public Predicate<DateTime> evaluate(DateTime value) {
...
}
}
When I tried to save an instance of the DateTimeIsBetweenCriteria class, I got this exception:
Exception in thread "main" org.hibernate.AnnotationException:
Property ...AbstractCriteria.clazz has an unbound type and no explicit target entity.
Resolve this Generic usage issue or set an explicit target attribute (eg #OneToMany(target=) or use an explicit #Type
I need clazz for a method in AbstractCriteria, and T can be String, DateTime, Boolean, etc.
I was thinking about making clazz #Transient, but if I do that, I still have to have another column to identify which criteria to save so that when loading the criteria back from the database, I know which kind of criteria it is. I think this is kinda hacky.
Any ideas how I can resolve that exception without changing my design?
I appreciate your time a lot.
in terms of generics you're on the right lines but you're trying to persist a class of type "Class". Databases don't have data type this can directly map to (hence your error), the closest I can think of is to use a string for the classname;
#Column (name="className")
private String classname;
and set with
.... setClassname (DateTime.getCanonicalName());
To retrieve the class;
Class.forName(classname);
But if I've got the right idea about what you are doing, you don't need to persist the class at all: all DateTimeIsBetweenCriteria instances with operate on DateTime - it's a template parameter of the class, not the instance. You should persist your between dates and not the behavior of the class.
I have changed my implementation to not using parameterized type:
#Entity
#Table(name = "criteria")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = AbstractCriteria.TYPE, discriminatorType = DiscriminatorType.STRING)
public abstract class AbstractCriteria implements Serializable {
public static final String TYPE = "type";
...
#Column(name = "clazz") private Class clazz;
...
protected AbstractCriteria(Class clazz) {
this.clazz = Preconditions.checkNotNull(clazz);
}
...
public abstract Predicate evaluate(Object value);
}
#Entity
#DiscriminatorValue(CriteriaType.Values.DATETIME_IS_BETWEEN)
public class DateTimeIsBetweenCriteria extends AbstractCriteria {
...
public DateTimeIsBetweenCriteria() {
super(DateTime.class);
}
...
#Override
public Predicate evaluate(Object value) {
if (value instanceof DateTime) {
// do evaluation and return here.
} else {
throw new UserDefinedException("...");
}
}
}
Is it possible define diferent Projections by SubTypes and Spring Data REST
use the most concrete projection about class type?
This issue was exposed on JIRA issue DATAREST-739 and also exists a merge commit but this not appears on official changelog and also I don't found any documentation or guide to solved it with the current releases.
The use case sample used into the issue is:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="type")
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public abstract class Message implements Identifiable<UUID> { ... }
#Entity
#DiscriminatorValue("TEXT")
#JsonTypeName("TEXT")
public class TextMessage extends Message { ... }
#Entity
#DiscriminatorValue("TODO")
#JsonTypeName("TODO")
public class TodoMessage extends Message { Boolean isDone; }
#Projection(name = "summary", types = TodoMessage.class)
public class TodoMessageSummary { Boolean getIsDone(); }
#Projection(name = "summary", types = TextMessage.class)
public class TextMessageSummary { ... }
public interface MessageRepo extends JpaRepository<Message, UUID> { ... }
#RepositoryRestResource(excerptProjection = TodoMessageSummary.class)
public interface TodoMessageRepo extends JpaRepository<Message, UUID> { ... }
#RepositoryRestResource(excerptProjection = TextMessageSummary.class)
public interface TextMessageRepo extends JpaRepository<TextMessage, UUID> { ... }
First issue: how to define an excerpt projection for MessageRepo to use TodoMessageSummary for TodoMessage entities and TextMessageSummary for TextMessage?
Second issue: how to define projection for another entity that has a Message field? Let's say you have the following:
#Projection(name = "summary", types = Dashboard.class)
public class DashboardSummary {
List<Message> getMessages();
}
SOLVED:
The trick is use inheritance into the subtype projections:
#Projection(name = "summary", types = Message.class)
public class MessageSummary {
#Value("#{target.getClass().getSimpleName()}")
String getType();
}
#Projection(name = "summary", types = TextMessage.class)
public class TextMessageSummary extends MessageSummary { ... }
#Projection(name = "summary", types = TodoMessage.class)
public class TodoMessageSummary extends MessageSummary {
Boolean getIsDone();
}
The Spring REST #RepositoryRestResource returns an array of Messages using the concrete subtype projection ( isDone must be appear into TodoMessage instances)
This issue a bit more complex if you need to made the same into a extendend #RequestMapping into a Controller, to do so I use the next snnipeed:
Page<Message> results = repository.findAll(predicate, pageable);
Converter<? super Message, ? extends MessageSummary> converter= l -> {
if(l instanceof TextMessage){
return projectionFactory.createProjection(TextMessageSummary.class,l);
}
else if(l instanceof TodoMessage){
return projectionFactory.createProjection(TodoMessageSummary.class,l);
}
else {
return projectionFactory.createProjection(MessageSummary.class,l);
}
};
Page<MessageSummary> projected =results.map(converter);
return pagedAssembler.toResource(projected);
Note that if only need resource type info on frontend for read only purpose (i.e. for POST/PUT use concrete subtype endpoints) I think that is not really needed use the #JsonTypeInfo because using SpEL into projections allow get this type info more easy and flexible:
#Value("#{target.getClass().getSimpleName()}")
String getType();
Yes it's possible. We have such sub-types structure in our application.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
public abstract class Vehicle {
protected VehicleType type;
}
public class Plane extends Vehicle {
private String title;
}
Projections for them:
public interface VehicleProjection {
String getType();
}
#Projection(name = "default", types = Plane.class)
public interface PlaneProjection extends VehicleProjection {
String getTitle();
}
And rest repository:
#RepositoryRestResource(collectionResourceRel = "collectionName", path = "path",
excerptProjection = VehicleProjection.class)
public interface RestVehicleRepository<T extends Vehicle> extends MongoRepository<T, String> {
}
Also we've registered those projections in configuration. Not sure whether it is required for your case, because we have more then one projection for each sub-type:
#Configuration
public class CustomRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(final RepositoryRestConfiguration config) {
config.getProjectionConfiguration().addProjection(PlaneProjection.class,
"default", Plane.class);
}
}
I have an Entity class with a set of enums:
#Entity
public class Something {
public enum Type{
FIRST_ENUM((short)1),
SECOND_ENUM((short)2);
private short id;
private Type(short id) {
this.id = id;
}
public short getId() {
return id;
}
}
#CollectionTable(name="table_name", joinColumns=#JoinColumn(name="something_id"))
#Column(name="type")
private Set<Type> types;
... //other props + getters and setters
}
For the enum I made a converter (whole converter package is loaded by #EntityScan annotation) :
#Converter(autoApply=true)
public class TypeConverter implements AttributeConverter<Type, Integer> {
#Override
public Integer convertToDatabaseColumn(Type st) {
//implementation
}
#Override
public Type convertToEntityAttribute(Integer i) {
//implementation
}
}
Now when I try to use the enum in a query
... AND {packagename}.Something$Type.FIRST_ENUM MEMBER OF {someobject}.something.types ...
I stumble upon following error:
org.hibernate.QueryException: Unrecognized Hibernate Type for handling query constant ({package}.Something$Type.FIRST_ENUM); expecting LiteralType implementation or AttributeConverter
Does anyone got a clue why I cannot use the enum in my query? It somehow seems the enum is not known to Hibernate. I don't understand why, because the class is loaded when I start my application.
i've a base class that provide only identification:
public abstract class Identifable<T> {
#Id
private T id = null;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
public boolean hasId() {
return id != null;
}
}
and several subclasses that extends it like:
#Entity
#Cache
public class MyEntity extends Identifable<String> {
/* some specific attributes and methods */
}
I get an java.lang.IllegalStateException: #Id field 'id' in com.mypkg.MyEntity must be of type Long, long, or String.
Why? Can't Objectify see the inherited #Id field?
Thanks
The cause:
Objectify only inspects types at runtime using reflection. Because of type erasure all unbounded type parameters are during compilation converted to Object type, which is what objectify sees and complains.
The solution:
Use concrete type for id field. Possibly move it to a child class, as proposed by #Anthony.
In JPA, you must use for a field marked with #Id one of the following types:
any Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date; java.sql.Date; java.math.BigDecimal; java.math.BigInteger
Just remove the generics from the base class and use one of the mentioned types for your id field.
Let's reason about if for a while... You are trying to build a super type in which the type of the ID varies. Are you sure that this is what you want objectify to build (a hierarchy of objects in which the root entity has a unknown ID type)? While I've seen this kind of code in several ORM frameworks, this is how I would build what you want.
Interface (not part of the object hierarchy):
public interface Identifable<T> {
public T getId();
public void setId(T id);
public boolean hasId();
}
Root of your hiearchy implements Identifable with a concrete type for the id:
#Entity
public class MyBaseClass implements Identifable<String> {
#Id
private String id = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean hasId() {
return id != null;
}
}
And subclass comes naturally out of it:
#EntitySubclass(index=true)
public class MyEntity extends MyBaseClass {
// fields, accessors and mutators
}