Deviling Master
Deviling Master

Reputation: 3113

Objectify Translator as JPA Attribute Converter

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

Answers (2)

Deviling Master
Deviling Master

Reputation: 3113

Solved using the @Translate which can be applied to specific fields

Upvotes: 0

stickfigure
stickfigure

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

Related Questions