All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.access.ObjectStore Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.cayenne.access;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.Fault;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.access.ObjectDiff.ArcOperation;
import org.apache.cayenne.access.event.SnapshotEvent;
import org.apache.cayenne.access.event.SnapshotEventListener;
import org.apache.cayenne.graph.CompoundDiff;
import org.apache.cayenne.graph.GraphChangeHandler;
import org.apache.cayenne.graph.GraphDiff;
import org.apache.cayenne.graph.GraphManager;
import org.apache.cayenne.graph.NodeCreateOperation;
import org.apache.cayenne.graph.NodeDeleteOperation;
import org.apache.cayenne.graph.NodeDiff;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.validation.ValidationException;
import org.apache.cayenne.validation.ValidationResult;

/**
 * ObjectStore stores objects using their ObjectId as a key. It works as a dedicated
 * object cache for a DataContext. Users rarely need to access ObjectStore directly, as
 * DataContext serves as a facade, providing cover methods for most ObjectStore
 * operations.
 * 
 * @since 1.0
 * @author Andrus Adamchik
 */
// Synchronization Note: There is often a need to do double synchronize on an ObjectStore
// and an underlying DataRowCache. To avoid deadlocks, Cayenne consistently follows the
// policy of locking an ObjectStore first, and then locking DataRowStore. This pattern
// must be followed in any new related developments.
public class ObjectStore implements Serializable, SnapshotEventListener, GraphManager {

    protected transient Map newObjectsMap;

    protected Map objectMap = new HashMap();
    protected Map queryResultMap = new HashMap();
    protected Map changes = new HashMap();

    // a sequential id used to tag GraphDiffs so that they can later be sorted in the
    // original creation order
    int currentDiffId;

    /**
     * Stores a reference to the DataRowStore.
     * 

* Serialization note: It is up to the owner of this ObjectStore to initialize * DataRowStore after deserialization of this object. ObjectStore will not know how to * restore the DataRowStore by itself. *

*/ protected transient DataRowStore dataRowCache; // used to avoid incorrect on-demand DataRowStore initialization after deserialization private boolean dataRowCacheSet; /** * The DataContext that owns this ObjectStore. */ protected DataContext context; public ObjectStore() { } public ObjectStore(DataRowStore dataRowCache) { setDataRowCache(dataRowCache); } /** * @since 1.2 */ void recordObjectDeleted(Persistent object) { object.setPersistenceState(PersistenceState.DELETED); registerDiff(object, new NodeDeleteOperation(object.getObjectId())); } /** * @since 1.2 */ void recordObjectCreated(Persistent object) { registerDiff(object, new NodeCreateOperation(object.getObjectId())); registerNode(object.getObjectId(), object); } /** * Performs tracking of object relationship changes. * * @since 1.2 */ // TODO: Andrus, 3/14/2006 - this method should be made non-public once we remove // direct ObjectStore access from CayenneDataObject. public void recordArcCreated( Persistent object, ObjectId targetId, String relationshipName) { registerDiff(object, new ArcOperation( object.getObjectId(), targetId, relationshipName, false)); } /** * Performs tracking of object relationship changes. * * @since 1.2 */ // TODO: Andrus, 3/14/2006 - this method should be made non-public once we remove // direct ObjectStore access from CayenneDataObject. public void recordArcDeleted( Persistent object, ObjectId targetId, String relationshipName) { registerDiff(object, new ArcOperation( object.getObjectId(), targetId, relationshipName, true)); } /** * Registers object change. * * @since 1.2 */ synchronized ObjectDiff registerDiff(Persistent object, NodeDiff diff) { ObjectId id = object.getObjectId(); if (object.getPersistenceState() == PersistenceState.COMMITTED) { object.setPersistenceState(PersistenceState.MODIFIED); // TODO: andrus 3/23/2006 snapshot versions are obsolete, but there is no // replacement yet, so we still need to handle them... if (object instanceof DataObject) { DataObject dataObject = (DataObject) object; DataRow snapshot = getCachedSnapshot(id); if (snapshot != null && snapshot.getVersion() != dataObject.getSnapshotVersion()) { DataContextDelegate delegate = dataObject .getDataContext() .nonNullDelegate(); if (delegate.shouldMergeChanges(dataObject, snapshot)) { ObjEntity entity = dataObject .getDataContext() .getEntityResolver() .lookupObjEntity(object); DataRowUtils.forceMergeWithSnapshot(entity, dataObject, snapshot); dataObject.setSnapshotVersion(snapshot.getVersion()); delegate.finishedMergeChanges(dataObject); } } } } if (diff != null) { diff.setDiffId(++currentDiffId); } ObjectDiff objectDiff = (ObjectDiff) changes.get(id); if (objectDiff == null) { objectDiff = new ObjectDiff(this, object); objectDiff.setDiffId(++currentDiffId); changes.put(id, objectDiff); } if (diff != null) { objectDiff.addDiff(diff); } return objectDiff; } /** * Returns a number of objects currently registered with this ObjectStore. * * @since 1.2 */ public int registeredObjectsCount() { return objectMap.size(); } /** * Returns a number of query results cached by this object store. Note that each * result is a list and can possibly contain a large number of entries. * * @since 1.2 */ public int cachedQueriesCount() { return queryResultMap.size(); } /** * Returns a DataRowStore associated with this ObjectStore. */ public DataRowStore getDataRowCache() { // perform deferred initialization... // Andrus, 11/7/2005 - potential problem with on-demand deferred initialization is // that deserialized context won't receive any events... which maybe ok, since it // didn't while it was stored in serialized form. if (dataRowCache == null && context != null && dataRowCacheSet) { synchronized (this) { if (dataRowCache == null) { DataDomain domain = context.getParentDataDomain(); if (domain != null) { setDataRowCache(domain.getSharedSnapshotCache()); } } } } return dataRowCache; } /** * Sets parent DataRowStore. Registers to receive SnapshotEvents if the cache is * configured to allow ObjectStores to receive such events. */ // note that as of 1.2, ObjectStore does not access DataRowStore directly when // retrieving snapshots. Instead it sends a query via the DataContext's channel so // that every element in the channel chain could intercept snapshot requests public void setDataRowCache(DataRowStore dataRowCache) { if (dataRowCache == this.dataRowCache) { return; } if (this.dataRowCache != null && dataRowCache.getEventManager() != null) { dataRowCache.getEventManager().removeListener( this, this.dataRowCache.getSnapshotEventSubject()); } this.dataRowCache = dataRowCache; if (dataRowCache != null && dataRowCache.getEventManager() != null) { // setting itself as non-blocking listener, // since event sending thread will likely be locking sender's // ObjectStore and snapshot cache itself. dataRowCache.getEventManager().addNonBlockingListener( this, "snapshotsChanged", SnapshotEvent.class, dataRowCache.getSnapshotEventSubject(), dataRowCache); } dataRowCacheSet = dataRowCache != null; } /** * Invalidates a collection of DataObjects. Changes objects state to HOLLOW. * * @see #objectsUnregistered(Collection) */ public synchronized void objectsInvalidated(Collection objects) { if (objects.isEmpty()) { return; } Collection ids = new ArrayList(objects.size()); Iterator it = objects.iterator(); while (it.hasNext()) { DataObject object = (DataObject) it.next(); // we don't care about NEW objects, // but we still do care about HOLLOW, since snapshot might still be // present if (object.getPersistenceState() == PersistenceState.NEW) { continue; } object.setPersistenceState(PersistenceState.HOLLOW); // remove cached changes changes.remove(object.getObjectId()); // remember the id ids.add(object.getObjectId()); } // TODO, andrus 3/28/2006 - DRC is null in nested contexts... implement // propagation of invalidate operation through the stack if (getDataRowCache() != null) { // send an event for removed snapshots getDataRowCache().processSnapshotChanges( this, Collections.EMPTY_MAP, Collections.EMPTY_LIST, ids, Collections.EMPTY_LIST); } } /** * Evicts a collection of DataObjects from the ObjectStore, invalidates the underlying * cache snapshots. Changes objects state to TRANSIENT. This method can be used for * manual cleanup of Cayenne cache. * * @see #objectsInvalidated(Collection) */ // this method is exactly the same as "objectsInvalidated", only additionally it // throws out registered objects public synchronized void objectsUnregistered(Collection objects) { if (objects.isEmpty()) { return; } Collection ids = new ArrayList(objects.size()); Iterator it = objects.iterator(); while (it.hasNext()) { DataObject object = (DataObject) it.next(); ObjectId id = object.getObjectId(); // remove object but not snapshot objectMap.remove(id); changes.remove(id); ids.add(id); object.setDataContext(null); object.setObjectId(null); object.setPersistenceState(PersistenceState.TRANSIENT); } // TODO, andrus 3/28/2006 - DRC is null in nested contexts... implement // propagation of unregister operation through the stack ... or do the opposite // and keep unregister local even for non-nested DC? if (getDataRowCache() != null) { // send an event for removed snapshots getDataRowCache().processSnapshotChanges( this, Collections.EMPTY_MAP, Collections.EMPTY_LIST, ids, Collections.EMPTY_LIST); } } /** * Reverts changes to all stored uncomitted objects. * * @since 1.1 */ public synchronized void objectsRolledBack() { Iterator it = getObjectIterator(); // collect candidates while (it.hasNext()) { DataObject object = (DataObject) it.next(); int objectState = object.getPersistenceState(); switch (objectState) { case PersistenceState.NEW: it.remove(); object.setDataContext(null); object.setObjectId(null); object.setPersistenceState(PersistenceState.TRANSIENT); break; case PersistenceState.DELETED: // Do the same as for modified... deleted is only a persistence state, // so // rolling the object back will set the state to committed case PersistenceState.MODIFIED: // this will clean any modifications and defer refresh from snapshot // till the next object accessor is called object.setPersistenceState(PersistenceState.HOLLOW); break; default: // Transient, committed and hollow need no handling break; } } // reset changes ... using new HashMap to allow event listeners to analyze the // original changes map after the rollback this.changes = new HashMap(); } /** * Performs tracking of object relationship changes. * * @since 1.1 * @deprecated since 1.2 use {@link #recordArcDeleted(Persistent, ObjectId, String)}. */ public void objectRelationshipUnset( DataObject source, DataObject target, ObjRelationship relationship, boolean processFlattened) { ObjectId targetId = (target != null) ? target.getObjectId() : null; recordArcDeleted(source, targetId, relationship.getName()); } /** * Performs tracking of object relationship changes. * * @since 1.1 * @deprecated since 1.2 use {@link #recordArcCreated(Persistent, ObjectId, String)}. */ public void objectRelationshipSet( DataObject source, DataObject target, ObjRelationship relationship, boolean processFlattened) { ObjectId targetId = (target != null) ? target.getObjectId() : null; recordArcCreated(source, targetId, relationship.getName()); } /** * Updates snapshots in the underlying DataRowStore. If refresh is * true, all snapshots in snapshots will be loaded into DataRowStore, * regardless of the existing cache state. If refresh is false, only * missing snapshots are loaded. This method is normally called internally by the * DataContext owning the ObjectStore to update the caches after a select query. * * @param objects a list of object whose snapshots need to be updated. * @param snapshots a list of snapshots. Must be of the same length and use the same * order as objects list. * @param refresh controls whether existing cached snapshots should be replaced with * the new ones. * @since 1.1 */ // TODO:, andrus 5/25/2006 - mark as deprecated after 1.2 - this method is no longer // used. public void snapshotsUpdatedForObjects(List objects, List snapshots, boolean refresh) { DataRowStore cache = getDataRowCache(); if (cache != null) { synchronized (this) { cache.snapshotsUpdatedForObjects(objects, snapshots, refresh); } } } /** * Processes internal objects after the parent DataContext was committed. Changes * object persistence state and handles snapshot updates. * * @since 1.1 * @deprecated since 1.2 unused. */ public synchronized void objectsCommitted() { postprocessAfterCommit(new CompoundDiff()); } /** * Builds and returns GraphDiff reflecting all uncommitted object changes. * * @since 1.2 */ ObjectStoreGraphDiff getChanges() { return new ObjectStoreGraphDiff(this); } /** * Returns internal changes map. * * @since 1.2 */ Map getChangesByObjectId() { return changes; } /** * @since 1.2 */ void postprocessAfterPhantomCommit() { Iterator it = changes.keySet().iterator(); while (it.hasNext()) { ObjectId id = (ObjectId) it.next(); Persistent object = (Persistent) objectMap.get(id); // assume that no new or deleted objects are present (as otherwise commit // wouldn't have been phantom). object.setPersistenceState(PersistenceState.COMMITTED); } // clear caches this.changes.clear(); } /** * Internal unsynchronized method to process objects state after commit. * * @since 1.2 */ void postprocessAfterCommit(GraphDiff parentChanges) { Iterator entries = objectMap.entrySet().iterator(); // have to scan through all entries while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); DataObject object = (DataObject) entry.getValue(); switch (object.getPersistenceState()) { case PersistenceState.DELETED: entries.remove(); object.setObjectContext(null); object.setPersistenceState(PersistenceState.TRANSIENT); break; case PersistenceState.NEW: case PersistenceState.MODIFIED: object.setPersistenceState(PersistenceState.COMMITTED); break; } } // re-register changed object ids if (!parentChanges.isNoop()) { parentChanges.apply(new GraphChangeHandler() { public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) { } public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) { } public void nodeCreated(Object nodeId) { } public void nodeIdChanged(Object nodeId, Object newId) { processIdChange(nodeId, newId); } public void nodePropertyChanged( Object nodeId, String property, Object oldValue, Object newValue) { } public void nodeRemoved(Object nodeId) { } }); } // create new instance of changes map so that event listeners who stored the // original diff don't get affected this.changes = new HashMap(); } /** * Adds a new object to the ObjectStore. * * @deprecated since 1.2 as a different change tracking algorithm is used. */ public synchronized void addObject(DataObject object) { recordObjectCreated(object); } /** * Starts tracking the registration of new objects from this ObjectStore. Used in * conjunction with unregisterNewObjects() to control garbage collection when an * instance of ObjectStore is used over a longer time for batch processing. * * @see org.apache.cayenne.access.ObjectStore#unregisterNewObjects() */ public synchronized void startTrackingNewObjects() { newObjectsMap = new HashMap(); } /** * Unregisters the newly registered DataObjects from this objectStore. Used in * conjunction with startTrackingNewObjects() to control garbage collection when an * instance of ObjectStore is used over a longer time for batch processing. * * @see org.apache.cayenne.access.ObjectStore#startTrackingNewObjects() */ public synchronized void unregisterNewObjects() { if (newObjectsMap != null) { objectsUnregistered(newObjectsMap.values()); newObjectsMap = null; } } /** * Returns a DataObject registered for a given ObjectId, or null if no such object * exists. This method does not do a database fetch. * * @deprecated since 1.2 a GraphManager {@link #getNode(Object)} method should be * used. */ public DataObject getObject(ObjectId id) { return (DataObject) getNode(id); } /** * Returns a snapshot for ObjectId from the underlying snapshot cache. If cache * contains no snapshot, a null is returned. * * @since 1.1 */ public DataRow getCachedSnapshot(ObjectId oid) { if (context != null && context.getChannel() != null) { ObjectIdQuery query = new CachedSnapshotQuery(oid); List results = context.getChannel().onQuery(context, query).firstList(); return results.isEmpty() ? null : (DataRow) results.get(0); } else { return null; } } /** * Returns cached query results for a given query, or null if no results are cached. * Note that ObjectStore will only lookup results in its local cache, and not the * shared cache associated with the underlying DataRowStore. * * @since 1.1 */ public synchronized List getCachedQueryResult(String name) { // results should have been stored as rows or objects when // they were originally cached... do no conversions here return (List) queryResultMap.get(name); } /** * Caches a list of query results. * * @since 1.1 */ public synchronized void cacheQueryResult(String name, List results) { queryResultMap.put(name, results); } /** * Returns a snapshot for ObjectId from the underlying snapshot cache. If cache * contains no snapshot, it will attempt fetching it using provided QueryEngine. If * fetch attempt fails or inconsistent data is returned, underlying cache will throw a * CayenneRuntimeException. * * @since 1.1 * @deprecated since 1.2. Use {@link #getSnapshot(ObjectId)} instead. */ public synchronized DataRow getSnapshot(ObjectId oid, QueryEngine engine) { return getDataRowCache().getSnapshot(oid, engine); } /** * Returns a snapshot for ObjectId from the underlying snapshot cache. If cache * contains no snapshot, it will attempt fetching it using provided QueryEngine. If * fetch attempt fails or inconsistent data is returned, underlying cache will throw a * CayenneRuntimeException. * * @since 1.2 */ public synchronized DataRow getSnapshot(ObjectId oid) { if (context != null && context.getChannel() != null) { ObjectIdQuery query = new ObjectIdQuery(oid, true, ObjectIdQuery.CACHE); List results = context.getChannel().onQuery(context, query).firstList(); return results.isEmpty() ? null : (DataRow) results.get(0); } else { return null; } } /** * Returns a list of objects that are registered with this DataContext, regardless of * their persistence state. List is returned by copy and can be modified by the * caller. * * @deprecated since 1.2 use GraphManager method {@link #registeredNodes()}. */ public synchronized List getObjects() { return new ArrayList(objectMap.values()); } /** * Returns an iterator over the registered objects. */ public synchronized Iterator getObjectIterator() { return objectMap.values().iterator(); } /** * Returns true if there are any modified, deleted or new objects * registered with this ObjectStore, false otherwise. This method will * treat "phantom" modifications are real ones. I.e. if you "change" an object * property to an equivalent value, this method will still think such object is * modified. Phantom modifications are only detected and discarded during commit. */ public synchronized boolean hasChanges() { return !changes.isEmpty(); } /** * Return a subset of registered objects that are in a certian persistence state. * Collection is returned by copy. */ public synchronized List objectsInState(int state) { List filteredObjects = new ArrayList(); Iterator it = objectMap.values().iterator(); while (it.hasNext()) { DataObject nextObj = (DataObject) it.next(); if (nextObj.getPersistenceState() == state) filteredObjects.add(nextObj); } return filteredObjects; } /** * SnapshotEventListener implementation that processes snapshot change event, updating * DataObjects that have the changes. *

* Implementation note: This method should not attempt to alter the underlying * DataRowStore, since it is normally invoked *AFTER* the DataRowStore was modified as * a result of some external interaction. *

* * @since 1.1 */ public void snapshotsChanged(SnapshotEvent event) { // filter events that we should not process if (event.getPostedBy() != this && event.getSource() == this.getDataRowCache()) { processSnapshotEvent(event); } } /** * @since 1.2 */ synchronized void processSnapshotEvent(SnapshotEvent event) { Map modifiedDiffs = event.getModifiedDiffs(); if (modifiedDiffs != null && !modifiedDiffs.isEmpty()) { Iterator oids = modifiedDiffs.entrySet().iterator(); while (oids.hasNext()) { Map.Entry entry = (Map.Entry) oids.next(); processUpdatedSnapshot(entry.getKey(), (DataRow) entry.getValue()); } } Collection deletedIDs = event.getDeletedIds(); if (deletedIDs != null && !deletedIDs.isEmpty()) { Iterator it = deletedIDs.iterator(); while (it.hasNext()) { processDeletedID(it.next()); } } processInvalidatedIDs(event.getInvalidatedIds()); processIndirectlyModifiedIDs(event.getIndirectlyModifiedIds()); // TODO: andrus, 3/28/2006 - 'SnapshotEventDecorator' serves as a bridge (or // rather a noop wrapper) between old snapshot events and new GraphEvents. Once // SnapshotEvents are replaced with GraphEvents (in 2.0) we won't need it GraphDiff diff = new SnapshotEventDecorator(event); ObjectContext originatingContext = (event.getPostedBy() instanceof ObjectContext) ? (ObjectContext) event.getPostedBy() : null; context.fireDataChannelChanged(originatingContext, diff); } /** * Performs validation of all uncommitted objects in the ObjectStore. If validation * fails, a ValidationException is thrown, listing all encountered failures. This is a * utility method for the users to call. Cayenne itself uses a different mechanism to * validate objects on commit. * * @since 1.1 * @throws ValidationException * @deprecated since 1.2 - This method is no longer used in Cayenne internally. */ public synchronized void validateUncommittedObjects() throws ValidationException { // we must iterate over a copy of object list, // as calling validateFor* on DataObjects can have a side effect // of modifying this ObjectStore, and thus resulting in // ConcurrentModificationExceptions in the Iterator Collection deleted = null; Collection inserted = null; Collection updated = null; Iterator allIt = getObjectIterator(); while (allIt.hasNext()) { DataObject dataObject = (DataObject) allIt.next(); switch (dataObject.getPersistenceState()) { case PersistenceState.NEW: if (inserted == null) { inserted = new ArrayList(); } inserted.add(dataObject); break; case PersistenceState.MODIFIED: if (updated == null) { updated = new ArrayList(); } updated.add(dataObject); break; case PersistenceState.DELETED: if (deleted == null) { deleted = new ArrayList(); } deleted.add(dataObject); break; } } ValidationResult validationResult = new ValidationResult(); if (deleted != null) { Iterator it = deleted.iterator(); while (it.hasNext()) { DataObject dataObject = (DataObject) it.next(); dataObject.validateForDelete(validationResult); } } if (inserted != null) { Iterator it = inserted.iterator(); while (it.hasNext()) { DataObject dataObject = (DataObject) it.next(); dataObject.validateForInsert(validationResult); } } if (updated != null) { Iterator it = updated.iterator(); while (it.hasNext()) { DataObject dataObject = (DataObject) it.next(); dataObject.validateForUpdate(validationResult); } } if (validationResult.hasFailures()) { throw new ValidationException(validationResult); } } /** * Initializes object with data from cache or from the database, if this object is not * fully resolved. * * @since 1.1 */ public void resolveHollow(DataObject object) { if (object.getPersistenceState() != PersistenceState.HOLLOW) { return; } // no way to resolve faults outside of DataContext. DataContext context = object.getDataContext(); if (context == null) { object.setPersistenceState(PersistenceState.TRANSIENT); return; } synchronized (this) { ObjectIdQuery query = new ObjectIdQuery( object.getObjectId(), false, ObjectIdQuery.CACHE); List results = context.getChannel().onQuery(context, query).firstList(); // handle deleted object if (results.size() == 0) { processDeletedID(object.getObjectId()); } else if (object.getPersistenceState() == PersistenceState.HOLLOW) { // if HOLLOW is returned (from parent DC?), rerun the query with forced // fetch query = new ObjectIdQuery( object.getObjectId(), false, ObjectIdQuery.CACHE_REFRESH); results = context.getChannel().onQuery(context, query).firstList(); if (results.size() == 0) { processDeletedID(object.getObjectId()); } } } } void processIdChange(Object nodeId, Object newId) { Persistent object = (Persistent) objectMap.remove(nodeId); if (object != null) { object.setObjectId((ObjectId) newId); objectMap.put(newId, object); Object change = changes.remove(nodeId); if (change != null) { changes.put(newId, change); } } } /** * Requires external synchronization. * * @since 1.2 */ void processDeletedID(Object nodeId) { // access object map directly - the method should be called in a synchronized // context... DataObject object = (DataObject) objectMap.get(nodeId); if (object != null) { DataContextDelegate delegate; switch (object.getPersistenceState()) { case PersistenceState.COMMITTED: case PersistenceState.HOLLOW: case PersistenceState.DELETED: // consult delegate delegate = context.nonNullDelegate(); if (delegate.shouldProcessDelete(object)) { objectMap.remove(nodeId); changes.remove(nodeId); // setting DataContext to null will also set // state to transient object.setObjectContext(null); delegate.finishedProcessDelete(object); } break; case PersistenceState.MODIFIED: // consult delegate delegate = context.nonNullDelegate(); if (delegate.shouldProcessDelete(object)) { object.setPersistenceState(PersistenceState.NEW); changes.remove(nodeId); recordObjectCreated(object); delegate.finishedProcessDelete(object); } break; } } } /** * @since 1.1 */ void processInvalidatedIDs(Collection invalidatedIDs) { if (invalidatedIDs != null && !invalidatedIDs.isEmpty()) { Iterator it = invalidatedIDs.iterator(); while (it.hasNext()) { ObjectId oid = (ObjectId) it.next(); DataObject object = (DataObject) getNode(oid); if (object == null) { continue; } // TODO: refactor "switch" to avoid code duplication switch (object.getPersistenceState()) { case PersistenceState.COMMITTED: object.setPersistenceState(PersistenceState.HOLLOW); break; case PersistenceState.MODIFIED: DataContext context = object.getDataContext(); DataRow diff = getSnapshot(oid); // consult delegate if it exists DataContextDelegate delegate = context.nonNullDelegate(); if (delegate.shouldMergeChanges(object, diff)) { ObjEntity entity = context .getEntityResolver() .lookupObjEntity(object); DataRowUtils.forceMergeWithSnapshot(entity, object, diff); delegate.finishedMergeChanges(object); } case PersistenceState.HOLLOW: // do nothing break; case PersistenceState.DELETED: // TODO: Do nothing? Or treat as merged? break; } } } } /** * Requires external synchronization. * * @since 1.1 */ void processIndirectlyModifiedIDs(Collection indirectlyModifiedIDs) { Iterator indirectlyModifiedIt = indirectlyModifiedIDs.iterator(); while (indirectlyModifiedIt.hasNext()) { Object oid = indirectlyModifiedIt.next(); // access object map directly - the method should be called in a synchronized // context... DataObject object = (DataObject) objectMap.get(oid); if (object == null || object.getPersistenceState() != PersistenceState.COMMITTED) { continue; } // for now "break" all "independent" object relationships... // in the future we may want to be more precise and go after modified // relationships only, or even process updated lists without invalidating... DataContextDelegate delegate = object.getDataContext().nonNullDelegate(); if (delegate.shouldMergeChanges(object, null)) { ObjEntity entity = context.getEntityResolver().lookupObjEntity(object); Iterator relationshipIterator = entity.getRelationships().iterator(); while (relationshipIterator.hasNext()) { ObjRelationship relationship = (ObjRelationship) relationshipIterator .next(); if (relationship.isSourceIndependentFromTargetChange()) { Object fault = relationship.isToMany() ? Fault.getToManyFault() : Fault.getToOneFault(); object.writePropertyDirectly(relationship.getName(), fault); } } delegate.finishedProcessDelete(object); } } } /** * Requires external synchronization. * * @since 1.1 */ void processUpdatedSnapshot(Object nodeId, DataRow diff) { // access object map directly - the method should be called ina synchronized // context... DataObject object = (DataObject) objectMap.get(nodeId); // no object, or HOLLOW object require no processing if (object != null) { int state = object.getPersistenceState(); if (state != PersistenceState.HOLLOW) { // perform same steps as resolveHollow() if (state == PersistenceState.COMMITTED) { // consult delegate if it exists DataContextDelegate delegate = context.nonNullDelegate(); if (delegate.shouldMergeChanges(object, diff)) { ObjEntity entity = context.getEntityResolver().lookupObjEntity( object); // TODO: andrus, 5/26/2006 - call to 'getSnapshot' is expensive, // however my attempts to merge the 'diff' instead of snapshot // via 'refreshObjectWithSnapshot' resulted in even worse // performance. // This sounds counterintuitive (Not sure if this is some HotSpot // related glitch)... still keeping the old algorithm here until // we // switch from snapshot events to GraphEvents and all this code // becomes obsolete. DataRow snapshot = getSnapshot(object.getObjectId()); DataRowUtils.refreshObjectWithSnapshot( entity, object, snapshot, true); delegate.finishedMergeChanges(object); } } // merge modified and deleted else if (state == PersistenceState.DELETED || state == PersistenceState.MODIFIED) { // consult delegate if it exists DataContextDelegate delegate = context.nonNullDelegate(); if (delegate.shouldMergeChanges(object, diff)) { ObjEntity entity = context.getEntityResolver().lookupObjEntity( object); DataRowUtils.forceMergeWithSnapshot(entity, object, diff); delegate.finishedMergeChanges(object); } } } } } /** * @since 1.2 */ public DataContext getContext() { return context; } /** * @since 1.2 */ public void setContext(DataContext context) { this.context = context; } // *********** GraphManager Methods ******** // ========================================= /** * Returns a registered DataObject or null of no object exists for the ObjectId. * * @since 1.2 */ public synchronized Object getNode(Object nodeId) { return objectMap.get(nodeId); } // non-synchronized version of getNode for private use final Object getNodeNoSync(Object nodeId) { return objectMap.get(nodeId); } /** * Returns all registered DataObjects. List is returned by copy and can be modified by * the caller. * * @since 1.2 */ public synchronized Collection registeredNodes() { return new ArrayList(objectMap.values()); } /** * @since 1.2 */ public void registerNode(Object nodeId, Object nodeObject) { objectMap.put(nodeId, nodeObject); if (newObjectsMap != null) { newObjectsMap.put(nodeId, nodeObject); } } /** * @since 1.2 */ public Object unregisterNode(Object nodeId) { Object object = getNode(nodeId); if (object != null) { objectsUnregistered(Collections.singleton(object)); } return object; } /** * Does nothing. * * @since 1.2 */ public void graphCommitAborted() { } /** * Does nothing. * * @since 1.2 */ public void graphCommitStarted() { } /** * Does nothing. * * @since 1.2 */ public void graphCommitted() { } /** * Does nothing. * * @since 1.2 */ public void graphRolledback() { } /** * Does nothing. * * @since 1.2 */ public void nodeIdChanged(Object nodeId, Object newId) { // noop } /** * Does nothing. * * @since 1.2 */ public void nodeCreated(Object nodeId) { // noop } /** * Does nothing. * * @since 1.2 */ public void nodeRemoved(Object nodeId) { // noop } /** * Does nothing. * * @since 1.2 */ public void nodePropertyChanged( Object nodeId, String property, Object oldValue, Object newValue) { // noop } /** * Does nothing. * * @since 1.2 */ public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) { // noop } /** * Does nothing. * * @since 1.2 */ public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) { // noop } // an ObjectIdQuery optimized for retrieval of multiple snapshots - it can be reset // with the new id final class CachedSnapshotQuery extends ObjectIdQuery { CachedSnapshotQuery(ObjectId oid) { super(oid, true, ObjectIdQuery.CACHE_NOREFRESH); } void resetId(ObjectId oid) { this.objectId = oid; this.replacementQuery = null; } } class SnapshotEventDecorator implements GraphDiff { SnapshotEvent event; SnapshotEventDecorator(SnapshotEvent event) { this.event = event; } SnapshotEvent getEvent() { return event; } public void apply(GraphChangeHandler handler) { throw new UnsupportedOperationException(); } public boolean isNoop() { throw new UnsupportedOperationException(); } public void undo(GraphChangeHandler handler) { throw new UnsupportedOperationException(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy