Reputation: 11
I have a class to be saved into appengine datastore, which among others contains, a Text field (String-like appengine datatype, but not limited to 500 chars). Also a twin class which is basically the same, but is used on the client side (ie without any com.google.appengine.api.datastore.* import).
Is there any datatype, which would let me save the Text server-side field into client-Side?
A possible option would be split the Text into some Strings, but that sounds pretty ugly...
Any suggestions?
Upvotes: 1
Views: 741
Reputation: 106
some additions to custom serializable libraries posted before
( http://juristr.com/blog/2010/02/gwt-app-engine-and-app-engine-data/ http://www.resmarksystems.com/code/ - get com.google.appengine.api.datastore.Text and other datastore types transferred to client)
need also to update com.google.appengine.eclipse.core.prefs to include library: filesCopiedToWebInfLib=...|appengine-utils-client-1.1.jar
another workaround is making string serializable blob to overcome 1500 bytes limit (it will lost sort and filter ability fir this field):
@Persistent(serialized = "true")
public String content;
it is possible to have less overhead on client with converting from com.google.appengine.api.datastore.Text to String with lifecycle listeners (not instance listeners, they will got send to client and make it fail). use it together with custom serialization which allows client support for com.google.appengine.api.datastore.Text with no additional transport class is required.
com.google.appengine.api.datastore.Text may be cleared before sending to client to avoid sending overhead (simplest way is to mark it transient).
on server side we have to avoid setting String property directly, because jdo will not catch it change (will catch only for new records or when some persistent field is modified after). this is very little overhead.
detaching of records should be performed via pm.makeTransient. when using pm.detachCopy it is required to mark entity as detachable = "true" (DetachLifecycleListener to be called) and implement DetachLifecycleListener.postDetach similar way as StoreLifecycleListener.preStore. othwerwise non-persistent fields will not be copied (by pm.detachCopy) and will be empty on client.
it is possible to handle several classes similar way
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.listener.DetachLifecycleListener;
import javax.jdo.listener.InstanceLifecycleEvent;
import javax.jdo.listener.LoadLifecycleListener;
import javax.jdo.listener.StoreLifecycleListener;
import com.google.appengine.api.datastore.Text;
import com.mycompany.mywebapp.shared.Entity;
import com.mycompany.mywebapp.shared.Message;
@SuppressWarnings("rawtypes")
public class PersistenceManagerStuff
{
public static final PersistenceManagerFactory PMF = JDOHelper.getPersistenceManagerFactory("transactions-optional");
public static EntityLifecycleListener entityLifecycleListener = new EntityLifecycleListener();
public static Class[] entityClassList = new Class[] { Entity.class };
public static MessageLifecycleListener messageLifecycleListener = new MessageLifecycleListener();
public static Class[] messageClassList = new Class[] { Message.class };
public static PersistenceManager getPersistenceManager()
{
PersistenceManager pm = PMF.getPersistenceManager();
pm.addInstanceLifecycleListener(entityLifecycleListener, entityClassList);
pm.addInstanceLifecycleListener(messageLifecycleListener, messageClassList);
return pm;
}
// [start] lifecycle listeners
public static class EntityLifecycleListener implements LoadLifecycleListener, StoreLifecycleListener//, DetachLifecycleListener
{
public void postLoad(InstanceLifecycleEvent event)
{
Entity entity = ((Entity) event.getSource());
if (entity.content_long != null)
entity.content = entity.content_long.getValue();
else
entity.content = null;
}
public void preStore(InstanceLifecycleEvent event)
{
Entity entity = ((Entity) event.getSource());
entity.setContent(entity.content);
/*
need mark class @PersistenceAware to use code below, otherwise use setter
if (entity.content != null)
entity.content_long = new Text(entity.content);
else
entity.content_long = null;
*/
}
public void postStore(InstanceLifecycleEvent event)
{
}
/*public void postDetach(InstanceLifecycleEvent event)
{
}
public void preDetach(InstanceLifecycleEvent event)
{
}*/
}
public static class MessageLifecycleListener implements LoadLifecycleListener, StoreLifecycleListener
{
public void postLoad(InstanceLifecycleEvent event)
{
Message message = ((Message) event.getSource());
if (message.content_long != null)
message.content = message.content_long.getValue();
else
message.content = null;
}
public void preStore(InstanceLifecycleEvent event)
{
Message message = ((Message) event.getSource());
message.setContent(message.content);
}
public void postStore(InstanceLifecycleEvent event)
{
}
}
// [end] lifecycle listeners
}
@SuppressWarnings("serial")
@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "false")
public class Entity implements Serializable
{
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
public Long id;
@NotPersistent
public String content;
@Persistent(column = "content")
public transient com.google.appengine.api.datastore.Text content_long;
public void setContent(String content)
{
this.content = content;
if (content != null)
content_long = new Text(content);
else
content_long = null;
}
public Entity() {}
}
@PersistenceAware
public class DataServiceImpl extends RemoteServiceServlet implements DataService
{
public Entity renameEntity(long id, String newContent) throws NotLoggedInException
{
PersistenceManager pm = PersistenceManagerStuff.getPersistenceManager();
Entity result = null;
try
{
Entity entity = (Entity) pm.getObjectById(Entity.class, id);
if (entity.longUserId != getLongUserId(pm))
throw new NotLoggedInException(String.format("wrong entity %d ownership", entity.id));
entity.modificationDate = java.lang.System.currentTimeMillis(); // will call lifecycle handlers for entity.content, but is still old value
//entity.content = newContent; // will not work, even owner class is @PersistenceAware
entity.setContent(newContent); // correct way to set long value
pm.makeTransient(result = entity);
}
catch (Exception e)
{
LOG.log(Level.WARNING, e.getMessage());
throw e;
}
finally
{
pm.close();
}
return result;
}
}
also in lifecycle handlers it is possible to mix old (short) and new (long) values into single entity if you have both (with different field names) and do not want to convert old to new. but is seems com.google.appengine.api.datastore.Text supports loading from old String values.
some low-level code to batch convert old values into new (using low level com.google.appengine.api.datastore api):
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Query q = new Query("Entity");
PreparedQuery pq = datastore.prepare(q);
for (com.google.appengine.api.datastore.Entity result : pq.asIterable())
{
String content = (String) result.getProperty("content");
if (content != null)
{
result.setProperty("content", new com.google.appengine.api.datastore.Text(content));
datastore.put(result);
}
}
Upvotes: 0
Reputation: 611
You can use Text for your persistable field. You just need to have a RPC serializer to be able to use it on the client (in GWT). Take a look at http://blog.js-development.com/2010/02/gwt-app-engine-and-app-engine-data.html, it explains how to do it.
Upvotes: 1