Search code examples
google-app-enginegoogle-cloud-datastoreobjectify

Put Text type to datastore with Objectify 6


I'm currently migrating project's DAO classes from JDO implementation to Objectify V6. The requirement I have, is to make sure that in a case of rollback it will be possible to load entities, which were saved by Objectify, with the old version of DAO.

In old code strings are stored as Text. And if I leave Text field in entity definition, Objectify puts it as a String to datastore (because there is no Text type any more).

Currently new DAO implementation is not backward compatible because of a ClassCastException which arise when JDO implementation casts String to Text type.

Is there a way to store Text type to datastore with Objectify V6?

I tried to use String instead of Text in entity definition and create a TranslatorFactory to make the conversion, but I wasn't able to find correct datastore Value implementation type.

public class StringTextTranslatorFactory implements TranslatorFactory<String, Text> {

  @Override
  public Translator<String, Text> create(TypeKey<String> tk, CreateContext ctx, Path path) {

    return new Translator<String, Text>() {

      @Override
      public String load(Value<Text> node, LoadContext ctx, Path path) throws SkipException {
        Text text = node.get();
        return text != null ? text.getValue() : "";
      }

      @Override
      public Value<Text> save(String pojo, boolean index, SaveContext ctx, Path path)
          throws SkipException {


        return ???;


      }
    };
  }
}

Update

The project is using an implementation of JDO 2.3 for the App Engine Datastore. The implementation is based on version 1.0 of the DataNucleus Access Platform.

Data entity defined as the following:

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class CrmNote {
  @PrimaryKey
  @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  private Long id;
  @Persistent
  private Text note;
}

Stacktrace:

java.lang.ClassCastException: java.lang.String cannot be cast to com.google.appengine.api.datastore.Text
    at com.timzon.snapabug.server.data.CrmNote.jdoReplaceField(CrmNote.java)
    at com.timzon.snapabug.server.data.CrmNote.jdoReplaceFields(CrmNote.java)
    at org.datanucleus.state.JDOStateManagerImpl.replaceFields(JDOStateManagerImpl.java:2772)
    at org.datanucleus.state.JDOStateManagerImpl.replaceFields(JDOStateManagerImpl.java:2791)
    at org.datanucleus.store.appengine.DatastorePersistenceHandler.fetchObject(DatastorePersistenceHandler.java:519)
    at org.datanucleus.store.appengine.query.DatastoreQuery.entityToPojo(DatastoreQuery.java:649)
    at org.datanucleus.store.appengine.query.DatastoreQuery.entityToPojo(DatastoreQuery.java:603)
    at org.datanucleus.store.appengine.query.DatastoreQuery.access$300(DatastoreQuery.java:119)
    at org.datanucleus.store.appengine.query.DatastoreQuery$6.apply(DatastoreQuery.java:783)
    at org.datanucleus.store.appengine.query.DatastoreQuery$6.apply(DatastoreQuery.java:774)
    at org.datanucleus.store.appengine.query.LazyResult.resolveNext(LazyResult.java:94)
    at org.datanucleus.store.appengine.query.LazyResult.resolveAll(LazyResult.java:116)
    at org.datanucleus.store.appengine.query.LazyResult.size(LazyResult.java:110)
    at org.datanucleus.store.appengine.query.StreamingQueryResult.size(StreamingQueryResult.java:130)
    at org.datanucleus.store.query.AbstractQueryResult.toArray(AbstractQueryResult.java:399)
    at java.util.ArrayList.<init>(ArrayList.java:178)
    at com.timzon.snapabug.server.dao.CrmNoteDAO.getOrderedCrmNotes(CrmNoteDAO.java:27)

Exception happens in auto-generated jdoReplaceField method which is added by JDO post-compilation "enhancement". I decompiled enhanced class and I see that datastore object is casted to Text type directly:

    public void jdoReplaceField(int index) {
        if (this.jdoStateManager == null) {
            throw new IllegalStateException("state manager is null");
        } else {
            switch(index) {
            case 0:
                this.id = (Long)this.jdoStateManager.replacingObjectField(this, index);
                break;
            case 1:
                this.note = (Text)this.jdoStateManager.replacingObjectField(this, index);
                break;
            default:
                throw new IllegalArgumentException("out of field index :" + index);
            }
        }
    }

So, if note field is saved in data store as a String, then in case of rollback a ClassCastException will be thrown.


Solution

  • There's no way to explicitly store a Text type with the Google-provided SDK that Objectify 6 uses; there is only StringValue. Text is not even in the jar.

    However, I don't think this should matter. Ultimately both SDKs (the old appengine one and the new one) are just converting back and forth to protobuf structures. They are supposed to be compatible.

    It's especially strange because the old low level API wrote strings into the Entity structure; Text was required only if the strings exceeded a certain length. So JDO should handle String. Do you have some sort of special annotation on your String field to force it to expect Text? What does that stacktrace look like?