Reputation: 3113
I have a POJO structure like this
@Entity
public class ClassA {
@Id
public Long id;
public ClassB classbObject;
}
And another class
@Entity
public class ClassB {
@Id
public Long id;
public String fieldA;
public String fieldB;
}
Now, basing on custom conditions, I need to save the classbObject
like an embedded one (default behaviour of Objectify) or only the id property (here it comes the Translator
).
This condition is also applied even with a single field ClassA
or a multiple field List<ClassA>
If the object will be saved as just ID, I also need to Index it to make query on that.
I already asked on the Objectify board and the reply is both useful (use the Translator) and incomplete (I can't find any documentation on how to use the Translator interface).
Two problem then I'm facing now:
1) Apply the translator only to specific objects (not saving the embedded object but just the id) and not all the objects related to the same kind.
With JPA this request can be made with the use of the @Converter
annotation
So, with this design, the class should be something like this
@Entity
public class ClassA {
@Id
public Long id;
@MyCustomTranslator
public ClassB classbObject;
// Default behaviour
public ClassB embeddedObject;
}
I need to specify that I don't need an hybrid persistence, a Java variable will be always save in the same way (embedded or just id), but I need to differentiate if a class (for example) has two variables of the same type or I have the same variable type in 2 differente classes
2) Alternatively of the first point, moving the decision in the Translator itself, so the "convert pojo to id" code use the coded assumptions to determine if the object should be stored as default embedded or convert it to a ID value.
Without documentation I tried to emulate the already existing Translator from Objectify source code, but I cannot get a working one.
This is the first attempt I tried
public class TestTranslator implements TranslatorFactory<ClassA, Long> {
@Override
public Translator<ClassA, Long> create(TypeKey<ClassA> arg0, CreateContext arg1, Path arg2) {
return new CustomTranslator();
}
public static class CustomTranslator implements Translator<ClassA, Long> {
@Override
public ClassA load(Long arg0, LoadContext arg1, Path arg2) throws SkipException {
ClassA a = new ClassA();
a.id = arg0;
// Other custom code
return a;
}
@Override
public Long save(ClassA arg0, boolean arg1, SaveContext arg2, Path arg3) throws SkipException {
return arg0.id;
}
}
}
Here is the code that I use for init
ObjectifyService.factory().getTranslators().add(new TestTranslator());
ObjectifyService.register(ClassA.class);
ObjectifyService.register(ClassB.class);
This does not work because I get a very long stacktrace during the init, with the main error
TestTranslator$CustomTranslator cannot be cast to com.googlecode.objectify.impl.translate.ClassTranslator
Also, this is not solving my number 1 problem, so I cannot decide if embed the Object as normal or apply my Translator.
Second attempt, with another superclass of Translator
public class TestValueTranslator extends ValueTranslatorFactory<ClassA, Long> {
public TestValueTranslator() {
super(ClassA.class);
}
@Override
protected ValueTranslator<ClassA, Long> createValueTranslator(TypeKey<ClassA> tk, CreateContext ctx, Path path) {
return new ValueTranslator<ClassA, Long>(Long.class) {
@Override
protected ClassA loadValue(Long value, LoadContext ctx, Path path) throws SkipException {
ClassA a = new ClassA();
a.id = value;
// Other custom code
return a;
}
@Override
protected Long saveValue(ClassA value, boolean index, SaveContext ctx, Path path) throws SkipException {
return value.id;
}
};
}
}
Same error as above
TestValueTranslator$1 cannot be cast to com.googlecode.objectify.impl.translate.ClassTranslator
It seems that Objectify is forcing me to use a ClassTranslator, which I tried to implement
public class TestClassTranslatorFactory extends ClassTranslatorFactory<ClassA> {
@Override
public ClassTranslator<ClassA> create(TypeKey<ClassA> tk, CreateContext ctx, Path path) {
// ????
return super.create(tk, ctx, path);
}
public class TestClassTranslator extends ClassTranslator<ClassA> {
public TestClassTranslator(Class<ClassA> declaredClass, Path path, Creator<ClassA> creator, Populator<ClassA> populator) {
super(declaredClass, path, creator, populator);
}
@Override
public ClassA loadSafe(PropertyContainer arg0, LoadContext arg1, Path arg2) throws SkipException {
EmbeddedEntity e = (EmbeddedEntity) arg0;
Long id = (Long) e.getProperty("id");
ClassA a = new ClassA();
a.id = id;
// Other custom code
return a;
}
@Override
public PropertyContainer saveSafe(ClassA arg0, boolean arg1, SaveContext arg2, Path arg3) throws SkipException {
EmbeddedEntity e = new EmbeddedEntity();
e.setProperty("id", arg0.id);
return e;
}
}
}
Two problems here:
1) I cannot understand how to create a TestClassTranslator
object in the TestClassTranslatorFactory
constructor.
2) Even if this class works, I'm force to create an embedded entity (with the id inside) and I cannot save as a pure Long (o List). Because of that the indexing part I'm not sure can be accomplished.
At the end, 2 questions:
1) Is there a correct Translator
to convert a class object to the pure id?
2) How can the converter be applied only to certain variables of the same type?
Upvotes: 0
Views: 560
Reputation: 3113
Solved using the @Translate
which can be applied to specific fields
Upvotes: 0
Reputation: 13556
There's a lot going on here and stackoverflow is probably a poor format for the back-and-forth dialogue which will eventually get you where you want to be. There is a google group which will likely be more useful.
Your first issue is that the contract for TranslatorFactory
requires that create()
return null if the TypeKey
is not appropriate for that factory. At registration time, factories are tried in order until one of them succeeds; the Translator
then becomes part of a fast-but-static metamodel for translating between POJOs and Entities. By always returning a translator, you're "claiming" everything during this discovery period. So check if the type is correct and return null if the type is not for you.
Another useful piece of knowledge is that there is a @Translate
annotation that you can use on specific fields which will force them to be processed by a particular translator. You can use this with factories that you do not register in advance, so you can selectively modify the behavior of certain fields without affecting other uses of that type.
Upvotes: 1