/* * Copyright 2005 Ralf Joachim, Gregory Bock, Werner Guttmann * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.castor.persist; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.castor.core.util.IdentityMap; import org.castor.core.util.IdentitySet; import org.castor.core.util.Messages; import org.castor.persist.proxy.LazyCGLIB; import org.exolab.castor.jdo.PersistenceException; import org.exolab.castor.persist.ClassMolder; import org.exolab.castor.persist.LockEngine; import org.exolab.castor.persist.OID; import org.exolab.castor.persist.spi.Identity; /** * A transaction records all objects accessed during the lifetime * of the transaction in this record (queries and created). * * This information, stored on a per-object basis within the * ObjectTracker, covers the database engine used * to persist the object, the object's OID, the object itself, and * whether the object has been deleted in this transaction, * created in this transaction. * * Sidenote: Objects identified as read only are not updated * when the transaction commits. * * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a> * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a> * @author <a href="mailto:gblock AT ctoforaday DOT COM">Gregory Block</a> * @version $Revision$ $Date: 2006-04-29 09:15:50 -0600 (Sat, 29 Apr 2006) $ * @since 0.9.9 */ public final class ObjectTracker { //-------------------------------------------------------------------------- /** Map of Object->ClassMolder. */ private final Map _objectToMolder = new IdentityMap(); /** Map of Engine -> OID -> Object. */ private final Map _engineToOIDToObject = new HashMap(); /** Map of Object->OID. */ private final Map _objectToOID = new IdentityMap(); /** Set of all objects marked 'deleted'. */ private final Map _deletedMap = new IdentityMap(); /** Set of all objects marked 'creating'. */ private final Map _creatingMap = new IdentityMap(); /** Set of all objects marked 'created'. */ private final Set _createdSet = new IdentitySet(); /** Set of all objects for which we need to update persistence. */ private final Set _updatePersistNeededSet = new IdentitySet(); /** Set of all objects for which we need to update cache. */ private final Set _updateCacheNeededSet = new IdentitySet(); /** Set of all objects marked read only in this transaction. */ private final Set _readOnlySet = new IdentitySet(); /** Set of all objects marked read-write in this transaction. */ private final Set _readWriteSet = new IdentitySet(); /** Operation counter of ObjectTracker. */ private long _operation = Long.MIN_VALUE; //-------------------------------------------------------------------------- /** * Retrieve the object for a given OID. * * @param engine Lock engine mapped to oid * @param oid Object id specified * @param allowReadOnly Allow (or ignore, if false) read-only objects to be returned. * @return The object associated with this oid. */ public Object getObjectForOID(final LockEngine engine, final OID oid, final boolean allowReadOnly) { Map oidToObject = (Map) _engineToOIDToObject.get(engine); if (oidToObject != null) { Object found = oidToObject.get(oid); if (!allowReadOnly) { if (_readOnlySet.contains(found)) { return null; } } return found; } // We don't know about this engine in this transaction. // Hence, to us, there is no mapping. Yet. return null; } /** * Returns true if the specified object is tracked as a read-write object. * @param object Object instance for which it should be determined whether * it's tracked as read-write object * @return True if the specified object is tracked as a read-write object */ public boolean isReadWrite(final Object object) { Object aObject = supportCGLibObject(object); return (_readWriteSet.contains(aObject)); } public void unmarkAllDeleted() { _operation++; _deletedMap.clear(); } /** * Reset ObjectTracker's state. */ public void clear() { _operation++; _createdSet.clear(); _creatingMap.clear(); _deletedMap.clear(); _engineToOIDToObject.clear(); _objectToMolder.clear(); _objectToOID.clear(); _readOnlySet.clear(); _readWriteSet.clear(); _updateCacheNeededSet.clear(); _updatePersistNeededSet.clear(); } /** * Returns true if the cache needs to be updated for the given object. * * @param object An object instance * @return true if the cache needs to be updated; false, otherwise. */ public boolean isUpdateCacheNeeded(final Object object) { Object aObject = supportCGLibObject(object); return _updateCacheNeededSet.contains(aObject); } /** * Returns true if the given object needs to be written to the persistence store. * * @param object An object instance * @return true if the object needs to be written to the persistence store */ public boolean isUpdatePersistNeeded(final Object object) { Object aObject = supportCGLibObject(object); return _updatePersistNeededSet.contains(aObject); } public void markUpdateCacheNeeded(final Object object) { _operation++; Object aObject = supportCGLibObject(object); if (!isTracking(aObject)) { return; } _updateCacheNeededSet.add(aObject); } public void unmarkUpdateCacheNeeded(final Object object) { _operation++; Object aObject = supportCGLibObject(object); _updateCacheNeededSet.remove(aObject); } public Collection getObjectsWithUpdateCacheNeededState() { return new ArrayList(_updateCacheNeededSet); } public void markUpdatePersistNeeded(final Object object) { _operation++; Object aObject = supportCGLibObject(object); if (!isTracking(aObject)) { return; } _updatePersistNeededSet.add(aObject); // updatePersistNeeded implies updateCacheNeeded (from comment in persist()) _updateCacheNeededSet.add(aObject); } public void unmarkUpdatePersistNeeded(final Object object) { _operation++; Object aObject = supportCGLibObject(object); _updatePersistNeededSet.remove(aObject); } public void markCreating(final Object object) throws PersistenceException { _operation++; Object aObject = supportCGLibObject(object); if (!isTracking(aObject)) { return; } if (_createdSet.contains(aObject)) { throw new PersistenceException("Invalid state change; can't mark something " + " creating which is already marked created."); } _creatingMap.put(aObject, new Long(_operation)); } public void markCreated(final Object object) { _operation++; Object aObject = supportCGLibObject(object); if (!isTracking(aObject)) { return; } _createdSet.add(aObject); _creatingMap.remove(aObject); } public void markDeleted(final Object object) { _operation++; Object aObject = supportCGLibObject(object); if (!isTracking(aObject)) { return; } _deletedMap.put(aObject, new Long(_operation)); } public void unmarkDeleted(final Object object) { _operation++; Object aObject = supportCGLibObject(object); _deletedMap.remove(aObject); } /** * Determine whether an object is being tracked within this tracking manager. * @param object The object for which it should be determined whether it is tracked. * @return True if the object specified is tracked; false otherwise */ public boolean isTracking(final Object object) { Object aObject = supportCGLibObject(object); return _objectToOID.containsKey(aObject); } /** * Record changes to an OID by re-tracking the OID information. When an object's * OID can change, ensure that the object is retracked. * * @param obj The object to record a tracking change for. * @param engine The engine which is responsible for the old and new OID * @param oldoid The old oid. * @param newoid The new oid. */ public void trackOIDChange(final Object obj, final LockEngine engine, final OID oldoid, final OID newoid) { _operation++; Object object = supportCGLibObject(obj); removeOIDForObject(engine, oldoid); setOIDForObject(object, engine, newoid); } /** * For a given lockengine and OID, set the object in the maps. Note that an OID * can only be accessed via the LockManager which manages it. * @param obj The object to track * @param engine The engine to which the OID belongs * @param oid The OID of the object to track */ public void setOIDForObject(final Object obj, final LockEngine engine, final OID oid) { _operation++; Object object = supportCGLibObject(obj); // Remove any current mapping. removeOIDForObject(engine, oid); // Now add it in. Map oidToObject = (Map) _engineToOIDToObject.get(engine); if (oidToObject == null) { oidToObject = new HashMap(); _engineToOIDToObject.put(engine, oidToObject); } oidToObject.put(oid, obj); // Add the reversal. _objectToOID.put(object, oid); } /** * For a given lockengine and OID, remove references to an object in the maps. * This eliminates both the engine->oid->object and the object->oid. * @param engine The engine to stop tracking the OID for * @param oid The oid of the object to stop tracking on. */ public void removeOIDForObject(final LockEngine engine, final OID oid) { _operation++; Object found = null; Map oidToObject = (Map) _engineToOIDToObject.get(engine); if (oidToObject != null) { found = oidToObject.get(oid); oidToObject.remove(oid); } if (found != null) { _objectToOID.remove(found); } } public boolean isCreating(final Object o) { Object object = supportCGLibObject(o); return _creatingMap.containsKey(object); } public boolean isCreated(final Object o) { Object object = supportCGLibObject(o); return _createdSet.contains(object); } public boolean isDeleted(final Object o) { Object object = supportCGLibObject(o); return _deletedMap.containsKey(object); } /** * Retrieve the ClassMolder associated with a specific object. * @param o Object instance the associated ClassMolder should be retrieved. * @return The ClassMolder instance associated with the Object instance specified. */ public ClassMolder getMolderForObject(final Object o) { Object object = supportCGLibObject(o); return (ClassMolder) _objectToMolder.get(object); } private void setMolderForObject(final Object obj, final ClassMolder molder) { _operation++; Object object = supportCGLibObject(obj); // remove if exists removeMolderForObject(object); // Update objectToMolder (handles both new and pre-existing mappings) _objectToMolder.put(object, molder); } private void removeMolderForObject(final Object obj) { _operation++; Object object = supportCGLibObject(obj); _objectToMolder.remove(object); } /** * Retrieve the list of all read-write objects being tracked. * @return List of all read-write objects being currently tracked. */ public Collection getReadWriteObjects() { ArrayList returnedList = new ArrayList(_readWriteSet); return returnedList; } /** * Retrieve the list of all read-only objects being tracked. * @return List of all read-only objects being currently tracked */ public Collection getReadOnlyObjects() { ArrayList returnedList = new ArrayList(_readOnlySet); return returnedList; } /** * Retrieve the list of 'creating' objects (to be created), sorted in the * order they should be created. * @return List of objects to be created, sorted in the order they should * be created. */ public Collection getObjectsWithCreatingStateSortedByLowestMolderPriority() { ArrayList entryList = new ArrayList(_creatingMap.entrySet()); Collections.sort(entryList, new ObjectMolderPriorityComparator(this, false)); ArrayList returnedList = new ArrayList(entryList.size()); for (int i = 0; i < entryList.size(); i++) { returnedList.add(((Map.Entry) entryList.get(i)).getKey()); } return returnedList; } /** * Retrieve the list of 'deleted' objects, sorted in the order they should be * deleted. * @return List of 'deleted' objects, sorted in the order they should be * deleted. */ public Collection getObjectsWithDeletedStateSortedByHighestMolderPriority() { ArrayList entryList = new ArrayList(_deletedMap.entrySet()); Collections.sort(entryList, new ObjectMolderPriorityComparator(this, true)); ArrayList returnedList = new ArrayList(entryList.size()); for (int i = 0; i < entryList.size(); i++) { returnedList.add(((Map.Entry) entryList.get(i)).getKey()); } return returnedList; } public void trackObject( final ClassMolder molder, final OID oid, final Object object) { _operation++; Object aObject = supportCGLibObject(object); setMolderForObject(aObject, molder); setOIDForObject(aObject, molder.getLockEngine(), oid); _readWriteSet.add(aObject); } public void untrackObject(final Object object) { _operation++; Object aObject = supportCGLibObject(object); // Grab any lockengine/OID information for removal of the nested // engine-oid-object maps. LockEngine engine = getMolderForObject(aObject).getLockEngine(); OID oid = getOIDForObject(aObject); removeMolderForObject(aObject); removeOIDForObject(engine, oid); _deletedMap.remove(aObject); _creatingMap.remove(aObject); _createdSet.remove(aObject); _updatePersistNeededSet.remove(aObject); _updateCacheNeededSet.remove(aObject); _readOnlySet.remove(aObject); _readWriteSet.remove(aObject); } public OID getOIDForObject(final Object o) { Object object = supportCGLibObject(o); return (OID) _objectToOID.get(object); } public boolean isReadOnly(final Object o) { Object object = supportCGLibObject(o); return (_readOnlySet.contains(object)); } public void markReadOnly(final Object o) { _operation++; Object object = supportCGLibObject(o); if (!isTracking(object)) { throw new IllegalStateException(Messages.format("persist.internal", "Attempt to make read-only object that is not in transaction")); } // Add it to our list of read only objects. _readOnlySet.add(object); _readWriteSet.remove(object); } public void unmarkReadOnly(final Object o) { _operation++; Object object = supportCGLibObject(o); if (!isTracking(object)) { throw new IllegalStateException(Messages.format("persist.internal", "Attempt to make read-write object that is not in transaction")); } // Add it to our list of read only objects. _readWriteSet.add(object); _readOnlySet.remove(object); } public int readOnlySize() { return _readOnlySet.size(); } public int readWriteSize() { return _readWriteSet.size(); } /** * "Transform" an object provided as a LazyCGLib into a 'natural' object within * the transaction, undecorated. This allows us to find the true object associated * with a decorated object in the transaction. * * We do this by asking the decorated object for enough information to re-generate * its proper OID; we then go looking for that OID in the system to find the object * in the transaction. * * @param object * @return */ private Object supportCGLibObject(final Object object) { if (object instanceof LazyCGLIB) { LazyCGLIB cgObject = (LazyCGLIB) object; // TODO [WG] We still might have an option for some serious optimization // here if the instance has not been materialized yet. Identity identity = cgObject.interceptedIdentity(); ClassMolder molder = cgObject.interceptedClassMolder(); LockEngine engine = molder.getLockEngine(); // Get the OID we're looking for. OID oid = new OID(molder, identity); // Retrieve the object for this OID; returns null if there ain't such object return getObjectForOID(engine, oid, true); } return object; } public String allObjectStates() { StringBuffer sb = new StringBuffer(); Iterator it = _objectToOID.keySet().iterator(); while (it.hasNext()) { sb.append(objectStateToString(it.next())); sb.append("\n"); } return sb.toString(); } /** * Returns the object's state. * @param obj Object for which its state should be output. * @return The state of the object specified */ public String objectStateToString(final Object obj) { StringBuffer sb = new StringBuffer(); sb.append(getOIDForObject(obj)); sb.append('\t'); sb.append("deleted: "); sb.append(_deletedMap.containsKey(obj)); sb.append('\t'); sb.append("creating: "); sb.append(_creatingMap.containsKey(obj)); sb.append('\t'); sb.append("created: "); sb.append(_createdSet.contains(obj)); return sb.toString(); } //-------------------------------------------------------------------------- private static final class ObjectMolderPriorityComparator implements Comparator { private ObjectTracker _tracker; private boolean _reverseOrder; /** * Creates an instance if this class. * @param tracker The asociated ObjectTracker instance * @param reverseOrder True if reverse order should be applied. */ public ObjectMolderPriorityComparator( final ObjectTracker tracker, final boolean reverseOrder) { _tracker = tracker; _reverseOrder = reverseOrder; } public int compare(final Object object1, final Object object2) { Map.Entry entry1 = (Map.Entry) object1; Map.Entry entry2 = (Map.Entry) object2; long oper1 = ((Long) entry1.getValue()).longValue(); long oper2 = ((Long) entry2.getValue()).longValue(); ClassMolder molder1 = _tracker.getMolderForObject(entry1.getKey()); ClassMolder molder2 = _tracker.getMolderForObject(entry2.getKey()); if (molder1 == null || molder2 == null) { if (oper1 > oper2) { return 1; } else if (oper1 < oper2) { return -1; } else { return 0; } } int pri1 = molder1.getPriority(); int pri2 = molder2.getPriority(); if (pri1 == pri2) { if (oper1 > oper2) { return 1; } if (oper1 < oper2) { return -1; } return 0; } if (_reverseOrder) { return (pri1 < pri2) ? 1 : -1; } return (pri1 < pri2) ? -1 : 1; } } //-------------------------------------------------------------------------- }