Ok, I know there are a bunch of similar questions, but nothing seems to work.
I have the following structure set up for my entities.
public abstract class MyAbstractClass {
// bunch of properties, getters, and setters that subclasses share
public abstract String getType();
}
public class MySubclass1 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_1"; //always the same for each instance of MySubclass1
}
}
public class MySubclass2 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_2"; //always the same for each instance of MySubclass2
}
}
In my controller, I try to map a request to the following method.
public #RequestBody MyAbstractClass saveObject(#RequestBody MyAbstractClass mac) {
// call model to save object
}
I would like to use 1 controller method versus separate ones for the 2 entities. But using the above results in the following.
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of path.to.my.entity.MyAbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Makes sense.
TRY 1
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="implementingClass")
public abstract class MyAbstractClass
What I think it does - adds a metadata implementingClass property that will store the subclass class.
What the result is.
Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'implementingClass' that is to contain type id (for class path.to.my.entity.MyAbstractClass)
Tried with "class" instead of "implementingClass" for the property and got similar results.
TRY 2
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#Type(name="MySubclass1", value=MySubclass1.class),
#Type(name="MySubclass2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
What I think it does - uses the defined name to do some sort of wrapping thing.
What the result is.
Could not resolve type id 'myUuid' into a subtype of [simple type, class path.to.my.entity.MyAbstractClass]
Same results even when adding #JsonTypeName("MySubclass1") and #JsonTypeName("MySubclass2") to the 2 subclasses.
Other Tries
I tried a lot. Nothing works. Won't include everything here.
I feel like there should be a simple way to do this, but I just keep on configuring things incorrectly.
I feel like the getType could maybe be leveraged, but I don't want to add an actual property for type (it's just a helper method). Also I would like to do this with annotations versus other options.
Thank you.
I figured it out but I guess I'll answer in case anyone else has this problem.
I added a type property to my subclasses instead of just a helper method (one example included below).
public class MySubclass1 extends MyAbstractClass {
#Transient
private final String type = "TYPE_1";
public String getType() {
return type;
}
}
Then I did the following for my abstract superclass.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#Type(name="TYPE_1", value=MySubclass1.class),
#Type(name="TYPE_2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
When providing the JSON, I was sure to include the type. I won't include this because it's weird knockout insanity.
It's not great. But it worked.
Related
I am using Lombok and I need a way to automatically set the value for a superclass field in the subclass, without redeclaring it.
It is working fine on its own (#SuperBuiler and #Builder.Default) when using the builder, but it is giving troubles with Spring-Data MongoDB.
org.springframework.data.mapping.MappingException: Ambiguous field mapping detected! Both protected test.jackson.polymorphism.domain.enumeration.EvaluationType test.jackson.polymorphism.domain.models.EvaluationModel.type and private test.jackson.polymorphism.domain.enumeration.EvaluationType test.jackson.polymorphism.domain.models.QuantitativeEvaluationModel.type map to the same field name type! Disambiguate using #Field annotation!
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.assertUniqueness(BasicMongoPersistentEntity.java:368)
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.doWithPersistentProperty(BasicMongoPersistentEntity.java:354)
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.doWithPersistentProperty(BasicMongoPersistentEntity.java:348)
These are my classes:
public enum EvaluationType{
QUALITATIVE, QUANTITATIVE;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public abstract class EvaluationModel {
protected EvaluationType type;
protected Integer evaluation;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class QualitativeEvaluationModel extends EvaluationModel {
/**
* How to set the default value for this field
* without redeclaring it?
*/
#Builder.Default
private EvaluationType type = EvaluationType.QUALITATIVE;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class QuantitativeEvaluationModel extends EvaluationModel {
/**
* How to set the default value for this field
* without redeclaring it?
*/
#Builder.Default
private EvaluationType type = EvaluationType.QUANTITATIVE;
#Builder.Default
private String currencyCode = Currency.getInstance("EUR").getCurrencyCode();
private BigDecimal economicValue;
}
How do I make these subclass to have a default value (mayb final) for the type field, but without redeclaring the field?
This should work both with the contructor and with the builder.
A solution to fix this problem (not specific to Lombok) is to use an initializer block (https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) in the subclasses.
#Data
#SuperBuilder
public class QualitativeEvaluationModel extends EvaluationModel {
{
super.type = EvaluationType.QUALITATIVE;
}
}
From your description, it sounds a bit like that you don't want a settable field, but just a fixed value depending on the subclass instance. In that case, a manually implemented getter method without any field is the better choice.
However, if you really want the user to be able to set a value different from the default, this is your way:
#SuperBuilder
public class QualitativeEvaluationModel extends EvaluationModel {
public static abstract class QualitativeEvaluationModelBuilder<C extends QualitativeEvaluationModel, B extends QualitativeEvaluationModelBuilder<C, B>> extends EvaluationModel.EvaluationModelBuilder<C, B>
{
public QualitativeEvaluationModelBuilder() {
type(EvaluationType.QUALITATIVE);
}
}
}
It defines the builder class exactly like Lombok would create it (Lombok will add anything that you didn't write manually). As it's really filled with generics, I used delombok to get it right.
Next, you have to define a default value for your builder instance simply by calling the builder's setter method for type in the builder's constructor.
In this way, users that don't call the type() method on the builder instance will get the default value. If you want something else as type, just call the type() method on the builder instance.
Note that this only affects the builder. If you also want to instantiate via (no-args) constructor, you have to manually implement the constructor and set the default there.
I want to create a new Child instance passing a Parent and other additional parameters.
For example if I have:
public class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
}
public class Child extends Parent {
public String subValue;
}
With lombok, is there a builder that lets me create a Child instance passing the Parent and the missing value as parameters?
Would be easier if I could write something like:
Parent p = Parent.builder()
.param1("a")
.param2("b")
// many parameters
.paramN("b")
.build();
Child c = Child.builder(p).subValue("c").build();
Other answers don't truly make your client code simply reuse the parent instance you already have. But this is doable. You have two options:
The hard one is to write your custom annotation that does what you want. You can even make it generic so that it works for any classes the have parent/child hierarchy. Have a look at this example. If you feel brave you can raise a feature request on Lombok's github page.
Option two would be to write your custom builder for the child. See example here. In your custom builder in the init step you would be reading a passed in Parent instance, and setup the inherited fields only.
The regular #Builder is not sufficient here, because you are dealing with a class hierarchy. However, #SuperBuilder was made exactly for such a case.
#SuperBuilder generates complex code loaded with generics. That makes this solution difficult to understand without in-depth knowledge about the code #SuperBuilder generates. You should think about whether this is worth it.
Here's the solution (with Lombok >= 1.18.16):
#SuperBuilder(toBuilder = true)
public static class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
protected B $fillValuesFromParent(Parent instance) {
$fillValuesFromInstanceIntoBuilder(instance, this);
return self();
}
}
}
#SuperBuilder(toBuilder = true)
public static class Child extends Parent {
public String subValue;
public static ChildBuilder<?, ?> toBuilder(Parent p) {
return new ChildBuilderImpl().$fillValuesFromParent(p);
}
}
The new toBuilder method on Child creates a new ChildBuilderImpl (which will create a Child instance when calling build()). To fill the values from the given Parent p, it calls the new $fillValuesFromParent method from ParentBuilder. This method further delegates the call to the method $fillValuesFromInstanceIntoBuilder, which is generated by Lombok and performs the actual copying of the field values to the new builder instance.
Also note the $ prefix on the methods. This basically says: I'm an implementation detail; don't use me unless you know what you are doing, I might break on the next Lombok version without further notice.
I would suggest you use #SuperBuilder
#SuperBuilder was introduced as experimental feature in lombok v1.18.2.
The #SuperBuilder annotation produces complex builder APIs for your
classes. In contrast to #Builder, #SuperBuilder also works with fields
from superclasses. However, it only works for types. Most importantly,
it requires that all superclasses also have the #SuperBuilder
annotation.
#Getter
#SuperBuilder
public class Parent {
public String name;
public String value;
}
#Getter
#SuperBuilder
public class Child extends Parent {
public String subValue;
}
Then all you need to do is
Child.builder().name("a").value("b").subValue("c").build();
I'm creating a framework for spring-data-elasticsearch as a practice project.
My question is about the #Document tag that will create the index based on the name provided in indexName parameter of annotation.
However, I'm thinking is it possible to make it dynamic! In most of my usecases, the index name will match the class name. All my index classes will extend a abstract class which has generic implementation for all the and specific implementations needs to be done in the entity class.
This means, I have to maintain the #Document annotation for every entity. But since all the entities will extend a particular abstract class, is it possible to annotate the abstract class and somehow tell spring to use the class name as index name.
import org.springframework.data.elasticsearch.annotations.Document;
#Document(indexName = "BaseClassName OR something like Animal.getName" /*And other index properties of-course*/)
abstract class Animal {
String name;
public String getName() {
return name;
}
public abstract String makeSomeNoise();
}
All the concrete class that extends the Animals will be indexed in Elasticserch.
abstract class TwoLeggedAnimals extends Animal {}
abstract class FourLeggedAnimals extends Animal {}
The above two are just the grouping classes. For the sake of the example
class Duck extends TwoLeggedAnimals {
public Duck() {
this.name = Duck.class.getSimpleName();
}
#Override
public String makeSomeNoise() {
return "quack";
}
}
Class Duck extends TwoLeggedAnimals which in turn extends the "Animals" class and thus, Duck qualifies for index creation.
The same explanation for Horse class
class Horse extends FourLeggedAnimals {
Horse() {
this.name = Horse.class.getSimpleName();
}
#Override
public String makeSomeNoise() {
return "neigh";
}
}
You did not write what your specific problem or error is and what ES version you are using.
You can put the #Document annotation with the index name on an abstract baseclass and then use a derived class to store your entites into the index without adding some annotation on your derived class; this works with no problems.
But you cannot store different types (like TwoLeggedAnimals and FourLeggedAnimals) in the same index since Elasticsearch 6.0 (see ES 6.0 breaking changes). Your program will work as long as you are using one type, as soon as you try to store the second type, you will get
Elasticsearch exception [type=illegal_argument_exception, reason=Rejecting mapping update to [animals] as the final mapping would have more than 1 type: [twoleggedanimal, fourleggedanimal]]
The last 5.x version 5.6 had support until 2019-03-11 (Elastic end of life dates), so that's not supported anymore.
So, as it is not possible to store more than one type in an index, you will have to rethink your classes and how you store them - please check ES removal of types as well, if the alternatives outlined there might help you.
I am dealing with this problem. I have this class:
public class SemaphoreResponse {
ISemaphore semaphore;
StatusHolder statusHolder;
public SemaphoreResponse() {
super();
}
// Getters and setters
}
I want to convert my json string to that class, and it throws me this exception
org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.despegar.henry.automation.services.semaphoreservice.response.ISemaphore, problem: abstract types can only be instantiated with additional type information
at [Source: java.io.StringReader#17a5f5a5; line: 1, column: 2] (through reference chain: com.despegar.henry.automation.services.semaphoreservice.response.SemaphoreResponse["semaphore"])
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.StdDeserializationContext.instantiationException(StdDeserializationContext.java:233)
at org.codehaus.jackson.map.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:60)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
So, i understand that this is happening because the "semaphore" attribute, which is an interface, so into that interface i know i have to add #JsonDeseralize like this
#JsonDeserialize(as = [class-name].class)
public interface ISemaphore {
public abstract String getId();
public abstract void setId(String id);
public abstract String getOwnerUserId();
}
But this is my problem. The attribute semaphore from SemaphoreResponse doesn't use always same class. ergo, i have two different classes called "MainSemaphore" and "ExecutionSemaphore", which both implements the interface ISemaphore. So, at the time for deserealization i want to pass the class that i want the interface adapts for as a parameter.
It would be something like
#JsonDeserialize(as = MainSemaphore.class) or #JsonDeserialize(as = ExecutionSemaphore.class) depending the case
How could i do that? I would appreciate your help
Besides full polymorphic handling, described by excellent article by ProgrammerBruce, there is also a simpler way to just support simple interface/impl, one-to-one, case: register mapping via module:
SimpleModule m = new SimpleModule(...);
m.addAbstractTypeMapping(ISemaphore.class, SemaphoreImpl.class);
mapper.registerModule(m);
and this would instruct Jackson to always deserialize things declare as ISemaphore using concrete class SemaphoreImpl.
I'm wondering how an abstract class with generics would handle with JPA? I mean what kind of annotations do I need for the field?
Consider these:
#MappedSuperclass
public abstract class AbstractMyClass<T> {
// What about Strings and Integers? Do I need some kind of #LOB?
private T field;
public T getField() {
return field;
}
public void setField(T field) {
this.field = field;
}
}
And then these
#Entity
#Table(name = "String")
public class MyStringClass extends AbstractMyClass<String> {
}
#Entity
#Table(name = "Integer")
public class MyIntegerClass extends AbstractMyClass<Integer> {
}
JPA is perfectly able to handle your proposed, because the generic appears at the abstract class level and for your concrete classes it has exactly a single value per class. In fact, JPA will store your subclasses in one or more table, according to the #InheritanceStrategy you have chosen and uses different mechanism for that.
You can figure out yourself why your case is not a problem, reasoning about how an ORM could save the two classes on a DB:
You can store MyStringClass and MyIntegerClass in the same table, adding a Discriminator column so that the ORM, when it loads from the DB, know which constructor should be called.
You can store every subclass in more table.
What is not possible, on the other side, is to define a generic
#Entity
#Table(name = "MyGenericClass")
public class MyGenericClass<T> {
private T t;
public MyGenericClass(T t) {
this.t=t;
}
}
The reason for this is that, at compile time, the T is "erased" because of type erasure. It is used at compile time to verify signatures and correctness of types, but then it is turned into a java.lang.Object inside the JVM. If you follow until now, you should be able to understand the following:
In your case, every concrete subclass of AbstractMyClass has a type T which is defined for all instances of the class. While the T information is not retained into the AbstractMyClass, it is retained and unique inside the subclasses.
In the second case I posted, each possible concrete instance of MyGenericClass could have a possible different value for T, and because of type erasure this information is not retained.
*Note: the fact that the second case cannot be handled by JPA is absolutely reasonable and if you fall in that case you should ask yourself questions about your design. Generics are a great tool to design flexible classes which can handle other classes in a type-safe manner, but type-safe is a programming language concept which has nothing to do with persistance.
Extra : you could use javap to see what really is erasure. Take off annotations from MyGenericClass and compile it.
G:\>javac MyGenericClass.java
G:\>javap -p MyGenericClass
Compiled from "MyGenericClass.java"
public class MyGenericClass extends java.lang.Object{
private java.lang.Object t;
public MyGenericClass(java.lang.Object);
}
We can. if the T implements Serializable
#Entity
public class IgsSubject extends BasicObject implements Serializable{
private static final long serialVersionUID = -5387429446192609471L;
#MappedSuperclass
public class IgsBasicLog<T> extends BasicObject {
#ManyToOne
#JoinColumn(name = "ITEM_ID")
private T item;
#Entity
public class IgsLogA extends IgsBasicLog<IgsSubject> implements Serializable {
private static final long serialVersionUID = -8207430344929724212L;
}