/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.api.impl; import javax.jdo.ObjectState; import javax.jdo.listener.ClearCallback; import org.zoodb.api.ZooInstanceEvent; import org.zoodb.internal.GenericObject; import org.zoodb.internal.Node; import org.zoodb.internal.Session; import org.zoodb.internal.ZooClassDef; import org.zoodb.internal.client.PCContext; import org.zoodb.internal.util.DBLogger; import org.zoodb.internal.util.DBTracer; import org.zoodb.internal.util.Pair; import org.zoodb.internal.util.Util; import org.zoodb.jdo.spi.PersistenceCapableImpl; import org.zoodb.jdo.spi.StateManagerImpl; /** * This is the common super class of all persistent classes. * It is separate from PersistenceCapabaleImpl to allow easier separation * from JDO. For example, PersistenceCapableImpl implements the * PersistenceCapable interface. * * @author Tilmann Zaeschke */ public abstract class ZooPC { private static final byte PS_PERSISTENT = 1; private static final byte PS_TRANSACTIONAL = 2; private static final byte PS_DIRTY = 4; private static final byte PS_NEW = 8; private static final byte PS_DELETED = 16; private static final byte PS_DETACHED = 32; // public enum STATE { // TRANSIENT, //optional JDO 2.2 // //TRANSIENT_CLEAN, //optional JDO 2.2 // //TRANSIENT_DIRTY, //optional JDO 2.2 // PERSISTENT_NEW, // //PERSISTENT_NON_TRANS, //optional JDO 2.2 // //PERSISTENT_NON_TRANS_DIRTY, //optional JDO 2.2 // PERSISTENT_CLEAN, // PERSISTENT_DIRTY, // HOLLOW, // PERSISTENT_DELETED, // PERSISTENT_NEW_DELETED, // DETACHED_CLEAN, // DETACHED_DIRTY // ; // } //store only byte i.o. reference! //TODO store only one of the following? private transient ObjectState status; private transient byte stateFlags; private transient PCContext context; //All data except Strings is stored in the long[] //Storing the string is only necessary because the the magic number of a string is not //as unique as the string itself. So this is only required for collisions in unique //string indexes. See Issue #55 in Test_091. private transient Pair<long[], Object[]> prevValues = null; private transient long txTimestamp = Session.TIMESTAMP_NOT_ASSIGNED; public final boolean jdoZooIsDirty() { return (stateFlags & PS_DIRTY) != 0; } public final boolean jdoZooIsNew() { return (stateFlags & PS_NEW) != 0; } public final boolean jdoZooIsDeleted() { return (stateFlags & PS_DELETED) != 0; } public final boolean jdoZooIsDetached() { return (stateFlags & PS_DETACHED) != 0; } public final boolean jdoZooIsTransactional() { return (stateFlags & PS_TRANSACTIONAL) != 0; } public final boolean jdoZooIsPersistent() { return (stateFlags & PS_PERSISTENT) != 0; } public final Node jdoZooGetNode() { return context.getNode(); } //not to be used from outside private final void setPersNew() { status = ObjectState.PERSISTENT_NEW; stateFlags = PS_PERSISTENT | PS_TRANSACTIONAL | PS_DIRTY | PS_NEW; context.getSession().internalGetCache().notifyDirty(this); } private final void setPersClean() { status = ObjectState.PERSISTENT_CLEAN; stateFlags = PS_PERSISTENT | PS_TRANSACTIONAL; } private final void setPersDirty() { status = ObjectState.PERSISTENT_DIRTY; stateFlags = PS_PERSISTENT | PS_TRANSACTIONAL | PS_DIRTY; context.getSession().internalGetCache().notifyDirty(this); } private final void setHollow() { status = ObjectState.HOLLOW_PERSISTENT_NONTRANSACTIONAL; stateFlags = PS_PERSISTENT; } private final void setPersDeleted() { status = ObjectState.PERSISTENT_DELETED; stateFlags = PS_PERSISTENT | PS_TRANSACTIONAL | PS_DIRTY | PS_DELETED; context.getSession().internalGetCache().notifyDelete(this); } private final void setPersNewDeleted() { status = ObjectState.PERSISTENT_NEW_DELETED; stateFlags = PS_PERSISTENT | PS_TRANSACTIONAL | PS_DIRTY | PS_NEW | PS_DELETED; context.getSession().internalGetCache().notifyDelete(this); } private final void setDetachedClean() { status = ObjectState.DETACHED_CLEAN; stateFlags = PS_DETACHED; } private final void setDetachedDirty() { status = ObjectState.DETACHED_DIRTY; stateFlags = PS_DETACHED | PS_DIRTY; } private final void setTransient() { status = ObjectState.TRANSIENT; //TODO other transient states? stateFlags = 0; jdoZooOid = Session.OID_NOT_ASSIGNED; } public final void jdoZooMarkClean() { //TODO is that all? setPersClean(); prevValues = null; } // public final void jdoZooMarkNew() { // ObjectState statusO = status; // if (statusO == ObjectState.TRANSIENT) { // setPersNew(); // } else if (statusO == ObjectState.PERSISTENT_NEW) { // //ignore // } else { // throw new IllegalStateException("Illegal state transition: " + status // + " -> Persistent New: " + Util.oidToString(jdoZooOid)); // } // } public final void jdoZooMarkDirty() { jdoZooGetContext().getSession().internalGetCache().flagOGTraversalRequired(); switch (status) { case DETACHED_DIRTY: //is already dirty return; case DETACHED_CLEAN: context.notifyEvent(this, ZooInstanceEvent.PRE_DIRTY); setDetachedDirty(); getPrevValues(); break; case PERSISTENT_NEW: case PERSISTENT_DIRTY: //is already dirty //status = ObjectState.PERSISTENT_DIRTY; return; case PERSISTENT_CLEAN: context.notifyEvent(this, ZooInstanceEvent.PRE_DIRTY); setPersDirty(); getPrevValues(); break; case HOLLOW_PERSISTENT_NONTRANSACTIONAL: context.notifyEvent(this, ZooInstanceEvent.PRE_DIRTY); //refresh first, then make dirty if (getClass() == GenericObject.class) { ((GenericObject)this).activateRead(); } else { zooActivateRead(); } jdoZooMarkDirty(); break; default: throw new IllegalStateException("Illegal state transition: " + status + "->Dirty: " + Util.oidToString(jdoZooOid)); } context.notifyEvent(this, ZooInstanceEvent.POST_DIRTY); } public final void jdoZooMarkDeleted() { switch (status) { case PERSISTENT_CLEAN: case PERSISTENT_DIRTY: setPersDeleted(); break; case PERSISTENT_NEW: setPersNewDeleted(); break; case HOLLOW_PERSISTENT_NONTRANSACTIONAL: //make a backup! This is a bit of a hack: //When deleting an object, we have to remove it from the attr-indexes. //However, during removal, the object is marked as PERSISTENT_DELETED, //independent of whether it was previously hollow or not. //If it is hollow, we have to refresh() it, otherwise not. //So we do the refresh here, where we still know whether it was hollow. if (context.getIndexer().isIndexed()) { //refresh + createBackup context.getNode().refreshObject(this); } setPersDeleted(); break; case PERSISTENT_DELETED: case PERSISTENT_NEW_DELETED: throw DBLogger.newUser("The object has already been deleted: " + Util.oidToString(jdoZooOid)); default: throw new IllegalStateException("Illegal state transition(" + Util.oidToString(jdoZooGetOid()) + "): " + status + "->Deleted"); } } public final void jdoZooMarkDetached() { switch (status) { case DETACHED_CLEAN: case DETACHED_DIRTY: throw new IllegalStateException("Object is already detached"); default: setDetachedClean(); } } public final void jdoZooMarkHollow() { //TODO is that all? setHollow(); prevValues = null; } public final void jdoZooMarkTransient() { switch (status) { case TRANSIENT: //nothing to do break; case PERSISTENT_CLEAN: case HOLLOW_PERSISTENT_NONTRANSACTIONAL: setTransient(); break; case PERSISTENT_NEW : setTransient(); break; case PERSISTENT_DIRTY: throw DBLogger.newUser("The object is dirty."); case PERSISTENT_DELETED: throw DBLogger.newUser("The object has already been deleted: " + Util.oidToString(jdoZooOid)); case PERSISTENT_NEW_DELETED : setTransient(); break; default: throw new IllegalStateException("Illegal state transition: " + status + "->Deleted"); } } public final boolean jdoZooIsStateHollow() { return status == ObjectState.HOLLOW_PERSISTENT_NONTRANSACTIONAL; } public final PCContext jdoZooGetContext() { return context; } public final ZooClassDef jdoZooGetClassDef() { return context.getClassDef(); } public final void jdoZooEvict() { if (this instanceof ClearCallback) { ((ClearCallback)this).jdoPreClear(); } context.notifyEvent(this, ZooInstanceEvent.PRE_CLEAR); context.getEvictor().evict(this); jdoZooMarkHollow(); context.notifyEvent(this, ZooInstanceEvent.POST_CLEAR); } public final boolean jdoZooHasState(ObjectState state) { return this.status == state; } public final void jdoZooInit(ObjectState state, PCContext bundle, long oid) { this.context = bundle; jdoZooSetOid(oid); this.status = state; switch (state) { case PERSISTENT_NEW: { setPersNew(); jdoZooSetTimestamp(bundle.getSession().getTransactionId()); jdoZooGetContext().notifyEvent(this, ZooInstanceEvent.CREATE); break; } case PERSISTENT_CLEAN: { setPersClean(); break; } case HOLLOW_PERSISTENT_NONTRANSACTIONAL: { setHollow(); break; } case PERSISTENT_DIRTY: { //This should only be called from on-demand evolution in GenericObjects if (!(this instanceof GenericObject)) { throw new UnsupportedOperationException("" + state); } setPersDirty(); break; } default: throw new UnsupportedOperationException("" + state); } if (this instanceof PersistenceCapableImpl) { ((PersistenceCapableImpl)this).jdoNewInstance(StateManagerImpl.STATEMANAGER); } } private final void getPrevValues() { if (prevValues != null) { throw new IllegalStateException(); } prevValues = context.getIndexer().getBackup(this); } public Pair<long[], Object[]> jdoZooGetBackup() { return prevValues; } //Specific to ZooDB /** * This method ensures that the specified object is in the cache. * * It should be called in the beginning of every method that reads persistent fields. * * For generated calls, we should not forget private method, because they can be called * from other instances. */ public final void zooActivateRead() { if (DBTracer.TRACE) DBTracer.logCall(this); switch (status) { case DETACHED_CLEAN: //nothing to do return; case HOLLOW_PERSISTENT_NONTRANSACTIONAL: try { Session session = context.getSession(); session.lock(); if (session.isClosed()) { throw DBLogger.newUser("The PersistenceManager of this object is not open."); } if (!session.isActive() && !session.getConfig().getNonTransactionalRead()) { throw DBLogger.newUser("The PersistenceManager of this object is not active " + "(-> use begin())."); } jdoZooGetNode().refreshObject(this); } finally { context.getSession().unlock(); } return; case PERSISTENT_DELETED: case PERSISTENT_NEW_DELETED: throw DBLogger.newUser("The object has been deleted."); case PERSISTENT_NEW: case PERSISTENT_CLEAN: case PERSISTENT_DIRTY: //nothing to do return; case TRANSIENT: case TRANSIENT_CLEAN: case TRANSIENT_DIRTY: //not persistent yet return; default: throw new IllegalStateException("" + status); } } /** * This method ensures that the specified object is in the cache and then flags it as dirty. * It includes a call to zooActivateRead(). * * It should be called in the beginning of every method that writes persistent fields. * * For generated calls, we should not forget private method, because they can be called * from other instances. */ public final void zooActivateWrite() { if (DBTracer.TRACE) DBTracer.logCall(this); switch (status) { case HOLLOW_PERSISTENT_NONTRANSACTIONAL: try { context.getSession().lock(); checkActiveForWrite(); jdoZooGetNode().refreshObject(this); jdoZooMarkDirty(); return; } finally { context.getSession().unlock(); } case PERSISTENT_DELETED: case PERSISTENT_NEW_DELETED: throw DBLogger.newUser("The object has been deleted."); case TRANSIENT: case TRANSIENT_CLEAN: case TRANSIENT_DIRTY: //not persistent yet return; case PERSISTENT_DIRTY: case PERSISTENT_NEW: case DETACHED_DIRTY: //nothing to do return; case PERSISTENT_CLEAN: try { context.getSession().lock(); checkActiveForWrite(); jdoZooMarkDirty(); return; } finally { context.getSession().unlock(); } case DETACHED_CLEAN: try { context.getSession().lock(); jdoZooMarkDirty(); return; } finally { context.getSession().unlock(); } default: } throw new UnsupportedOperationException(status.toString()); } private void checkActiveForWrite() { if (jdoZooGetContext().getSession().isClosed()) { throw DBLogger.newUser("The PersitenceManager of this object is not open."); } if (!jdoZooGetContext().getSession().isActive()) { throw DBLogger.newUser("The PersitenceManager of this object is not active " + "(-> use begin())."); } } public final void zooActivateWrite(String field) { //Here we cannot skip loading the field to be loaded, because it may be read beforehand zooActivateWrite(); } // private long jdoZooFlags = 0; //TODO instead use some fixed value like INVALID_OID private transient long jdoZooOid = Session.OID_NOT_ASSIGNED; // void jdoZooSetFlag(long flag) { // jdoZooFlags |= flag; // } // // void jdoZooUnsetFlag(long flag) { // jdoZooFlags &= ~flag; // } // // boolean jdoZooFlagIsSet(long flag) { // return (jdoZooFlags & flag) > 0; // } // public void jdoZooSetDirty() { jdoZooSetFlag(StateManagerImpl.JDO_PC_DIRTY); } // public void jdoZooSetNew() { jdoZooSetFlag(StateManagerImpl.JDO_PC_NEW); } // public void jdoZooSetDeleted() { jdoZooSetFlag(StateManagerImpl.JDO_PC_DELETED); } // public void jdoZooSetPersistent() { jdoZooSetFlag(StateManagerImpl.JDO_PC_PERSISTENT); } // public void jdoZooSetDirtyNewFalse() { // jdoZooUnsetFlag(StateManagerImpl.JDO_PC_DIRTY | StateManagerImpl.JDO_PC_NEW); // } public final void jdoZooSetOid(long oid) { jdoZooOid = oid;} public final long jdoZooGetOid() { return jdoZooOid; } //TODO public ZooPC() { super(); setTransient(); //jdoStateManager = StateManagerImpl.SINGLE; } @Override public String toString() { return super.toString() + " oid=" + Util.oidToString(jdoZooOid) + " state=" + status; } public void jdoZooSetTimestamp(long ts) { txTimestamp = ts; } public long jdoZooGetTimestamp() { return txTimestamp; } } // end class definition