Here is a subset of the code. It doesn't run or have the logic that changes
the values. I have included the ChallengeDao, which highlights what I had
to do to make work correctly. If I remove the PM.closePersistentManager()
it fails in many different ways. I can even get a failure about can't
modify multiple entities from different groups in the same transaction, and
I don't even have transactions.
My goal is to have one PersistenceManager per request. That way I don't
have to re-query the objects from datastore twice, because I have read-only
logic afterwards that packets the data needed for the GWT front end.
Like I said before, I am will to share the entire code base with people that
are working on these issues in datanucleus code, but not to the entire
group. I am willing to answer any questions you might have.
Thanks,
Jeffrey
public class *PM* {
private PM() {
}
static private PersistenceManagerFactory pmfInstance;
static private HashMap<Thread, PersistenceManager> pmSet = new
HashMap<Thread, PersistenceManager>();
static private HashSet<Thread> testThreads = new HashSet<Thread>();
static private long testId = 0;
static private HashMap<Object, Object> testObj;
synchronized static private void createPmfInstance() {
if (pmfInstance == null) {
pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
}
}
static public void startTest() {
testThreads.add(Thread.currentThread());
}
static public void endTest() {
testThreads.remove(Thread.currentThread());
}
static public Long createTestId(Object obj) {
if (!testThreads.contains(Thread.currentThread())) {
throw new IllegalStateException
("TestId is illegal when PersistenceManagerFactory is
active");
}
if (testObj == null) {
testObj = new HashMap<Object, Object>();
}
long newId = testId++;
while (testObj.containsKey(newId)) {
newId = testId++;
}
testObj.put(newId, obj);
return newId;
}
static public Long setTestId(long id, Object obj) {
if (!testThreads.contains(Thread.currentThread())) {
throw new IllegalStateException
("TestId is illegal when PersistenceManagerFactory is
active");
}
if (testObj == null) {
testObj = new HashMap<Object, Object>();
}
testObj.put(id, obj);
return id;
}
static public PersistenceManager getPersistenceManager() {
if (pmfInstance == null) {
createPmfInstance();
}
PersistenceManager pm = pmSet.get(Thread.currentThread());
if (pm == null) {
pm = pmfInstance.getPersistenceManager();
pmSet.put(Thread.currentThread(), pm);
}
return pm;
}
static public void closePersistenceManager() {
PersistenceManager pm = pmSet.remove(Thread.currentThread());
if (pm != null) {
pm.close();
}
}
static public void rollbackPersistenceManager() {
org.datanucleus.jdo.JDOPersistenceManager pm =
(JDOPersistenceManager) PM.getPersistenceManager();
ObjectManager om = pm.getObjectManager();
om.clearDirty();
PM.closePersistenceManager();
}
static public <T> T getObjectById(Class<T> cls, Object key) {
Object obj;
if (testThreads.contains(Thread.currentThread())) {
obj = testObj.get(key);
} else {
obj = getPersistenceManager().getObjectById(cls, key);
}
return (T) obj;
}
static public <T> Collection<T> getObjectsById(Class<T> cls,
Collection<?> keys) {
Collection<T> collection = new LinkedList<T>();
if (testThreads.contains(Thread.currentThread())) {
if (keys != null && !keys.isEmpty()) {
for (Object key : keys) {
collection.add((T) testObj.get(key));
}
}
} else {
if (keys != null && !keys.isEmpty()) {
//TODO this is not optimized for Batch gets - switch to low
level api
PersistenceManager pm = getPersistenceManager();
LinkedList<Object> oids = new LinkedList<Object>();
for (Object key : keys) {
oids.addLast(pm.newObjectIdInstance(cls, key));
}
collection = pm.getObjectsById(oids);
}
}
return collection;
}
}
//*********************************************************************************************************************
public class *ChallengeDao* {
public ChallengeDao() {
}
public Challenge newChallenge(PlayerId targetId, PlayerId attackerId,
Troops troops) {
PM.closePersistenceManager();
PersistenceManager pm = PM.getPersistenceManager();
PlayerDao playerDao = SessionUtil.getPlayerDao();
Player target = playerDao.getPlayer(targetId);
//This is to prevent the challenge from being created if active
challenge can't be set.
if (target.getActiveChallengeAgainstId() != null) {
throw new SlowPlayException("Player is already a target of a
challenge");
}
Player attacker = playerDao.getPlayer(attackerId);
Challenge challenge = new Challenge(target, attacker);
pm.makePersistent(challenge);
IntegrityCheck ic = IntegrityCheck.start("New Challenge");
target.setActiveChallengeAgainst(challenge);
pm.makePersistent(target);
PM.closePersistenceManager();
Logs.Object.info("newChallenge:" + challenge);
joinChallenge(challenge.getId(), attackerId, troops);
ic.finish();
return challenge;
}
public ChallengeMove joinChallenge(ChallengeId challengeId, PlayerId
joinerId, Troops troops) {
if (getChallengeMoveByChallengePlayer(challengeId, joinerId) !=
null) {
throw new SlowPlayException("Already in battle, can't join
again");
}
PersistenceManager pm = PM.getPersistenceManager();
Challenge challenge = getChallenge(challengeId);
Player joiner = SessionUtil.getPlayerDao().getPlayer(joinerId);
ChallengeMove challengeMove = new ChallengeMove(challenge, joiner,
troops);
pm.makePersistent(challengeMove);
IntegrityCheck ic = IntegrityCheck.start("Join Challenge");
challenge.addChallengeMove(challengeMove);
pm.makePersistent(challenge);
joiner.addActiveChallengeMove(challengeMove);
pm.makePersistent(joiner);
ic.finish();
PM.closePersistenceManager();
Logs.Object.info("newChallengeMove:" + challengeMove);
return challengeMove;
}
public void deployTroops(PlayerId targetId, PlayerId joinerId, Troops
troops) {
PM.closePersistenceManager();
PersistenceManager pm = PM.getPersistenceManager();
Challenge challenge = getChallengeByTarget(targetId);
if (challenge == null) {
newChallenge(targetId, joinerId, troops);
} else {
ChallengeMove challengeMove =
getChallengeMoveByChallengePlayer(challenge.getId(), joinerId);
if (challengeMove == null) {
joinChallenge(challenge.getId(), joinerId, troops);
} else {
Logs.Object.info("Deployed Troops:" +
challengeMove.getId());
challengeMove.setTroopsDeployed(troops);
pm.makePersistent(challengeMove);
PM.closePersistenceManager();
}
}
}
public void resolveChallenges() {
PM.closePersistenceManager();
List<Challenge> challenges = getChallengesToResolve();
LinkedList<ChallengeId> challengeIds = new
LinkedList<ChallengeId>();
for (Challenge challenge : challenges) {
challengeIds.add(challenge.getId());
}
PM.closePersistenceManager();
Challenge challenge = null;
try {
for (ChallengeId challengeId : challengeIds) {
PM.getPersistenceManager();
challenge = getChallenge(challengeId);
challenge.resolve();
PM.closePersistenceManager();
Logs.Service.info("Resolved:" + challenge);
}
} catch (Exception e) {
e.printStackTrace();
Logs.Exception.severe("BadResolve [" + challenge + "]:" + e);
}
}
}
//*********************************************************************************************************************
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class *Player* {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long key;
@Persistent
private Long userKey;
@Persistent
private String userName;
@Persistent
private Long cityKey;
@Persistent
private Long originalFamilyKey;
@Persistent
private Long currentFamilyKey;
//**** Money ****
@Persistent
private int money = 0;
@Persistent
private int totalMoneyReceived = 0;
//***** Store ****
@Persistent
private int storeX = -1;
@Persistent
private int storeY = -1;
@Persistent
private String storeName;
@Persistent
private String storeImage;
@Persistent(mappedBy = "target")
private Set<Convincer> convincers = new HashSet<Convincer>();
//***** Troops/Challenge ****
@Persistent
private int muscle;
@Persistent
private int dames;
@Persistent(mappedBy = "owner")
private Set<TroopHire> activeTroopHires = new HashSet<TroopHire>();
@Persistent
private Long activeChallengeAgainstKey;
@Persistent(defaultFetchGroup = "true")
private Set<Long> activeChallengeMoveKeys;
//**** Stats ****
@Persistent
private int respect = 0;
@Persistent
private int statsChallengesInCount = 0;
@Persistent
private double statsConvinceGained = 0;
@Persistent
private int statsPlayersConvinced = 0;
@Persistent
private int statsStolenTroops = 0;
@Persistent
private int statsLostTroops = 0;
@Persistent
private int statsMoneyEarned = 0;
}
//*********************************************************************************************************************
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class *Convincer* {
@SuppressWarnings("unused")
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private Player target;
@Persistent
private Long targetPlayerKey;
@Persistent
private Long convincerPlayerKey;
@Persistent
private String convincerUserName;
@Persistent
private double convincePoints;
@Persistent
private boolean inFamily;
}
//*********************************************************************************************************************
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class *TroopHire* implements Comparable<TroopHire> {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private Player owner;
@Persistent
private int muscle;
@Persistent
private int dames;
@Persistent
private Date hireTime;
@Persistent
private Date availableTime;
}
//*********************************************************************************************************************
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class *Family* {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long key;
@Persistent
private String name;
@Persistent
private Long creatorPlayerKey;
@Persistent
private Long topPlayerKey;
@Persistent(defaultFetchGroup = "true")
private Set<Long> memberPlayerKeys;
@Persistent
private int memberCount;
@Persistent
private int largestMemberCount;
@Persistent
private Date deathTime;
}
//*********************************************************************************************************************
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class *Challenge* {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
public Long key;
@Persistent
private int locationKey;
@Persistent
private Long targetPlayerKey;
@Persistent
private String targetName;
@Persistent
private Long defendingFamilyKey;
@Persistent
private int defenderCount;
@Persistent
private int attackerCount;
@Persistent(defaultFetchGroup = "true")
public Set<Long> challengeMoveKeys;
@NotPersistent
private Collection<ChallengeMove> challengeMoves;
@Persistent
private Date creationTime;
@Persistent
private Date lockTime;
@Persistent
private Date fightTime;
@Persistent
private Date troopAvailableTime;
@Persistent
private Date resolveTime;
@Persistent
private int badResolveCount = 0;
@NotPersistent
private boolean playerConvinced;
}
//*********************************************************************************************************************
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class *ChallengeMove* implements Comparable<ChallengeMove> {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long key;
@Persistent
private Long challengeKey;
@Persistent
private int locationKey;
@Persistent
private Long targetPlayerKey;
@Persistent
private String targetName;
@Persistent
private Long playerKey;
@Persistent
private String playerName;
@Persistent
private Long familyKey;
@Persistent
private boolean attack;
@Persistent
private int muscle;
@Persistent
private int dames;
@Persistent
private int respect;
@Persistent
private int stolenMuscle;
@Persistent
private int stolenDames;
@Persistent
private double convinceGain;
@Persistent
private boolean convincedTarget;
@Persistent
private int moneyEarned;
@Persistent
private Date joinTime;
@Persistent
private Date resolveTime;
@Persistent
private Date fightTime;
@Persistent
private Date troopAvailableTime;
@Transient
private double ratioBonus;
}
On Mon, Nov 23, 2009 at 11:46 AM, Ikai L (Google) <[email protected]> wrote:
> Can you post a subset of your code? It'd be great for the community to take
> a look at what you are doing and see if there's anything that jumps out at
> us.
>
> On Sat, Nov 21, 2009 at 11:46 PM, Jeffrey Goetsch <[email protected]>wrote:
>
>> I have been having a lot of trouble with JDO objects not storing, or at
>> least not storing some of the data. This has been extremely frustrating,
>> and have thought about giving up on Appengine a few times, because my
>> current design seems bring out all the bugs in the Datastore/Datanucleus
>> code. Are other people also having major issues?
>>
>> Currently, my design starts a Data session at the beginning of the
>> request, and then closes at the end of the request. I figured this should
>> save all the changes that I have made to the Datastore. What I am finding
>> is that some of the objects are being stored and other objects are not. I
>> have managed to get most of the code working by open and closing
>> PersistenceManager multiple times during the processing of a request. This
>> feels very hacky, and introduces other bugs where I have a handle to an
>> object opened in one of the early PersistenceManager, but not in the
>> currently opened one.
>>
>> The project has lots of business logic unit test, which makes sure that
>> all the objects get updated correctly per request. So, when I discover bad
>> data in the datastore, I know it is a problem with the storing of the
>> objects. I have made multiple attempts at simplifying the issue to post to
>> this list, but as the code gets simpler the issues seem to go away. I am
>> willing to share my larger code base with developers that are working on
>> trying to fix these problems, but I don't want make a general post to
>> everyone.
>>
>> Thanks,
>> Jeffrey Goetsch
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "Google App Engine for Java" group.
>> To post to this group, send email to
>> [email protected].
>> To unsubscribe from this group, send email to
>> [email protected]<google-appengine-java%[email protected]>
>> .
>> For more options, visit this group at
>> http://groups.google.com/group/google-appengine-java?hl=.
>>
>
>
>
> --
> Ikai Lan
> Developer Programs Engineer, Google App Engine
>
> --
> You received this message because you are subscribed to the Google Groups
> "Google App Engine for Java" group.
> To post to this group, send email to
> [email protected].
> To unsubscribe from this group, send email to
> [email protected]<google-appengine-java%[email protected]>
> .
> For more options, visit this group at
> http://groups.google.com/group/google-appengine-java?hl=.
>
--
You received this message because you are subscribed to the Google Groups
"Google App Engine for Java" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-appengine-java?hl=en.