/*
* $Id: BeanMixin.java 1075 2009-05-07 06:41:19Z lhoriman $
* $URL: https://subetha.googlecode.com/svn/branches/resin/rtest/src/org/subethamail/rtest/util/BeanMixin.java $
*/
package com.googlecode.objectify.test;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Id;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.memcache.MemcacheSerialization;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.annotation.Cached;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.impl.CachingDatastoreService;
/**
* Tests of a bizarre bug in Google's memcache serialization of Key objects.
*
* @author Jeff Schnitzer <jeff@infohazard.org>
*/
public class EvilMemcacheBugTests extends TestBase
{
/** */
private static Logger log = LoggerFactory.getLogger(EvilMemcacheBugTests.class);
/** */
static class SimpleParent
{
@Id String id;
SimpleParent(){}
SimpleParent(String id) {
this.id = id;
}
static Key<SimpleParent> getSimpleParentKey(String id) {
return new Key<SimpleParent>(SimpleParent.class, id);
}
}
/** */
@Cached
static class SimpleEntity
{
@Id
String id;
@Parent Key<SimpleParent> simpleParentKey;
String foo = "bar";
static Key<SimpleEntity> getSimpleChildKey(String id) {
return new Key<SimpleEntity>(SimpleParent.getSimpleParentKey(id), SimpleEntity.class, id);
}
SimpleEntity() {}
SimpleEntity(String id) {
this.id = id;
this.simpleParentKey = SimpleParent.getSimpleParentKey(id);
}
}
/** */
@Test
public void testMoreSophisticatedInAndOutOfTransaction() throws Exception {
this.fact.register(SimpleParent.class);
this.fact.register(SimpleEntity.class);
String simpleId = "btoc";
SimpleEntity simple = new SimpleEntity(simpleId);
Objectify nonTxnOfy = this.fact.begin();
nonTxnOfy.put(simple);
Objectify txnOfy = this.fact.beginTransaction();
SimpleEntity simple2;
try {
simple2 = txnOfy.get(SimpleEntity.getSimpleChildKey(simpleId));
simple2.foo = "joe";
txnOfy.put(simple2);
txnOfy.getTxn().commit();
} finally {
if (txnOfy.getTxn().isActive())
txnOfy.getTxn().rollback();
}
SimpleEntity simple3 = nonTxnOfy.get(SimpleEntity.getSimpleChildKey(simpleId));
assert simple2.foo.equals(simple3.foo);
}
/** */
@Test
public void testRawTransactionalCaching() throws Exception {
// Need to register it so the entity kind becomes cacheable
this.fact.register(SimpleEntity.class);
DatastoreService ods = DatastoreServiceFactory.getDatastoreService();
DatastoreService ds = new CachingDatastoreService(this.fact, ods);
// This is the weirdest thing. If you change the *name* of one of these two keys, the test passes.
// If the keys have the same *name*, the test fails because ent3 has the "original" property. WTF??
com.google.appengine.api.datastore.Key parentKey = KeyFactory.createKey("SimpleParent", "asdf");
com.google.appengine.api.datastore.Key childKey = KeyFactory.createKey(parentKey, "SimpleEntity", "asdf");
Entity ent1 = new Entity(childKey);
ent1.setProperty("foo", "original");
ds.put(ent1);
// Weirdly, this will solve the problem too
//MemcacheService cs = MemcacheServiceFactory.getMemcacheService();
//cs.clearAll();
Transaction txn = ds.beginTransaction();
Entity ent2;
try {
ent2 = ods.get(txn, childKey);
//ent2 = new Entity(childKey); // solves the problem
ent2.setProperty("foo", "changed");
ds.put(txn, ent2);
txn.commit();
} finally {
if (txn.isActive())
txn.rollback();
}
Entity ent3 = ds.get(childKey);
assert "changed".equals(ent3.getProperty("foo"));
}
/** */
@SuppressWarnings("unchecked")
@Test
public void testRawCaching() throws Exception {
// I can not for the life of me figure out why this test passes when the
// previous test fails.
MemcacheService cs1 = MemcacheServiceFactory.getMemcacheService();
cs1.setNamespace("blah");
com.google.appengine.api.datastore.Key parentKey = KeyFactory.createKey("SimpleParent", "asdf");
com.google.appengine.api.datastore.Key childKey = KeyFactory.createKey(parentKey, "SimpleEntity", "asdf");
Entity ent = new Entity(childKey);
ent.setProperty("foo", "original");
cs1.put(childKey, ent);
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
ds.put(ent);
Transaction txn = ds.beginTransaction();
Entity ent2 = ds.get(txn, childKey);
//Entity ent2 = (Entity)cs1.get(childKey);
assert ent2.getProperty("foo").equals("original");
ent2.setProperty("foo", "changed");
Map<Object, Object> holder = new HashMap<Object, Object>();
holder.put(childKey, ent2);
cs1.putAll(holder);
Map<Object, Object> fetched = cs1.getAll((Collection)Collections.singleton(childKey));
Entity ent3 = (Entity)fetched.get(childKey);
assert ent3.getProperty("foo").equals("changed");
}
/** */
@Test
public void testEntityKeys() throws Exception
{
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
com.google.appengine.api.datastore.Key parentKeyA = KeyFactory.createKey("SimpleParent", "same");
com.google.appengine.api.datastore.Key childKeyA = KeyFactory.createKey(parentKeyA, "SimpleEntity", "different");
Entity entA1 = new Entity(childKeyA);
ds.put(entA1);
Entity entA2 = ds.get(childKeyA);
assert new String(MemcacheSerialization.makePbKey(entA1.getKey())).equals(new String(MemcacheSerialization.makePbKey(childKeyA)));
assert new String(MemcacheSerialization.makePbKey(entA2.getKey())).equals(new String(MemcacheSerialization.makePbKey(childKeyA)));
com.google.appengine.api.datastore.Key parentKeyB = KeyFactory.createKey("SimpleParent", "same");
com.google.appengine.api.datastore.Key childKeyB = KeyFactory.createKey(parentKeyB, "SimpleEntity", "same");
Entity entB1 = new Entity(childKeyB);
ds.put(entB1);
Entity entB2 = ds.get(childKeyB);
// This works
assert new String(MemcacheSerialization.makePbKey(entB1.getKey())).equals(new String(MemcacheSerialization.makePbKey(childKeyB)));
// This fails! It is a bug in the datastore. See http://code.google.com/p/googleappengine/issues/detail?id=2088
// Objectify works around this problem, so it is not a serious issue.
assert new String(MemcacheSerialization.makePbKey(entB2.getKey())).equals(new String(MemcacheSerialization.makePbKey(childKeyB)));
}
@SuppressWarnings("unchecked")
@Test
public void testWithoutObjectify() throws Exception {
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
MemcacheService ms = MemcacheServiceFactory.getMemcacheService();
ms.setNamespace("testing1423");
com.google.appengine.api.datastore.Key parentKey = KeyFactory.createKey("SimpleParent", "asdf");
com.google.appengine.api.datastore.Key childKey = KeyFactory.createKey(parentKey, "SimpleEntity", "asdf");
//save a test entity
Entity ent1 = new Entity(childKey);
ent1.setProperty("foo", "original");
ds.put(ent1);
ms.put(ent1.getKey(), ent1);
//load it, and change the value.
Entity ent1loaded = ds.get(ent1.getKey());
ent1loaded.setProperty("foo", "changed");
//save it to the datastore and memcache
ds.put(ent1loaded);
ms.put(ent1loaded.getKey(), ent1loaded);
//dump memcache -- silly GAE can't hide this method from me!
Method meth = ms.getClass().getMethod("grabTail", int.class);
meth.setAccessible(true);
List dump = (List)meth.invoke(ms, 100);
for(Object obj : dump)
log.info(obj.toString());
assert (dump.size() == 1);
// Entity ent3 = (Entity) ms.getAll((Collection)Collections.singleton(childKey)).values().toArray()[0];
// assert "changed".equals(ent3.getProperty("foo"));
}
}