This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch STABLE-4.2
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/STABLE-4.2 by this push:
new 1f7f2a672 CAY-2876 Memory leak in the ObjectStore
1f7f2a672 is described below
commit 1f7f2a672e17efd28bd9fb1cf11db75ca41aa45f
Author: Nikita Timofeev <[email protected]>
AuthorDate: Wed Feb 26 16:54:52 2025 +0400
CAY-2876 Memory leak in the ObjectStore
---
RELEASE-NOTES.txt | 1 +
.../ObjectIdRelationshipHandlerTest.java | 8 +-
.../java/org/apache/cayenne/access/ObjectDiff.java | 42 +++---
.../org/apache/cayenne/access/ObjectStore.java | 154 +++++++++++++--------
.../access/ObjectStorePersistentWrapper.java | 105 ++++++++++++++
5 files changed, 228 insertions(+), 82 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 536ed09fe..fcfdd19b7 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -22,6 +22,7 @@ Bug Fixes:
CAY-2866 DefaultDataDomainFlushAction breaks on circular relationship update
CAY-2868 Regression: DefaultDbRowOpSorter shouldn't sort update operations
CAY-2871 QualifierTranslator breaks on a relationship with a compound FK
+CAY-2876 Memory leak in the ObjectStore
CAY-2879 Negative number for non parameterized ObjectSelect query not
processed correctly
----------------------------------
diff --git
a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/relationship/ObjectIdRelationshipHandlerTest.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/relationship/ObjectIdRelationshipHandlerTest.java
index 12cc2fede..e1c50d32e 100644
---
a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/relationship/ObjectIdRelationshipHandlerTest.java
+++
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/relationship/ObjectIdRelationshipHandlerTest.java
@@ -20,6 +20,8 @@ package org.apache.cayenne.lifecycle.relationship;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.server.ServerModule;
import org.apache.cayenne.configuration.server.ServerRuntime;
import org.apache.cayenne.lifecycle.db.E1;
import org.apache.cayenne.lifecycle.db.UuidRoot1;
@@ -44,7 +46,11 @@ public class ObjectIdRelationshipHandlerTest {
@Before
public void setUp() throws Exception {
- runtime =
ServerRuntime.builder().addConfig("cayenne-lifecycle.xml").build();
+ runtime = ServerRuntime.builder()
+ // Using soft, as weak could lead to unloading data before
test completes
+ .addModule(b -> ServerModule.contributeProperties(b)
+ .put(Constants.SERVER_OBJECT_RETAIN_STRATEGY_PROPERTY,
"soft"))
+ .addConfig("cayenne-lifecycle.xml").build();
// a filter is required to invalidate root objects after commit
ObjectIdRelationshipFilter filter = new ObjectIdRelationshipFilter();
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
index 9b2b68de4..c62f95a3d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
@@ -65,26 +65,24 @@ public class ObjectDiff extends NodeDiff {
private Map<ArcOperation, ArcOperation> flatIds;
private Map<ArcOperation, ArcOperation> phantomFks;
- private Persistent object;
+ private final ObjectStorePersistentWrapper object;
- ObjectDiff(final Persistent object) {
+ ObjectDiff(final ObjectStorePersistentWrapper object) {
- super(object.getObjectId());
+ super(object.dataObject().getObjectId());
- // retain the object, as ObjectStore may have weak references to
- // registered
- // objects and we can't allow it to deallocate dirty objects.
+ // retain the object, as ObjectStore may have weak references to
registered objects,
+ // and we can't allow it to deallocate dirty objects.
this.object = object;
- EntityResolver entityResolver =
object.getObjectContext().getEntityResolver();
+ EntityResolver entityResolver =
object.dataObject().getObjectContext().getEntityResolver();
- this.entityName = object.getObjectId().getEntityName();
+ this.entityName = object.dataObject().getObjectId().getEntityName();
this.classDescriptor = entityResolver.getClassDescriptor(entityName);
- int state = object.getPersistenceState();
+ int state = object.dataObject().getPersistenceState();
- // take snapshot of simple properties and arcs used for optimistic
- // locking..
+ // take snapshot of simple properties and arcs used for optimistic
locking..
if (state == PersistenceState.COMMITTED || state ==
PersistenceState.DELETED
|| state == PersistenceState.MODIFIED) {
@@ -99,7 +97,7 @@ public class ObjectDiff extends NodeDiff {
@Override
public boolean visitAttribute(AttributeProperty property) {
- snapshot.put(property.getName(),
property.readProperty(object));
+ snapshot.put(property.getName(),
property.readProperty(object.dataObject()));
return true;
}
@@ -114,8 +112,8 @@ public class ObjectDiff extends NodeDiff {
// eagerly resolve optimistically locked relationships
Object target = (lock && isUsedForLocking)
- ? property.readProperty(object)
- : property.readPropertyDirectly(object);
+ ? property.readProperty(object.dataObject())
+ :
property.readPropertyDirectly(object.dataObject());
if (target instanceof Persistent) {
target = ((Persistent) target).getObjectId();
@@ -130,14 +128,14 @@ public class ObjectDiff extends NodeDiff {
}
Object getObject() {
- return object;
+ return object.dataObject();
}
ClassDescriptor getClassDescriptor() {
// class descriptor is initiated in constructor, but is nullified on
// serialization
if (classDescriptor == null) {
- EntityResolver entityResolver =
object.getObjectContext().getEntityResolver();
+ EntityResolver entityResolver =
object.dataObject().getObjectContext().getEntityResolver();
this.classDescriptor =
entityResolver.getClassDescriptor(entityName);
}
@@ -152,7 +150,7 @@ public class ObjectDiff extends NodeDiff {
Object value = arcSnapshot != null ? arcSnapshot.get(propertyName) :
null;
if (value instanceof Fault) {
- Persistent target = (Persistent) ((Fault)
value).resolveFault(object, propertyName);
+ Persistent target = (Persistent) ((Fault)
value).resolveFault(object.dataObject(), propertyName);
value = target != null ? target.getObjectId() : null;
arcSnapshot.put(propertyName, value);
@@ -167,7 +165,7 @@ public class ObjectDiff extends NodeDiff {
public ObjectId getCurrentArcSnapshotValue(String propertyName) {
Object value = currentArcSnapshot != null ?
currentArcSnapshot.get(propertyName) : null;
if (value instanceof Fault) {
- Persistent target = (Persistent) ((Fault)
value).resolveFault(object, propertyName);
+ Persistent target = (Persistent) ((Fault)
value).resolveFault(object.dataObject(), propertyName);
value = target != null ? target.getObjectId() : null;
currentArcSnapshot.put(propertyName, value);
@@ -328,7 +326,7 @@ public class ObjectDiff extends NodeDiff {
return false;
}
- int state = object.getPersistenceState();
+ int state = object.dataObject().getPersistenceState();
if (state == PersistenceState.NEW || state ==
PersistenceState.DELETED) {
return false;
}
@@ -342,7 +340,7 @@ public class ObjectDiff extends NodeDiff {
public boolean visitAttribute(AttributeProperty property) {
Object oldValue = snapshot.get(property.getName());
- Object newValue = property.readProperty(object);
+ Object newValue = property.readProperty(object.dataObject());
if (!property.equals(oldValue, newValue)) {
modFound[0] = true;
@@ -363,7 +361,7 @@ public class ObjectDiff extends NodeDiff {
return true;
}
- Object newValue = property.readPropertyDirectly(object);
+ Object newValue =
property.readPropertyDirectly(object.dataObject());
if (newValue instanceof Fault) {
return true;
}
@@ -411,7 +409,7 @@ public class ObjectDiff extends NodeDiff {
@Override
public boolean visitAttribute(AttributeProperty property) {
- Object newValue = property.readProperty(object);
+ Object newValue = property.readProperty(object.dataObject());
// no baseline to compare
if (snapshot == null) {
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStore.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStore.java
index 74b778c79..aa522f017 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStore.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStore.java
@@ -53,8 +53,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* ObjectStore stores objects using their ObjectId as a key. It works as a
dedicated
@@ -66,6 +65,9 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class ObjectStore implements Serializable, SnapshotEventListener,
GraphManager {
+ /**
+ * Actual content is ObjectId -> ObjectStorePersistentWrapper
+ */
protected Map<Object, Persistent> objectMap;
protected Map<Object, ObjectDiff> changes;
@@ -73,6 +75,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* Map that tracks flattened paths for given object Id that is present in
db.
* Presence of path in this map is used to separate insert from update
case of flattened records.
* @since 4.1
+ * @deprecated since 4.2.2 it is unused
*/
protected Map<Object, Map<String, ObjectId>> trackedFlattenedPaths;
@@ -138,7 +141,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
Collection<GraphDiff> getLifecycleEventInducedChanges() {
return lifecycleEventInducedChanges != null
? lifecycleEventInducedChanges
- : Collections.<GraphDiff>emptyList();
+ : Collections.emptyList();
}
void registerLifecycleEventInducedChange(GraphDiff diff) {
@@ -166,10 +169,11 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
if (objectDiff == null) {
- Persistent object = objectMap.get(nodeId);
- if (object == null) {
+ ObjectStorePersistentWrapper persistentWrapper =
(ObjectStorePersistentWrapper)objectMap.get(nodeId);
+ if (persistentWrapper == null) {
throw new CayenneRuntimeException("No object is registered in
context with Id %s", nodeId);
}
+ Persistent object = persistentWrapper.dataObject();
if (object.getPersistenceState() == PersistenceState.COMMITTED) {
object.setPersistenceState(PersistenceState.MODIFIED);
@@ -201,7 +205,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
}
}
- objectDiff = new ObjectDiff(object);
+ objectDiff = new ObjectDiff(persistentWrapper);
objectDiff.setDiffId(++currentDiffId);
changes.put(nodeId, objectDiff);
}
@@ -213,13 +217,28 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
return objectDiff;
}
+ private Persistent getUnwrapped(Object nodeId) {
+ Persistent persistent = objectMap.get(nodeId);
+ if(persistent == null) {
+ return null;
+ }
+ return ((ObjectStorePersistentWrapper) persistent).dataObject();
+ }
+
/**
* Returns a number of objects currently registered with this ObjectStore.
*
* @since 1.2
*/
public int registeredObjectsCount() {
- return objectMap.size();
+ AtomicInteger counter = new AtomicInteger();
+ objectMap.forEach((id, obj) -> {
+ ObjectStorePersistentWrapper wrapper =
(ObjectStorePersistentWrapper) obj;
+ if(wrapper.hasObject()){
+ counter.incrementAndGet();
+ }
+ });
+ return counter.get();
}
/**
@@ -303,9 +322,6 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
// remove object but not snapshot
objectMap.remove(id);
changes.remove(id);
- if(id != null && trackedFlattenedPaths != null) {
- trackedFlattenedPaths.remove(id);
- }
ids.add(id);
object.setObjectContext(null);
@@ -391,7 +407,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
for (Object id : changes.keySet()) {
- Persistent object = objectMap.get(id);
+ Persistent object = getUnwrapped(id);
// assume that no new or deleted objects are present (as otherwise
commit
// wouldn't have been phantom).
@@ -411,7 +427,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
// scan through changed objects, set persistence state to committed
for (Object id : changes.keySet()) {
- Persistent object = objectMap.get(id);
+ Persistent object = getUnwrapped(id);
switch (object.getPersistenceState()) {
case PersistenceState.DELETED:
@@ -508,7 +524,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* Returns an iterator over the registered objects.
*/
public synchronized Iterator<Persistent> getObjectIterator() {
- return objectMap.values().iterator();
+ return new WrapperIterator(objectMap.values().iterator());
}
/**
@@ -530,8 +546,9 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
List<Persistent> filteredObjects = new ArrayList<>();
for (Persistent object : objectMap.values()) {
- if (object.getPersistenceState() == state) {
- filteredObjects.add(object);
+ ObjectStorePersistentWrapper wrapper =
(ObjectStorePersistentWrapper) object;
+ if (wrapper.hasObject() && object.getPersistenceState() == state) {
+ filteredObjects.add(wrapper.dataObject());
}
}
@@ -595,20 +612,24 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
if (object != null) {
object.setObjectId((ObjectId) newId);
- objectMap.put(newId, object);
+ objectMap.merge(newId, object, (oldValue, newValue) -> {
+ ObjectStorePersistentWrapper oldWrapper =
(ObjectStorePersistentWrapper) oldValue;
+ ObjectStorePersistentWrapper newWrapper =
(ObjectStorePersistentWrapper) newValue;
+ if(oldWrapper.trackedFlattenedPaths != null) {
+ if(newWrapper.trackedFlattenedPaths != null) {
+
newWrapper.trackedFlattenedPaths.putAll(oldWrapper.trackedFlattenedPaths);
+ } else {
+ newWrapper.trackedFlattenedPaths =
oldWrapper.trackedFlattenedPaths;
+ }
+ }
+ return newWrapper;
+ });
ObjectDiff change = changes.remove(nodeId);
if (change != null) {
changes.put(newId, change);
}
}
-
- if(trackedFlattenedPaths != null) {
- Map<String, ObjectId> paths = trackedFlattenedPaths.remove(nodeId);
- if(paths != null) {
- trackedFlattenedPaths.put(newId, paths);
- }
- }
}
/**
@@ -619,7 +640,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
void processDeletedID(ObjectId nodeId) {
// access object map directly - the method should be called in a
synchronized context...
- Persistent object = objectMap.get(nodeId);
+ Persistent object = getUnwrapped(nodeId);
if (object != null) {
@@ -725,7 +746,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
void processIndirectlyModifiedIDs(Collection<ObjectId>
indirectlyModifiedIDs) {
for (ObjectId oid : indirectlyModifiedIDs) {
// access object map directly - the method should be called in a
synchronized context...
- final DataObject object = (DataObject) objectMap.get(oid);
+ final DataObject object = (DataObject) getUnwrapped(oid);
if (object == null || object.getPersistenceState() !=
PersistenceState.COMMITTED) {
continue;
@@ -779,7 +800,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
void processUpdatedSnapshot(ObjectId nodeId, DataRow diff) {
// access object map directly - the method should be called in a
synchronized context...
- DataObject object = (DataObject) objectMap.get(nodeId);
+ DataObject object = (DataObject) getUnwrapped(nodeId);
// no object, or HOLLOW object require no processing
if (object != null) {
@@ -858,12 +879,12 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
*/
@Override
public synchronized Object getNode(Object nodeId) {
- return objectMap.get(nodeId);
+ return getUnwrapped(nodeId);
}
// non-synchronized version of getNode for private use
final Object getNodeNoSync(Object nodeId) {
- return objectMap.get(nodeId);
+ return getUnwrapped(nodeId);
}
/**
@@ -874,7 +895,15 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
*/
@Override
public synchronized Collection<Object> registeredNodes() {
- return new ArrayList<Object>(objectMap.values());
+ List<Object> values = new ArrayList<>(objectMap.size());
+ objectMap.forEach((id, persistent)
+ -> {
+ ObjectStorePersistentWrapper wrapper =
(ObjectStorePersistentWrapper) persistent;
+ if(wrapper.hasObject()) {
+ values.add(wrapper.dataObject());
+ }
+ });
+ return values;
}
/**
@@ -882,7 +911,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
*/
@Override
public synchronized void registerNode(Object nodeId, Object nodeObject) {
- objectMap.put(nodeId, (Persistent) nodeObject);
+ objectMap.put(nodeId, new ObjectStorePersistentWrapper((Persistent)
nodeObject));
}
/**
@@ -993,47 +1022,32 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* @since 4.1
*/
boolean hasFlattenedPath(ObjectId objectId, String path) {
- if(trackedFlattenedPaths == null) {
- return false;
- }
- return trackedFlattenedPaths
- .getOrDefault(objectId,
Collections.emptyMap()).containsKey(path);
+ ObjectStorePersistentWrapper wrapper = (ObjectStorePersistentWrapper)
objectMap.get(objectId);
+ return wrapper.hasFlattenedPath(path);
}
/**
* @since 4.2
*/
public ObjectId getFlattenedId(ObjectId objectId, String path) {
- if(trackedFlattenedPaths == null) {
- return null;
- }
-
- return trackedFlattenedPaths
- .getOrDefault(objectId, Collections.emptyMap()).get(path);
+ ObjectStorePersistentWrapper wrapper = (ObjectStorePersistentWrapper)
objectMap.get(objectId);
+ return wrapper.getFlattenedId(path);
}
/**
* @since 4.2
*/
public Collection<ObjectId> getFlattenedIds(ObjectId objectId) {
- if(trackedFlattenedPaths == null) {
- return Collections.emptyList();
- }
-
- return trackedFlattenedPaths
- .getOrDefault(objectId, Collections.emptyMap()).values();
+ ObjectStorePersistentWrapper wrapper = (ObjectStorePersistentWrapper)
objectMap.get(objectId);
+ return wrapper.getFlattenedIds();
}
/**
* @since 4.2.1
*/
public Map<String, ObjectId> getFlattenedPathIdMap(ObjectId objectId) {
- if(trackedFlattenedPaths == null) {
- return Collections.emptyMap();
- }
-
- return trackedFlattenedPaths
- .getOrDefault(objectId, Collections.emptyMap());
+ ObjectStorePersistentWrapper wrapper = (ObjectStorePersistentWrapper)
objectMap.get(objectId);
+ return wrapper.getFlattenedPathIdMap();
}
/**
@@ -1041,12 +1055,9 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* @since 4.1
*/
public void markFlattenedPath(ObjectId objectId, String path, ObjectId id)
{
- if(trackedFlattenedPaths == null) {
- trackedFlattenedPaths = new ConcurrentHashMap<>();
- }
- trackedFlattenedPaths
- .computeIfAbsent(objectId, o -> new ConcurrentHashMap<>())
- .put(path, id);
+ ObjectStorePersistentWrapper wrapper = (ObjectStorePersistentWrapper)
objectMap
+ .computeIfAbsent(objectId, objId -> new
ObjectStorePersistentWrapper(null));
+ wrapper.markFlattenedPath(path, id);
}
// an ObjectIdQuery optimized for retrieval of multiple snapshots - it can
be reset
@@ -1090,4 +1101,29 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
throw new UnsupportedOperationException();
}
}
+
+ static class WrapperIterator implements Iterator<Persistent> {
+
+ final Iterator<ObjectStorePersistentWrapper> iterator;
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ WrapperIterator(Iterator<Persistent> iterator) {
+ this.iterator = (Iterator)iterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public Persistent next() {
+ return iterator.next().dataObject();
+ }
+
+ @Override
+ public void remove() {
+ iterator.remove();
+ }
+ }
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStorePersistentWrapper.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStorePersistentWrapper.java
new file mode 100644
index 000000000..8a7677966
--- /dev/null
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStorePersistentWrapper.java
@@ -0,0 +1,105 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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 org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Wrapper for a {@link Persistent} object used by the {@link ObjectStore} to
keep additional info about persistent.
+ * <p>
+ * Right now the only additional information it keeps is flattened path linked
to the object.
+ * @since 4.2.2
+ */
+public class ObjectStorePersistentWrapper implements Persistent {
+ protected final Persistent dataObject;
+ protected Map<String, ObjectId> trackedFlattenedPaths;
+
+ public ObjectStorePersistentWrapper(Persistent dataObject) {
+ this.dataObject = dataObject;
+ }
+
+ public boolean hasObject() {
+ return dataObject != null;
+ }
+
+ public Persistent dataObject() {
+ return dataObject;
+ }
+
+ public void markFlattenedPath(String path, ObjectId objectId) {
+ if (trackedFlattenedPaths == null) {
+ trackedFlattenedPaths = new ConcurrentHashMap<>();
+ }
+ trackedFlattenedPaths.put(path, objectId);
+ }
+
+ public boolean hasFlattenedPath(String path) {
+ return trackedFlattenedPaths != null &&
trackedFlattenedPaths.containsKey(path);
+ }
+
+ public ObjectId getFlattenedId(String path) {
+ return trackedFlattenedPaths == null ? null :
trackedFlattenedPaths.get(path);
+ }
+
+ public Collection<ObjectId> getFlattenedIds() {
+ return trackedFlattenedPaths == null ? Collections.emptyList() :
trackedFlattenedPaths.values();
+ }
+
+ public Map<String, ObjectId> getFlattenedPathIdMap() {
+ return trackedFlattenedPaths == null ? Collections.emptyMap() :
trackedFlattenedPaths;
+ }
+
+ @Override
+ public ObjectId getObjectId() {
+ return dataObject.getObjectId();
+ }
+
+ @Override
+ public void setObjectId(ObjectId id) {
+ dataObject.setObjectId(id);
+ }
+
+ @Override
+ public int getPersistenceState() {
+ return dataObject.getPersistenceState();
+ }
+
+ @Override
+ public void setPersistenceState(int state) {
+ dataObject.setPersistenceState(state);
+ }
+
+ @Override
+ public ObjectContext getObjectContext() {
+ return dataObject.getObjectContext();
+ }
+
+ @Override
+ public void setObjectContext(ObjectContext objectContext) {
+ dataObject.setObjectContext(objectContext);
+ }
+}