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.
Related
I am working in the design of a SDK that will provide: basic definitions (interfaces), logging and transaction engine for different projects. Each project will be considered as a platform and will be developed as a different project using as basis the SDK; they has similarities but each implementation should be able to solve specific behaviors, however the basic definitions from the core SDK should solve most part of the problems. For example: HUUniversity or MITUniversity
So far I have almost all achieved for example: there is StudentManager interface that provide essential behavior for any implementation:
public interface StudentManager<T> extends Manager, Release {
int getCurrentStudent();
int getTotalStudent();
TransactionManager getStudentManager();
List<T> getStudent();
void addStudent(T participant);
T getStudent(String id);
T removeStudent(String id);
}
That way each platform implementation will be able to implement its own definition where basically extend from the basic definition provided within the SDK but the each implementation will be strongly typed and would be able to implement new behaviors:
public interface HUStudentManager extends StudentManager<HUStudent>, ParticipantListener {
List<HUStudentCommand> getCommands(String audioId);
HUStudent getParticipant(ListType list, String id);
HUStudent getParticipantByName(String name);
List<HUStudent> getParticipants(StudentState state);
List<HUStudent getParticipantsOnList(ListType list);
List<HUStudent> getParticipantsOnList(ListType list, Sort sort);
void addParticipantOnList(HUStudent participant, ListType listType, long epoch);
HUStudentCommand removeCommand(String id);
HUStudentCommand removeParticipantByName String name);
void saveCommand(HUStudentCommand command)
}
Implementation: the HU platform has its own definition of a StudentManager (HUStudentManager), and extend from the basis (defined on the SDK) since the SDK doesn't know about any HUStudent definition I added a generic param to it so each
public class HUStudentManagerImpl extends HU implements
HUStudentManager<HUStudent> {
#Override
public void addStudent(HUStudent student) {
if(Utils.isNull(m_students.putIfAbsent(student.getId(), participant))){
m_totalStudents.incrementAndGet();
getLogger().log(Keywords.DEBUG,"{0}The instance: {1} with the specified key: {2} has been added to the ConcurrentMap<String, HUStudents>", getData(), student.getClass().toString(), student.getId());
}else{
getLogger().log(Keywords.WARNING,"{0}The instance: {1} with the specified key: {2} already exists in the ConcurrentMap<String, HUStudents>", getData(), student.getClass().toString(), student.getId());
}
}
}
The sample above works fine and solve the problem where I leave to each developer use his own definitions for the specific platform which of course will extend from the basic definitions
But I wasn't able to figured out how let the developer to use his own definition for a single type within the interface definition ie:
public interface Student extends IManager, IRelease {
UUID getUUID();
String getId();
<T> T getSchedule();
<T> T getElapsedTime();
}
I pretend that the basic interface allows to each developer to use its own definition force them to implement a basic behavior or implement a new one but extending from the existing on the SDK:
public interface HUStudent extends Student {
HUClass getClass()
}
How can I implement this on the final HUStudentImpl class without get the compiler error in order to suppress the types. is that possible or should I shadowed the definitions in super class with the desire type
public interface HUStudentImpl extends HU, implements HUStudent {
//Type safety: The expression of type getSchedule() needs unchecked
//conversion to conform to HUSchedule
HUSchedule getSchedule(); //Def from Student interface at SDK
HUElapsedTime getElapsedTime(); //Def from Student interface at SDK
}
I cannot use parameter over the Student interface since each getter could be a different type.
Hope someone can enlighten me and point me in the right direction.
Thanks in advance, best regards.
I've got around 5 objects that I want to do similar things with.
I figured out that not to polute the code I will put a logic for those objects in one place.
public class MetaObjectController<T extends MetaObject> {
#Autowired
private final MetaObjectRepository<T> repository;
// generic logic
Here's how repository looks:
public interface MetaObjectRepository<T extends MetaObject> extends GraphRepository<T> {
T findByName(String name);
}
Now, I create concrete class which uses delegation:
public class ExperimentalController {
#Autowired
private final MetaObjectController<MetaCategory> metaController;
#RequestMapping(method = RequestMethod.POST)
public void add(#RequestBody MetaCategory toAdd) {
metaController.add(toAdd);
}
Now, when I look at the generated queries I see, that although instantiated correctly, repository puts MetaObject as an entity name instead of runtime type.
Is there a way to force the repository to use runtime type?
Please don't advise to put a #Query annnotation. That's not what I am looking for.
This is most probably due to type erasure: at runtime there is only the type constraint available which is MetaObject. If you want to use (via spring-data) the actually relevant subclass you will have to create explicit interfaces of the MetaObjectRepository like this:
public class Transmogrifier extends MetaObject
public interface MetaTransmogrifierRepository
extends MetaObjectRepository<Transmogrifier> {}
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.
In my library there is a concept of "user levels". I have provided several default levels but for various reasons want to give the user the option of using their own levels.
Currently this is implemented as
public interface AdminLevel {
public void name();
}
public enum StandardAdminLevels implements AdminLevel {
ADMIN,
ANONYMOUS
}
The problem is that the user is usually going to be passing their required user level in an annotation. Things I have tried and failed:
Using AdminLevel as the type - Fails with "invalid type for annotation member"
Using String as the type but setting the value with StandardAdminLevels.ADMIN.name() - Fails with "attribute value must be constant"
Making StandardAdminLevels a final class that doesn't implement anything with public static final field's for each of the levels (essentially an enum) - Fails with "invalid type for annotation member"
Is there any other way I can't think of to have extendable enums in annotations? I'm trying to stick with enums due to their clarity and basic protection against invalid values, but the only other way I can think of is String constants. The problem I have is that would require verification at every single point user levels are used, potentially even in client code
Any ideas?
One idea: every possible AdminLevel is its own class. Pass the Class of that class to the annotation.
public final class Admin implements AdminLevel { ... }
public final class Anonymous implements AdminLevel { ... }
#Requires(Admin.class) public void protectedMethod() { ... }
#interface Requires {
Class<? extends AdminLevel> value();
}
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;
}