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

org.apache.cayenne.access.DataContext 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.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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.log4j.Level;
import org.apache.cayenne.CayenneException;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataObjectUtils;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.DeleteDenyException;
import org.apache.cayenne.Fault;
import org.apache.cayenne.FaultFailureException;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.QueryResponse;
import org.apache.cayenne.access.event.DataContextEvent;
import org.apache.cayenne.access.util.IteratedSelectObserver;
import org.apache.cayenne.conf.Configuration;
import org.apache.cayenne.event.EventManager;
import org.apache.cayenne.event.EventSubject;
import org.apache.cayenne.graph.CompoundDiff;
import org.apache.cayenne.graph.GraphDiff;
import org.apache.cayenne.graph.GraphEvent;
import org.apache.cayenne.graph.GraphManager;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.property.ClassDescriptor;
import org.apache.cayenne.property.CollectionProperty;
import org.apache.cayenne.property.Property;
import org.apache.cayenne.property.PropertyVisitor;
import org.apache.cayenne.property.SingleObjectArcProperty;
import org.apache.cayenne.query.NamedQuery;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.PrefetchTreeNode;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.util.EventUtil;
import org.apache.cayenne.util.GenericResponse;
import org.apache.cayenne.util.Util;

/**
 * Class that provides applications with access to Cayenne persistence features. In most
 * cases this is the only access class directly used in the application.
 * 

* Most common DataContext use pattern is to create one DataContext per session. "Session" * may be a an HttpSession in a web application, or any other similar concept in a * multiuser application. *

*

* DataObjects are registered with DataContext either implicitly when they are fetched via * a query, or read via a relationship from another object, or explicitly via calling * {@link #createAndRegisterNewObject(Class)}during new DataObject creation. DataContext * tracks changes made to its DataObjects in memory, and flushes them to the database when * {@link #commitChanges()}is called. Until DataContext is committed, changes made to its * objects are not visible in other DataContexts. *

*

* Each DataObject can belong only to a single DataContext. To create a replica of an * object from a different DataContext in a local context, use * {@link #localObject(ObjectId, Persistent)} method. *

* For more information see Cayenne User Guide. *

*

* Note that all QueryEngine interface methods are deprecated in the DataContext. Since * 1.2 release DataContext implements ObjectContext and DataChannel interfaces. *

* * @author Andrus Adamchik */ public class DataContext implements ObjectContext, DataChannel, QueryEngine, Serializable { // DataContext events public static final EventSubject WILL_COMMIT = EventSubject.getSubject( DataContext.class, "DataContextWillCommit"); public static final EventSubject DID_COMMIT = EventSubject.getSubject( DataContext.class, "DataContextDidCommit"); public static final EventSubject DID_ROLLBACK = EventSubject.getSubject( DataContext.class, "DataContextDidRollback"); /** * A holder of a DataContext bound to the current thread. * * @since 1.1 */ // TODO: Andrus, 11/7/2005 - should we use InheritableThreadLocal instead? protected static final ThreadLocal threadDataContext = new ThreadLocal(); // event posting default for new DataContexts private static boolean transactionEventsEnabledDefault; // enable/disable event handling for individual instances private boolean transactionEventsEnabled; // Set of DataContextDelegates to be notified. private DataContextDelegate delegate; protected boolean usingSharedSnaphsotCache; protected boolean validatingObjectsOnCommit; protected ObjectStore objectStore; protected transient DataChannel channel; // note that entity resolver is initialized from the parent channel the first time it // is accessed, and later cached in the context protected transient EntityResolver entityResolver; protected transient DataContextMergeHandler mergeHandler; /** * Stores user defined properties associated with this DataContext. * * @since 1.2 */ protected Map userProperties; /** * Stores the name of parent DataDomain. Used to defer initialization of the parent * QueryEngine after deserialization. This helps avoid an issue with certain servlet * engines (e.g. Tomcat) where HttpSessions with DataContext's are deserialized at * startup before Cayenne stack is fully initialized. */ protected transient String lazyInitParentDomainName; /** * Returns the DataContext bound to the current thread. * * @since 1.1 * @return the DataContext associated with caller thread. * @throws IllegalStateException if there is no DataContext bound to the current * thread. * @see org.apache.cayenne.conf.WebApplicationContextFilter */ public static DataContext getThreadDataContext() throws IllegalStateException { DataContext dc = (DataContext) threadDataContext.get(); if (dc == null) { throw new IllegalStateException("Current thread has no bound DataContext."); } return dc; } /** * Binds a DataContext to the current thread. DataContext can later be retrieved by * users in the same thread by calling {@link DataContext#getThreadDataContext}. * Using null parameter will unbind currently bound DataContext. * * @since 1.1 */ public static void bindThreadDataContext(DataContext context) { threadDataContext.set(context); } /** * Factory method that creates and returns a new instance of DataContext based on * default domain. If more than one domain exists in the current configuration, * {@link DataContext#createDataContext(String)} must be used instead. ObjectStore * associated with created DataContext will have a cache stack configured using parent * domain settings. */ public static DataContext createDataContext() { return Configuration.getSharedConfiguration().getDomain().createDataContext(); } /** * Factory method that creates and returns a new instance of DataContext based on * default domain. If more than one domain exists in the current configuration, * {@link DataContext#createDataContext(String, boolean)} must be used instead. * ObjectStore associated with newly created DataContext will have a cache stack * configured according to the specified policy, overriding a parent domain setting. * * @since 1.1 */ public static DataContext createDataContext(boolean useSharedCache) { return Configuration.getSharedConfiguration().getDomain().createDataContext( useSharedCache); } /** * Factory method that creates and returns a new instance of DataContext using named * domain as its parent. If there is no domain matching the name argument, an * exception is thrown. */ public static DataContext createDataContext(String domainName) { DataDomain domain = Configuration.getSharedConfiguration().getDomain(domainName); if (domain == null) { throw new IllegalArgumentException("Non-existent domain: " + domainName); } return domain.createDataContext(); } /** * Creates and returns new DataContext that will use a named DataDomain as its parent. * ObjectStore associated with newly created DataContext will have a cache stack * configured according to the specified policy, overriding a parent domain setting. * * @since 1.1 */ public static DataContext createDataContext(String domainName, boolean useSharedCache) { DataDomain domain = Configuration.getSharedConfiguration().getDomain(domainName); if (domain == null) { throw new IllegalArgumentException("Non-existent domain: " + domainName); } return domain.createDataContext(useSharedCache); } /** * Creates a new DataContext that is not attached to the Cayenne stack. */ public DataContext() { this((DataChannel) null, null); } /** * Creates a DataContext with parent QueryEngine and a DataRowStore that should be * used by the ObjectStore. * * @since 1.1 * @param parent parent QueryEngine used to communicate with the data source. * @param objectStore ObjectStore used by DataContext. * @deprecated since 1.2 - use {@link #DataContext(DataChannel, ObjectStore)} * constructor instead. Note that DataDomain is both a DataChannel and a * QueryEngine, so you may need to do a cast: * new DataContext((DataChannel) domain, objectStore). */ public DataContext(QueryEngine parent, ObjectStore objectStore) { this((DataChannel) parent, objectStore); } /** * Creates a new DataContext with parent DataChannel and ObjectStore. * * @since 1.2 */ public DataContext(DataChannel channel, ObjectStore objectStore) { // use a setter to properly initialize EntityResolver setChannel(channel); this.setTransactionEventsEnabled(transactionEventsEnabledDefault); // inject self as parent context if (objectStore != null) { this.objectStore = objectStore; objectStore.setContext(this); DataDomain domain = getParentDataDomain(); this.usingSharedSnaphsotCache = domain != null && objectStore.getDataRowCache() == domain.getSharedSnapshotCache(); } } /** * Returns a map of user-defined properties associated with this DataContext. * * @since 1.2 */ protected Map getUserProperties() { // as not all users will take advantage of properties, creating the // map on demand to keep DataContext lean... if (userProperties == null) { userProperties = new HashMap(); } return userProperties; } /** * Creates and returns a new child DataContext. * * @since 1.2 */ public DataContext createChildDataContext() { DataContextFactory factory = getParentDataDomain().getDataContextFactory(); // child ObjectStore should not have direct access to snapshot cache, so do not // pass it in constructor. ObjectStore objectStore = new ObjectStore(); DataContext child = factory != null ? factory .createDataContext(this, objectStore) : new DataContext( (DataChannel) this, objectStore); child.setValidatingObjectsOnCommit(isValidatingObjectsOnCommit()); child.usingSharedSnaphsotCache = isUsingSharedSnapshotCache(); return child; } /** * Returns a user-defined property previously set via 'setUserProperty'. Note that it * is a caller responsibility to synchronize access to properties. * * @since 1.2 */ public Object getUserProperty(String key) { return getUserProperties().get(key); } /** * Sets a user-defined property. Note that it is a caller responsibility to * synchronize access to properties. * * @since 1.2 */ public void setUserProperty(String key, Object value) { getUserProperties().put(key, value); } /** * Returns parent QueryEngine object. In most cases returned object is an instance of * DataDomain. * * @deprecated since 1.2. Use 'getParentDataDomain()' or 'getChannel()' instead. */ public QueryEngine getParent() { return getParentDataDomain(); } /** * Sets direct parent of this DataContext. * * @deprecated since 1.2, use setChannel instead. */ public void setParent(QueryEngine parent) { if (parent == null || parent instanceof DataChannel) { setChannel((DataChannel) parent); } else { throw new CayenneRuntimeException( "Only parents that implement DataChannel are supported."); } } /** * Returns parent DataChannel, that is normally a DataDomain or another DataContext. * * @since 1.2 */ public DataChannel getChannel() { awakeFromDeserialization(); return channel; } /** * @since 1.2 */ public void setChannel(DataChannel channel) { if (this.channel != channel) { if (this.mergeHandler != null) { this.mergeHandler.setActive(false); } this.entityResolver = null; this.mergeHandler = null; this.channel = channel; if (channel != null) { // cache entity resolver, as we have no idea how expensive it is to query // it on the channel every time this.entityResolver = channel.getEntityResolver(); EventManager eventManager = channel.getEventManager(); if (eventManager != null) { this.mergeHandler = new DataContextMergeHandler(this); // listen to our channel events... // note that we must reset listener on channel switch, as there is no // guarantee that a new channel uses the same EventManager. EventUtil.listenForChannelEvents(channel, mergeHandler); } } } } /** * Returns a DataDomain used by this DataContext. DataDomain is looked up in the * DataChannel hierarchy. If a channel is not a DataDomain or a DataContext, null is * returned. * * @return DataDomain that is a direct or indirect parent of this DataContext in the * DataChannel hierarchy. * @since 1.1 */ public DataDomain getParentDataDomain() { awakeFromDeserialization(); if (channel == null) { return null; } if (channel instanceof DataDomain) { return (DataDomain) channel; } if (channel instanceof DataContext) { return ((DataContext) channel).getParentDataDomain(); } return null; } /** * Sets a DataContextDelegate for this context. Delegate is notified of certain events * in the DataContext lifecycle and can customize DataContext behavior. * * @since 1.1 */ public void setDelegate(DataContextDelegate delegate) { this.delegate = delegate; } /** * Returns a delegate currently associated with this DataContext. * * @since 1.1 */ public DataContextDelegate getDelegate() { return delegate; } /** * @return a delegate instance if it is initialized, or a shared noop implementation * the context has no delegate. Useful to prevent extra null checks and * conditional logic in the code. * @since 1.1 */ DataContextDelegate nonNullDelegate() { return (delegate != null) ? delegate : NoopDelegate.noopDelegate; } /** * Returns ObjectStore associated with this DataContext. */ public ObjectStore getObjectStore() { return objectStore; } /** * Returns true if there are any modified, deleted or new objects * registered with this DataContext, false otherwise. */ public boolean hasChanges() { return getObjectStore().hasChanges(); } /** * Returns a list of objects that are registered with this DataContext and have a * state PersistenceState.NEW */ public Collection newObjects() { return getObjectStore().objectsInState(PersistenceState.NEW); } /** * Returns a list of objects that are registered with this DataContext and have a * state PersistenceState.DELETED */ public Collection deletedObjects() { return getObjectStore().objectsInState(PersistenceState.DELETED); } /** * Returns a list of objects that are registered with this DataContext and have a * state PersistenceState.MODIFIED */ public Collection modifiedObjects() { return getObjectStore().objectsInState(PersistenceState.MODIFIED); } /** * Returns a collection of all uncommitted registered objects. * * @since 1.2 */ public Collection uncommittedObjects() { int len = getObjectStore().registeredObjectsCount(); if (len == 0) { return Collections.EMPTY_LIST; } // guess target collection size Collection objects = new ArrayList(len > 100 ? len / 2 : len); Iterator it = getObjectStore().getObjectIterator(); while (it.hasNext()) { Persistent object = (Persistent) it.next(); int state = object.getPersistenceState(); if (state == PersistenceState.MODIFIED || state == PersistenceState.NEW || state == PersistenceState.DELETED) { objects.add(object); } } return objects; } /** * Returns an object for a given ObjectId. When an object is not yet registered with * this context's ObjectStore, the behavior of this method depends on whether ObjectId * is permanent or temporary and whether a DataContext is a part of a nested context * hierarchy or not. More specifically the following rules are applied in order: *
    *
  • If a matching registered object is found in this DataContext, it is * immediately returned.
  • *
  • If a context is nested (i.e. it has another DataContext as its parent * channel), an attempt is made to locate a registered object up the hierarchy chain, * until it is found. Such object is transferred to this DataContext and returned to * the caller.
  • *
  • If the ObjectId is temporary, null is returned; if it is permanent, a HOLLOW * object (aka fault) is created and returned.
  • *
* * @deprecated since 1.2 use 'localObject(id, null)' */ public DataObject registeredObject(ObjectId id) { return (DataObject) localObject(id, null); } /** * Returns a DataRow reflecting current, possibly uncommitted, object state. *

* Warning: This method will return a partial snapshot if an object * or one of its related objects that propagate their keys to this object have * temporary ids. DO NOT USE this method if you expect a DataRow to represent a * complete object state. *

* * @since 1.1 */ public DataRow currentSnapshot(DataObject object) { ObjEntity entity = getEntityResolver().lookupObjEntity(object); // for a HOLLOW object return snapshot from cache if (object.getPersistenceState() == PersistenceState.HOLLOW && object.getDataContext() != null) { return getObjectStore().getSnapshot(object.getObjectId()); } DataRow snapshot = new DataRow(10); Iterator attributes = entity.getAttributeMap().entrySet().iterator(); while (attributes.hasNext()) { Map.Entry entry = (Map.Entry) attributes.next(); String attrName = (String) entry.getKey(); ObjAttribute objAttr = (ObjAttribute) entry.getValue(); // processing compound attributes correctly snapshot.put(objAttr.getDbAttributePath(), object .readPropertyDirectly(attrName)); } Iterator relationships = entity.getRelationshipMap().entrySet().iterator(); while (relationships.hasNext()) { Map.Entry entry = (Map.Entry) relationships.next(); ObjRelationship rel = (ObjRelationship) entry.getValue(); // if target doesn't propagates its key value, skip it if (rel.isSourceIndependentFromTargetChange()) { continue; } Object targetObject = object.readPropertyDirectly(rel.getName()); if (targetObject == null) { continue; } // if target is Fault, get id attributes from stored snapshot // to avoid unneeded fault triggering if (targetObject instanceof Fault) { DataRow storedSnapshot = getObjectStore().getSnapshot( object.getObjectId()); if (storedSnapshot == null) { throw new CayenneRuntimeException( "No matching objects found for ObjectId " + object.getObjectId() + ". Object may have been deleted externally."); } DbRelationship dbRel = (DbRelationship) rel.getDbRelationships().get(0); Iterator joins = dbRel.getJoins().iterator(); while (joins.hasNext()) { DbJoin join = (DbJoin) joins.next(); String key = join.getSourceName(); snapshot.put(key, storedSnapshot.get(key)); } continue; } // target is resolved and we have an FK->PK to it, // so extract it from target... DataObject target = (DataObject) targetObject; Map idParts = target.getObjectId().getIdSnapshot(); // this may happen in uncommitted objects - see the warning in the JavaDoc of // this method. if (idParts.isEmpty()) { continue; } DbRelationship dbRel = (DbRelationship) rel.getDbRelationships().get(0); Map fk = dbRel.srcFkSnapshotWithTargetSnapshot(idParts); snapshot.putAll(fk); } // process object id map // we should ignore any object id values if a corresponding attribute // is a part of relationship "toMasterPK", since those values have been // set above when db relationships where processed. Map thisIdParts = object.getObjectId().getIdSnapshot(); if (thisIdParts != null) { // put only those that do not exist in the map Iterator idIterator = thisIdParts.entrySet().iterator(); while (idIterator.hasNext()) { Map.Entry entry = (Map.Entry) idIterator.next(); Object nextKey = entry.getKey(); if (!snapshot.containsKey(nextKey)) { snapshot.put(nextKey, entry.getValue()); } } } return snapshot; } /** * Creates a list of DataObjects local to this DataContext from a list of DataObjects * coming from a different DataContext. This method is a way to map objects * from one context into the other (as opposed to "synchronize"). This means that the * state of modified objects will be reflected only if this context is a child of an * original DataObject context. If it is a peer or parent, you won't see any * uncommitted changes from the original context. *

* Note that the objects in the list do not have to be of the same type or even from * the same DataContext. * * @since 1.0.3 * @deprecated since 1.2 - use {@link #localObject(ObjectId, Persistent)} to specify * how each local object should be handled. */ public List localObjects(List objects) { List localObjects = new ArrayList(objects.size()); Iterator it = objects.iterator(); while (it.hasNext()) { DataObject object = (DataObject) it.next(); if (object == null) { throw new CayenneRuntimeException("Null object"); } localObjects.add(localObject(object.getObjectId(), null)); } return localObjects; } /** * Converts a list of data rows to a list of DataObjects. * * @since 1.1 */ public List objectsFromDataRows( ObjEntity entity, List dataRows, boolean refresh, boolean resolveInheritanceHierarchy) { return new ObjectResolver(this, entity, refresh, resolveInheritanceHierarchy) .synchronizedObjectsFromDataRows(dataRows); } /** * Converts a list of DataRows to a List of DataObject registered with this * DataContext. Internally calls * {@link #objectsFromDataRows(ObjEntity,List,boolean,boolean)}. * * @since 1.1 * @see DataRow * @see DataObject */ public List objectsFromDataRows( Class objectClass, List dataRows, boolean refresh, boolean resolveInheritanceHierarchy) { ObjEntity entity = this.getEntityResolver().lookupObjEntity(objectClass); if (entity == null) { throw new CayenneRuntimeException("Unmapped Java class: " + objectClass); } return objectsFromDataRows(entity, dataRows, refresh, resolveInheritanceHierarchy); } /** * Creates a DataObject from DataRow. This is a convenience shortcut to * {@link #objectsFromDataRows(Class,java.util.List,boolean,boolean)}. * * @see DataRow * @see DataObject */ public DataObject objectFromDataRow( Class objectClass, DataRow dataRow, boolean refresh) { List list = objectsFromDataRows( objectClass, Collections.singletonList(dataRow), refresh, true); return (DataObject) list.get(0); } /** * Instantiates new object and registers it with itself. Object class is determined * from ObjEntity. Object class must have a default constructor. *

* Note: preferred way to create new objects is via * {@link #createAndRegisterNewObject(Class)}method. It works exactly the same way, * but makes the application type-safe. *

* * @see #createAndRegisterNewObject(Class) */ public DataObject createAndRegisterNewObject(String objEntityName) { ClassDescriptor descriptor = getEntityResolver() .getClassDescriptor(objEntityName); if (descriptor == null) { throw new IllegalArgumentException("Invalid entity name: " + objEntityName); } DataObject dataObject; try { dataObject = (DataObject) descriptor.createObject(); } catch (Exception ex) { throw new CayenneRuntimeException("Error instantiating object.", ex); } // this will initialize to-many lists descriptor.injectValueHolders(dataObject); dataObject.setObjectId(new ObjectId(objEntityName)); dataObject.setDataContext(this); dataObject.setPersistenceState(PersistenceState.NEW); getObjectStore().recordObjectCreated(dataObject); return dataObject; } /** * Creates and registers new persistent object. This is an ObjectContext version of * 'createAndRegisterNewObject'. * * @since 1.2 */ public Persistent newObject(Class persistentClass) { if (persistentClass == null) { throw new NullPointerException("Null 'persistentClass'"); } // TODO: only supports DataObject subclasses if (!DataObject.class.isAssignableFrom(persistentClass)) { throw new IllegalArgumentException( this + ": this implementation of ObjectContext only supports full DataObjects. Class " + persistentClass + " is invalid."); } return createAndRegisterNewObject(persistentClass); } /** * Instantiates new object and registers it with itself. Object class must have a * default constructor. * * @since 1.1 */ public DataObject createAndRegisterNewObject(Class objectClass) { if (objectClass == null) { throw new NullPointerException("DataObject class can't be null."); } ObjEntity entity = getEntityResolver().lookupObjEntity(objectClass); if (entity == null) { throw new IllegalArgumentException("Class is not mapped with Cayenne: " + objectClass.getName()); } return createAndRegisterNewObject(entity.getName()); } /** * Registers a transient object with the context, recursively registering all * transient DataObjects attached to this object via relationships. * * @param object new object that needs to be made persistent. */ public void registerNewObject(final DataObject object) { if (object == null) { throw new NullPointerException("Can't register null object."); } ObjEntity entity = getEntityResolver().lookupObjEntity(object); if (entity == null) { throw new IllegalArgumentException( "Can't find ObjEntity for DataObject class: " + object.getClass().getName() + ", class is likely not mapped."); } // sanity check - maybe already registered if (object.getObjectId() != null) { if (object.getDataContext() == this) { // already registered, just ignore return; } else if (object.getDataContext() != null) { throw new IllegalStateException( "DataObject is already registered with another DataContext. " + "Try using 'localObjects()' instead."); } } else { object.setObjectId(new ObjectId(entity.getName())); } object.setDataContext(this); object.setPersistenceState(PersistenceState.NEW); getObjectStore().recordObjectCreated(object); // now we need to find all arc changes, inject missing value holders and pull in // all transient connected objects ClassDescriptor descriptor = getEntityResolver().getClassDescriptor( entity.getName()); if (descriptor == null) { throw new IllegalArgumentException("Invalid entity name: " + entity.getName()); } descriptor.visitProperties(new PropertyVisitor() { public boolean visitCollectionArc(CollectionProperty property) { property.injectValueHolder(object); if (!property.isFault(object)) { Iterator it = ((Collection) property.readProperty(object)).iterator(); while (it.hasNext()) { Object target = it.next(); if (target instanceof DataObject) { DataObject targetDO = (DataObject) target; // make sure it is registered registerNewObject(targetDO); getObjectStore().recordArcCreated( object, targetDO.getObjectId(), property.getName()); } } } return true; } public boolean visitSingleObjectArc(SingleObjectArcProperty property) { Object target = property.readPropertyDirectly(object); if (target instanceof DataObject) { DataObject targetDO = (DataObject) target; // make sure it is registered registerNewObject(targetDO); getObjectStore().recordArcCreated( object, targetDO.getObjectId(), property.getName()); } return true; } public boolean visitProperty(Property property) { return true; } }); } /** * Unregisters a Collection of DataObjects from the DataContext and the underlying * ObjectStore. This operation also unsets DataContext and ObjectId for each object * and changes its state to TRANSIENT. * * @see #invalidateObjects(Collection) */ public void unregisterObjects(Collection dataObjects) { getObjectStore().objectsUnregistered(dataObjects); } /** * "Invalidates" a Collection of DataObject. This operation would remove each object's * snapshot from cache and change object's state to HOLLOW. On the next access to this * object, it will be refetched. * * @see #unregisterObjects(Collection) */ public void invalidateObjects(Collection dataObjects) { getObjectStore().objectsInvalidated(dataObjects); } /** * Schedules all objects in the collection for deletion on the next commit of this * DataContext. Object's persistence state is changed to PersistenceState.DELETED; * objects related to this object are processed according to delete rules, i.e. * relationships can be unset ("nullify" rule), deletion operation is cascaded * (cascade rule). *

* "Nullify" delete rule side effect: passing a collection representing * to-many relationship with nullify delete rule may result in objects being removed * from collection. *

* * @since 1.2 */ public void deleteObjects(Collection objects) { if (objects.isEmpty()) { return; } // clone object list... this maybe a relationship collection with nullify delete // rule, so modifying Iterator it = new ArrayList(objects).iterator(); while (it.hasNext()) { DataObject object = (DataObject) it.next(); deleteObject(object); } } /** * Schedules an object for deletion on the next commit of this DataContext. Object's * persistence state is changed to PersistenceState.DELETED; objects related to this * object are processed according to delete rules, i.e. relationships can be unset * ("nullify" rule), deletion operation is cascaded (cascade rule). * * @param object a persistent object that we want to delete. * @throws DeleteDenyException if a DENY delete rule is applicable for object * deletion. * @throws NullPointerException if object is null. */ public void deleteObject(Persistent object) throws DeleteDenyException { new DataContextDeleteAction(this).performDelete(object); } /** * Refetches object data for ObjectId. This method is used internally by Cayenne to * resolve objects in state PersistenceState.HOLLOW. It can also be * used to refresh certain objects. * * @throws CayenneRuntimeException if object id doesn't match any records, or if there * is more than one object is fetched. */ public DataObject refetchObject(ObjectId oid) { if (oid == null) { throw new NullPointerException("Null ObjectId"); } if (oid.isTemporary()) { throw new CayenneRuntimeException("Can't refetch ObjectId " + oid + ", as it is a temporary id."); } synchronized (getObjectStore()) { DataObject object = (DataObject) objectStore.getNode(oid); // clean up any cached data for this object if (object != null) { this.invalidateObjects(Collections.singleton(object)); } } DataObject object = (DataObject) DataObjectUtils.objectForQuery( this, new ObjectIdQuery(oid)); if (object == null) { throw new CayenneRuntimeException( "Refetch failure: no matching objects found for ObjectId " + oid); } return object; } /** * Returns a DataNode that should handle queries for all DataMap components. * * @since 1.1 * @deprecated since 1.2 DataContext's QueryEngine implementation is replaced by * DataChannel. Use "getParentDataDomain().lookupDataNode(..)". */ public DataNode lookupDataNode(DataMap dataMap) { DataDomain domain = getParentDataDomain(); if (domain == null) { throw new CayenneRuntimeException( "DataContext is not attached to a DataDomain "); } return domain.lookupDataNode(dataMap); } /** * If the parent channel is a DataContext, reverts local changes to make this context * look like the parent, if the parent channel is a DataDomain, reverts all changes. * * @since 1.2 */ // TODO: Andrus, 1/19/2006: implement for nested DataContexts public void rollbackChangesLocally() { if (getChannel() instanceof DataDomain) { rollbackChanges(); } else { throw new CayenneRuntimeException("Implementation pending."); } } /** * Reverts any changes that have occurred to objects registered with DataContext; also * performs cascading rollback of all parent DataContexts. */ public void rollbackChanges() { if (objectStore.hasChanges()) { GraphDiff diff = getObjectStore().getChanges(); getObjectStore().objectsRolledBack(); if (channel != null) { channel.onSync(this, null, DataChannel.ROLLBACK_CASCADE_SYNC); } fireDataChannelRolledback(this, diff); } } /** * "Flushes" the changes to the parent {@link DataChannel}. If the parent channel is * a DataContext, it updates its objects with this context's changes, without a * database update. If it is a DataDomain (the most common case), the changes are * written to the database. To cause cascading commit all the way to the database, one * must use {@link #commitChanges()}. * * @since 1.2 * @see #commitChanges() */ public void commitChangesToParent() { flushToParent(false); } /** * @deprecated Since 1.2, use {@link #commitChanges()} instead. */ public void commitChanges(Level logLevel) throws CayenneRuntimeException { commitChanges(); } /** * Synchronizes object graph with the database. Executes needed insert, update and * delete queries (generated internally). */ public void commitChanges() throws CayenneRuntimeException { flushToParent(true); } /** * Returns EventManager associated with the ObjectStore. * * @since 1.2 */ public EventManager getEventManager() { return channel != null ? channel.getEventManager() : null; } /** * An implementation of a {@link DataChannel} method that is used by child contexts to * synchronize state with this context. Not intended for direct use. * * @since 1.2 */ public GraphDiff onSync( ObjectContext originatingContext, GraphDiff changes, int syncType) { // sync client changes switch (syncType) { case DataChannel.ROLLBACK_CASCADE_SYNC: return onContextRollback(originatingContext); case DataChannel.FLUSH_NOCASCADE_SYNC: return onContextFlush(originatingContext, changes, false); case DataChannel.FLUSH_CASCADE_SYNC: return onContextFlush(originatingContext, changes, true); default: throw new CayenneRuntimeException("Unrecognized SyncMessage type: " + syncType); } } GraphDiff onContextRollback(ObjectContext originatingContext) { rollbackChanges(); return (channel != null) ? channel.onSync( this, null, DataChannel.ROLLBACK_CASCADE_SYNC) : new CompoundDiff(); } GraphDiff onContextFlush( ObjectContext originatingContext, GraphDiff changes, boolean cascade) { if (this != originatingContext && changes != null) { changes.apply(new ChildDiffLoader(this)); fireDataChannelChanged(originatingContext, changes); } return (cascade) ? flushToParent(true) : new CompoundDiff(); } /** * Synchronizes with the parent channel, performing a flush or a commit. * * @since 1.2 */ GraphDiff flushToParent(boolean cascade) { if (this.getChannel() == null) { throw new CayenneRuntimeException( "Cannot commit changes - channel is not set."); } int syncType = cascade ? DataChannel.FLUSH_CASCADE_SYNC : DataChannel.FLUSH_NOCASCADE_SYNC; // prevent multiple commits occuring simulteneously synchronized (getObjectStore()) { DataContextFlushEventHandler eventHandler = null; ObjectStoreGraphDiff changes = getObjectStore().getChanges(); boolean noop = isValidatingObjectsOnCommit() ? changes.validateAndCheckNoop() : changes.isNoop(); if (noop) { // need to clear phantom changes getObjectStore().postprocessAfterPhantomCommit(); return new CompoundDiff(); } if (isTransactionEventsEnabled()) { eventHandler = new DataContextFlushEventHandler(this); eventHandler.registerForDataContextEvents(); fireWillCommit(); } try { GraphDiff returnChanges = getChannel().onSync(this, changes, syncType); getObjectStore().postprocessAfterCommit(returnChanges); // this is a legacy event ... will deprecate in 2.0 fireTransactionCommitted(); // this event is caught by peer nested DataContexts to synchronize the // state fireDataChannelCommitted(this, changes); // this event is caught by child DataContexts to update temporary // ObjectIds with permanent if (!returnChanges.isNoop()) { fireDataChannelCommitted(getChannel(), returnChanges); } return returnChanges; } // "catch" is needed to unwrap OptimisticLockExceptions catch (CayenneRuntimeException ex) { fireTransactionRolledback(); Throwable unwound = Util.unwindException(ex); if (unwound instanceof CayenneRuntimeException) { throw (CayenneRuntimeException) unwound; } else { throw new CayenneRuntimeException("Commit Exception", unwound); } } finally { if (isTransactionEventsEnabled()) { eventHandler.unregisterFromDataContextEvents(); } } } } /** * Performs a single database select query returning result as a ResultIterator. It is * caller's responsibility to explicitly close the ResultIterator. A failure to do so * will result in a database connection not being released. Another side effect of an * open ResultIterator is that an internal Cayenne transaction that originated in this * method stays open until the iterator is closed. So users should normally close the * iterator within the same thread that opened it. */ public ResultIterator performIteratedQuery(Query query) throws CayenneException { if (Transaction.getThreadTransaction() != null) { return internalPerformIteratedQuery(query); } else { // manually manage a transaction, so that a ResultIterator wrapper could close // it when it is done. Transaction tx = getParentDataDomain().createTransaction(); Transaction.bindThreadTransaction(tx); ResultIterator result; try { result = internalPerformIteratedQuery(query); } catch (Exception e) { Transaction.bindThreadTransaction(null); tx.setRollbackOnly(); throw new CayenneException(e); } finally { // note: we are keeping the transaction bound to the current thread on // success - iterator will unbind it. Unsetting a transaction here would // result in some strangeness, at least on Ingres if (tx.getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) { try { tx.rollback(); } catch (Exception rollbackEx) { } } } return new TransactionResultIteratorDecorator(result, tx); } } /** * Runs an iterated query in transactional context provided by the caller. * * @since 1.2 */ ResultIterator internalPerformIteratedQuery(Query query) throws CayenneException { // note that for now DataChannel API does not support cursors (aka // ResultIterator), so we have to go directly to the DataDomain. IteratedSelectObserver observer = new IteratedSelectObserver(); getParentDataDomain().performQueries(Collections.singletonList(query), observer); return observer.getResultIterator(); } /** * Executes a query returning a generic response. * * @since 1.2 */ public QueryResponse performGenericQuery(Query query) { query = nonNullDelegate().willPerformGenericQuery(this, query); if (query == null) { return new GenericResponse(); } if (this.getChannel() == null) { throw new CayenneRuntimeException( "Can't run query - parent DataChannel is not set."); } return onQuery(this, query); } /** * Performs a single selecting query. Various query setting control the behavior of * this method and the results returned: *
    *
  • Query caching policy defines whether the results are retrieved from cache or * fetched from the database. Note that queries that use caching must have a name that * is used as a caching key.
  • *
  • Query refreshing policy controls whether to refresh existing data objects and * ignore any cached values.
  • *
  • Query data rows policy defines whether the result should be returned as * DataObjects or DataRows.
  • *
*

* Since 1.2 takes any Query parameter, not just GenericSelectQuery *

* * @return A list of DataObjects or a DataRows, depending on the value returned by * {@link QueryMetadata#isFetchingDataRows()}. */ public List performQuery(Query query) { query = nonNullDelegate().willPerformQuery(this, query); if (query == null) { return new ArrayList(1); } // this filter should go away when we remove deprecated api from the Delegate. query = filterThroughDelegateDeprecated(query); if (query == null) { return new ArrayList(1); } List result = onQuery(this, query).firstList(); return result != null ? result : new ArrayList(1); } /** * An implementation of a {@link DataChannel} method that is used by child contexts to * execute queries. Not intended for direct use. * * @since 1.2 */ public QueryResponse onQuery(ObjectContext context, Query query) { return new DataContextQueryAction(this, context, query).execute(); } /** * Performs a single database query that does not select rows. Returns an array of * update counts. * * @since 1.1 */ public int[] performNonSelectingQuery(Query query) { int[] count = performGenericQuery(query).firstUpdateCount(); return count != null ? count : new int[0]; } /** * Performs a named mapped query that does not select rows. Returns an array of update * counts. * * @since 1.1 */ public int[] performNonSelectingQuery(String queryName) { return performNonSelectingQuery(new NamedQuery(queryName)); } /** * Performs a named mapped non-selecting query using a map of parameters. Returns an * array of update counts. * * @since 1.1 */ public int[] performNonSelectingQuery(String queryName, Map parameters) { return performNonSelectingQuery(new NamedQuery(queryName, parameters)); } /** * Executes all queries in collection. * * @deprecated since 1.2 DataContext's QueryEngine implementation is replaced by * DataChannel. */ public void performQueries(Collection queries, OperationObserver callback) { // filter through the delegate List finalQueries = new ArrayList(queries.size()); Iterator it = queries.iterator(); while (it.hasNext()) { Query query = (Query) it.next(); query = filterThroughDelegateDeprecated(query); if (query != null) { finalQueries.add(query); } } if (!finalQueries.isEmpty()) { getParentDataDomain().performQueries(queries, callback); } } /** * @since 1.2 * @deprecated since 1.2 */ // deprecated code is extracted in a separate method to avoid Eclipse warnings... Query filterThroughDelegateDeprecated(Query query) { if (query instanceof org.apache.cayenne.query.GenericSelectQuery) { org.apache.cayenne.query.GenericSelectQuery genericSelect = (org.apache.cayenne.query.GenericSelectQuery) query; return nonNullDelegate().willPerformSelect(this, genericSelect); } return query; } /** * Binds provided transaction to the current thread, and then runs queries. * * @since 1.1 * @deprecated since 1.2. Use Transaction.bindThreadTransaction(..) to provide custom * transactions, besides DataContext's QueryEngine implementation is * replaced by DataChannel. */ public void performQueries( Collection queries, OperationObserver callback, Transaction transaction) { Transaction.bindThreadTransaction(transaction); try { performQueries(queries, callback); } finally { Transaction.bindThreadTransaction(null); } } /** * Performs prefetching. Prefetching would resolve a set of relationships for a list * of DataObjects in the most optimized way (preferrably in a single query per * relationship). *

* WARNING: Currently supports only "one-step" to one relationships. This is an * arbitrary limitation and will be removed eventually. *

* * @deprecated Since 1.2. This is a utility method that handles a very specific case. * It shouldn't be in DataContext. */ public void prefetchRelationships(SelectQuery query, List objects) { QueryMetadata metadata = query.getMetaData(getEntityResolver()); Collection prefetches = metadata.getPrefetchTree() != null ? query .getPrefetchTree() .nonPhantomNodes() : Collections.EMPTY_LIST; if (objects == null || objects.size() == 0 || prefetches.size() == 0) { return; } ObjEntity entity = metadata.getObjEntity(); Iterator prefetchesIt = prefetches.iterator(); while (prefetchesIt.hasNext()) { PrefetchTreeNode prefetch = (PrefetchTreeNode) prefetchesIt.next(); String path = prefetch.getPath(); if (path.indexOf('.') >= 0) { throw new CayenneRuntimeException("Only one-step relationships are " + "supported at the moment, this will be fixed soon. " + "Unsupported path : " + path); } ObjRelationship relationship = (ObjRelationship) entity.getRelationship(path); if (relationship == null) { throw new CayenneRuntimeException("Invalid relationship: " + path); } if (relationship.isToMany()) { throw new CayenneRuntimeException( "Only to-one relationships are supported at the moment. " + "Can't prefetch to-many: " + path); } org.apache.cayenne.access.util.PrefetchHelper.resolveToOneRelations( this, objects, path); } } /** * Returns a list of objects or DataRows for a named query stored in one of the * DataMaps. Internally Cayenne uses a caching policy defined in the named query. If * refresh flag is true, a refresh is forced no matter what the caching policy is. * * @param queryName a name of a GenericSelectQuery defined in one of the DataMaps. If * no such query is defined, this method will throw a * CayenneRuntimeException. * @param expireCachedLists A flag that determines whether refresh of cached lists * is required in case a query uses caching. * @since 1.1 */ public List performQuery(String queryName, boolean expireCachedLists) { return performQuery(queryName, Collections.EMPTY_MAP, expireCachedLists); } /** * Returns a list of objects or DataRows for a named query stored in one of the * DataMaps. Internally Cayenne uses a caching policy defined in the named query. If * refresh flag is true, a refresh is forced no matter what the caching policy is. * * @param queryName a name of a GenericSelectQuery defined in one of the DataMaps. If * no such query is defined, this method will throw a * CayenneRuntimeException. * @param parameters A map of parameters to use with stored query. * @param expireCachedLists A flag that determines whether refresh of cached lists * is required in case a query uses caching. * @since 1.1 */ public List performQuery(String queryName, Map parameters, boolean expireCachedLists) { NamedQuery query = new NamedQuery(queryName, parameters); query.setForceNoCache(expireCachedLists); return performQuery(query); } /** * Returns EntityResolver. EntityResolver can be null if DataContext has not been * attached to an DataChannel. */ public EntityResolver getEntityResolver() { awakeFromDeserialization(); return entityResolver; } /** * Sets default for posting transaction events by new DataContexts. */ public static void setTransactionEventsEnabledDefault(boolean flag) { transactionEventsEnabledDefault = flag; } /** * Enables or disables posting of transaction events by this DataContext. */ public void setTransactionEventsEnabled(boolean flag) { this.transactionEventsEnabled = flag; } public boolean isTransactionEventsEnabled() { return this.transactionEventsEnabled; } /** * Returns true if the ObjectStore uses shared cache of a parent * DataDomain. * * @since 1.1 */ public boolean isUsingSharedSnapshotCache() { return usingSharedSnaphsotCache; } /** * Returns whether this DataContext performs object validation before commit is * executed. * * @since 1.1 */ public boolean isValidatingObjectsOnCommit() { return validatingObjectsOnCommit; } /** * Sets the property defining whether this DataContext should perform object * validation before commit is executed. * * @since 1.1 */ public void setValidatingObjectsOnCommit(boolean flag) { this.validatingObjectsOnCommit = flag; } /** * @deprecated since 1.2. Use 'getEntityResolver().getDataMaps()' instead. */ public Collection getDataMaps() { return (getEntityResolver() != null) ? getEntityResolver().getDataMaps() : Collections.EMPTY_LIST; } void fireWillCommit() { // post event: WILL_COMMIT if (this.transactionEventsEnabled) { DataContextEvent commitChangesEvent = new DataContextEvent(this); getEventManager().postEvent(commitChangesEvent, DataContext.WILL_COMMIT); } } void fireTransactionRolledback() { // post event: DID_ROLLBACK if ((this.transactionEventsEnabled)) { DataContextEvent commitChangesEvent = new DataContextEvent(this); getEventManager().postEvent(commitChangesEvent, DataContext.DID_ROLLBACK); } } void fireTransactionCommitted() { // old-style event if ((this.transactionEventsEnabled)) { DataContextEvent commitChangesEvent = new DataContextEvent(this); getEventManager().postEvent(commitChangesEvent, DataContext.DID_COMMIT); } } /** * @since 1.2 */ void fireDataChannelCommitted(Object postedBy, GraphDiff changes) { EventManager manager = getEventManager(); if (manager != null) { GraphEvent e = new GraphEvent(this, postedBy, changes); manager.postEvent(e, DataChannel.GRAPH_FLUSHED_SUBJECT); } } /** * @since 1.2 */ void fireDataChannelRolledback(Object postedBy, GraphDiff changes) { EventManager manager = getEventManager(); if (manager != null) { GraphEvent e = new GraphEvent(this, postedBy, changes); manager.postEvent(e, DataChannel.GRAPH_ROLLEDBACK_SUBJECT); } } /** * @since 1.2 */ void fireDataChannelChanged(Object postedBy, GraphDiff changes) { EventManager manager = getEventManager(); if (manager != null) { GraphEvent e = new GraphEvent(this, postedBy, changes); manager.postEvent(e, DataChannel.GRAPH_CHANGED_SUBJECT); } } // --------------------------------------------- // Serialization Support // --------------------------------------------- private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // If the "parent" of this datacontext is a DataDomain, then just write the // name of it. Then when deserialization happens, we can get back the DataDomain // by name, from the shared configuration (which will either load it if need be, // or return an existing one. if (this.channel == null && this.lazyInitParentDomainName != null) { out.writeObject(lazyInitParentDomainName); } else if (this.channel instanceof DataDomain) { DataDomain domain = (DataDomain) this.channel; out.writeObject(domain.getName()); } else { // Hope that whatever this.parent is, that it is Serializable out.writeObject(this.channel); } // Serialize local snapshots cache if (!isUsingSharedSnapshotCache()) { out.writeObject(objectStore.getDataRowCache()); } } // serialization support private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 1. read non-transient properties in.defaultReadObject(); // 2. read parent or its name Object value = in.readObject(); if (value instanceof DataChannel) { // A real QueryEngine object - use it this.channel = (DataChannel) value; } else if (value instanceof String) { // The name of a DataDomain - use it this.lazyInitParentDomainName = (String) value; } else { throw new CayenneRuntimeException( "Parent attribute of DataContext was neither a QueryEngine nor " + "the name of a valid DataDomain:" + value); } // 3. Deserialize local snapshots cache if (!isUsingSharedSnapshotCache()) { DataRowStore cache = (DataRowStore) in.readObject(); objectStore.setDataRowCache(cache); } // CayenneDataObjects have a transient datacontext // because at deserialize time the datacontext may need to be different // than the one at serialize time (for programmer defined reasons). // So, when a dataobject is resurrected because it's datacontext was // serialized, it will then set the objects datacontext to the correctone // If deserialized "otherwise", it will not have a datacontext (good) synchronized (getObjectStore()) { Iterator it = objectStore.getObjectIterator(); while (it.hasNext()) { DataObject object = (DataObject) it.next(); object.setDataContext(this); } } } // Re-attaches itself to the parent domain with previously stored name. // // TODO: Andrus 11/7/2005 - this is one of the places where Cayenne // serialization relies on shared config... This is bad. We need some // sort of thread-local solution that would allow to use an alternative configuration. // private final void awakeFromDeserialization() { if (channel == null && lazyInitParentDomainName != null) { // call a setter to ensure EntityResolver is extracted from channel setChannel(Configuration.getSharedConfiguration().getDomain( lazyInitParentDomainName)); } } /** * Resolves object fault if needed. If a property is not null, it is assumed that the * object will be modified, so object snapshot is retained and object state is * changed. * * @since 1.2 */ public void prepareForAccess(Persistent object, String property) { if (object.getPersistenceState() == PersistenceState.HOLLOW) { if (!(object instanceof DataObject)) { throw new CayenneRuntimeException("Can only resolve DataObjects. Got: " + object); } getObjectStore().resolveHollow((DataObject) object); if (object.getPersistenceState() != PersistenceState.COMMITTED) { String state = PersistenceState.persistenceStateName(object .getPersistenceState()); // TODO: andrus 4/13/2006, modified and deleted states are possible due to // a race condition, should we handle them here? throw new FaultFailureException( "Error resolving fault for ObjectId: " + object.getObjectId() + " and state (" + state + "). Possible cause - matching row is missing from the database."); } } } /** * Retains DataObject snapshot and changes its state if needed. * * @since 1.2 */ public void propertyChanged( Persistent object, String property, Object oldValue, Object newValue) { if (object.getPersistenceState() == PersistenceState.COMMITTED) { getObjectStore().registerDiff(object, null); } } /** * Returns this context's ObjectStore. * * @since 1.2 */ public GraphManager getGraphManager() { return objectStore; } /** * Returns an object local to this DataContext and matching the ObjectId. If * prototype is not null, local object is refreshed with the prototype * values. *

* In case you pass a non-null second parameter, you are responsible for setting * correct persistence state of the returned local object, as generally there is no * way for Cayenne to determine the resulting local object state. * * @since 1.2 */ public Persistent localObject(ObjectId id, Persistent prototype) { // ****** Warning: when changing the code below, don't forget to change // CayenneContext's implementation which right now relies on copy/paste "reuse" if (id == null) { throw new IllegalArgumentException("Null ObjectId"); } // note that per-object ClassDescriptor lookup is needed as even if all // objects where fetched as a part of the same query, as they may belong to // different subclasses ClassDescriptor descriptor = getEntityResolver().getClassDescriptor( id.getEntityName()); Persistent cachedObject = (Persistent) getGraphManager().getNode(id); // merge into an existing object if (cachedObject != null) { int state = cachedObject.getPersistenceState(); // TODO: Andrus, 1/24/2006 implement smart merge for modified objects... if (cachedObject != prototype && state != PersistenceState.MODIFIED && state != PersistenceState.DELETED) { descriptor.injectValueHolders(cachedObject); if (prototype != null && prototype.getPersistenceState() != PersistenceState.HOLLOW) { descriptor.shallowMerge(prototype, cachedObject); if (state == PersistenceState.HOLLOW) { cachedObject.setPersistenceState(PersistenceState.COMMITTED); } } } return cachedObject; } // create and merge into a new object else { Persistent localObject; synchronized (getGraphManager()) { localObject = (Persistent) descriptor.createObject(); localObject.setObjectContext(this); localObject.setObjectId(id); getGraphManager().registerNode(id, localObject); } if (prototype != null && prototype.getPersistenceState() != PersistenceState.HOLLOW) { localObject.setPersistenceState(PersistenceState.COMMITTED); descriptor.injectValueHolders(localObject); descriptor.shallowMerge(prototype, localObject); } else { localObject.setPersistenceState(PersistenceState.HOLLOW); } return localObject; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy