This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push:
new bf3446627 CAY-2876 Memory leak in the ObjectStore
bf3446627 is described below
commit bf344662776351b64deb1fdf0bbe0676db86c92e
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 +
.../commitlog/CommitLogFilter_All_FlattenedIT.java | 10 +-
.../access/DefaultObjectMapRetainStrategy.java | 4 +-
.../apache/cayenne/access/NoSyncObjectStore.java | 4 +-
.../java/org/apache/cayenne/access/ObjectDiff.java | 43 +++--
.../cayenne/access/ObjectMapRetainStrategy.java | 4 +-
.../org/apache/cayenne/access/ObjectStore.java | 195 +++++++++++----------
.../apache/cayenne/access/ObjectStoreEntry.java | 70 ++++++++
.../cayenne/access/ObjectStoreGraphDiff.java | 7 +-
.../access/DataContextPrefetchMultistepIT.java | 2 +-
.../cayenne/access/DataContextSerializationIT.java | 4 +-
.../org/apache/cayenne/access/ObjectStoreTest.java | 2 +-
12 files changed, 219 insertions(+), 127 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index f964462b3..52ab66e33 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -21,6 +21,7 @@ Bug Fixes:
CAY-2701 MySQL DST-related LocalDateTime issues
CAY-2871 QualifierTranslator breaks on a relationship with a compound FK
CAY-2872 CayenneModeler "Documentation" link is broken
+CAY-2876 Memory leak in the ObjectStore
CAY-2879 Negative number for non parameterized ObjectSelect query not
processed correctly
----------------------------------
diff --git
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java
index 7a755890a..63c0f1a6e 100644
---
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java
+++
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java
@@ -27,6 +27,8 @@ import org.apache.cayenne.commitlog.model.ObjectChange;
import org.apache.cayenne.commitlog.model.ObjectChangeType;
import org.apache.cayenne.commitlog.model.ToManyRelationshipChange;
import org.apache.cayenne.commitlog.unit.FlattenedRuntimeCase;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.runtime.CoreModule;
import org.apache.cayenne.query.SelectById;
import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
import org.junit.Before;
@@ -48,6 +50,8 @@ public class CommitLogFilter_All_FlattenedIT extends
FlattenedRuntimeCase {
protected CayenneRuntimeBuilder configureCayenne() {
this.mockListener = mock(CommitLogListener.class);
return super.configureCayenne()
+ .addModule(b -> CoreModule.extend(b)
+
.setProperty(Constants.OBJECT_RETAIN_STRATEGY_PROPERTY, "soft"))
.addModule(b ->
CommitLogModule.extend(b).addListener(mockListener));
}
@@ -63,9 +67,9 @@ public class CommitLogFilter_All_FlattenedIT extends
FlattenedRuntimeCase {
e4.insert(12);
e34.insert(1, 11);
- E3 e3 = SelectById.query(E3.class, 1).selectOne(context);
- E4 e4_1 = SelectById.query(E4.class, 11).selectOne(context);
- E4 e4_2 = SelectById.query(E4.class, 12).selectOne(context);
+ E3 e3 = SelectById.queryId(E3.class, 1).selectOne(context);
+ E4 e4_1 = SelectById.queryId(E4.class, 11).selectOne(context);
+ E4 e4_2 = SelectById.queryId(E4.class, 12).selectOne(context);
doAnswer((Answer<Object>) invocation -> {
diff --git
a/cayenne/src/main/java/org/apache/cayenne/access/DefaultObjectMapRetainStrategy.java
b/cayenne/src/main/java/org/apache/cayenne/access/DefaultObjectMapRetainStrategy.java
index 05f248ed8..8fcbf42db 100644
---
a/cayenne/src/main/java/org/apache/cayenne/access/DefaultObjectMapRetainStrategy.java
+++
b/cayenne/src/main/java/org/apache/cayenne/access/DefaultObjectMapRetainStrategy.java
@@ -22,7 +22,7 @@ import java.util.HashMap;
import java.util.Map;
import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.Persistent;
+import org.apache.cayenne.ObjectId;
import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.configuration.RuntimeProperties;
import org.apache.cayenne.di.Inject;
@@ -46,7 +46,7 @@ public class DefaultObjectMapRetainStrategy implements
ObjectMapRetainStrategy {
this.runtimeProperties = runtimeProperties;
}
- public Map<Object, Persistent> createObjectMap() {
+ public Map<ObjectId, ObjectStoreEntry> createObjectMap() {
String strategy =
runtimeProperties.get(Constants.OBJECT_RETAIN_STRATEGY_PROPERTY);
if (strategy == null || WEAK_RETAIN_STRATEGY.equals(strategy)) {
diff --git
a/cayenne/src/main/java/org/apache/cayenne/access/NoSyncObjectStore.java
b/cayenne/src/main/java/org/apache/cayenne/access/NoSyncObjectStore.java
index 9358c08d2..914543a23 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/NoSyncObjectStore.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/NoSyncObjectStore.java
@@ -20,7 +20,7 @@ package org.apache.cayenne.access;
import java.util.Map;
-import org.apache.cayenne.Persistent;
+import org.apache.cayenne.ObjectId;
/**
* An {@link ObjectStore} which doesn't receive notifications
@@ -30,7 +30,7 @@ import org.apache.cayenne.Persistent;
*/
public class NoSyncObjectStore extends ObjectStore {
- public NoSyncObjectStore(DataRowStore dataRowCache, Map<Object,
Persistent> objectMap) {
+ public NoSyncObjectStore(DataRowStore dataRowCache, Map<ObjectId,
ObjectStoreEntry> objectMap) {
super(dataRowCache, objectMap);
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ObjectDiff.java
b/cayenne/src/main/java/org/apache/cayenne/access/ObjectDiff.java
index 9b2b68de4..6ae2df6d3 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectDiff.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectDiff.java
@@ -65,23 +65,22 @@ public class ObjectDiff extends NodeDiff {
private Map<ArcOperation, ArcOperation> flatIds;
private Map<ArcOperation, ArcOperation> phantomFks;
- private Persistent object;
+ private final ObjectStoreEntry entry;
- ObjectDiff(final Persistent object) {
+ ObjectDiff(final ObjectStoreEntry entry) {
- super(object.getObjectId());
+ super(entry.persistent().getObjectId());
- // 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;
+ // retain the object, as ObjectStore may have weak references to
registered objects,
+ // and we can't allow it to deallocate dirty objects.
+ this.entry = entry;
- EntityResolver entityResolver =
object.getObjectContext().getEntityResolver();
+ EntityResolver entityResolver =
object().getObjectContext().getEntityResolver();
- this.entityName = object.getObjectId().getEntityName();
+ this.entityName = object().getObjectId().getEntityName();
this.classDescriptor = entityResolver.getClassDescriptor(entityName);
- int state = object.getPersistenceState();
+ int state = object().getPersistenceState();
// take snapshot of simple properties and arcs used for optimistic
// locking..
@@ -99,7 +98,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()));
return true;
}
@@ -114,8 +113,8 @@ public class ObjectDiff extends NodeDiff {
// eagerly resolve optimistically locked relationships
Object target = (lock && isUsedForLocking)
- ? property.readProperty(object)
- : property.readPropertyDirectly(object);
+ ? property.readProperty(object())
+ : property.readPropertyDirectly(object());
if (target instanceof Persistent) {
target = ((Persistent) target).getObjectId();
@@ -129,15 +128,15 @@ public class ObjectDiff extends NodeDiff {
}
}
- Object getObject() {
- return object;
+ Persistent object() {
+ return entry.persistent();
}
ClassDescriptor getClassDescriptor() {
// class descriptor is initiated in constructor, but is nullified on
// serialization
if (classDescriptor == null) {
- EntityResolver entityResolver =
object.getObjectContext().getEntityResolver();
+ EntityResolver entityResolver =
object().getObjectContext().getEntityResolver();
this.classDescriptor =
entityResolver.getClassDescriptor(entityName);
}
@@ -152,7 +151,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(), propertyName);
value = target != null ? target.getObjectId() : null;
arcSnapshot.put(propertyName, value);
@@ -167,7 +166,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(), propertyName);
value = target != null ? target.getObjectId() : null;
currentArcSnapshot.put(propertyName, value);
@@ -328,7 +327,7 @@ public class ObjectDiff extends NodeDiff {
return false;
}
- int state = object.getPersistenceState();
+ int state = object().getPersistenceState();
if (state == PersistenceState.NEW || state ==
PersistenceState.DELETED) {
return false;
}
@@ -342,7 +341,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());
if (!property.equals(oldValue, newValue)) {
modFound[0] = true;
@@ -363,7 +362,7 @@ public class ObjectDiff extends NodeDiff {
return true;
}
- Object newValue = property.readPropertyDirectly(object);
+ Object newValue = property.readPropertyDirectly(object());
if (newValue instanceof Fault) {
return true;
}
@@ -411,7 +410,7 @@ public class ObjectDiff extends NodeDiff {
@Override
public boolean visitAttribute(AttributeProperty property) {
- Object newValue = property.readProperty(object);
+ Object newValue = property.readProperty(object());
// no baseline to compare
if (snapshot == null) {
diff --git
a/cayenne/src/main/java/org/apache/cayenne/access/ObjectMapRetainStrategy.java
b/cayenne/src/main/java/org/apache/cayenne/access/ObjectMapRetainStrategy.java
index 8d1e4fbb9..3749298d0 100644
---
a/cayenne/src/main/java/org/apache/cayenne/access/ObjectMapRetainStrategy.java
+++
b/cayenne/src/main/java/org/apache/cayenne/access/ObjectMapRetainStrategy.java
@@ -20,7 +20,7 @@ package org.apache.cayenne.access;
import java.util.Map;
-import org.apache.cayenne.Persistent;
+import org.apache.cayenne.ObjectId;
/**
* A strategy for retaining objects in {@link ObjectStore}. The strategy can
be weak, soft
@@ -30,5 +30,5 @@ import org.apache.cayenne.Persistent;
*/
public interface ObjectMapRetainStrategy {
- Map<Object, Persistent> createObjectMap();
+ Map<ObjectId, ObjectStoreEntry> createObjectMap();
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
index 0dcdc7aa1..ff7f2facd 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
@@ -53,7 +53,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-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
@@ -65,14 +65,16 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class ObjectStore implements Serializable, SnapshotEventListener,
GraphManager {
- protected Map<Object, Persistent> objectMap;
+ protected Map<ObjectId, ObjectStoreEntry> objectMap;
protected Map<Object, ObjectDiff> changes;
/**
* 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 5.0 unused
*/
+ @Deprecated
protected Map<Object, Map<CayennePath, ObjectId>> trackedFlattenedPaths;
// a sequential id used to tag GraphDiffs so that they can later be sorted
in the
@@ -106,7 +108,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
*
* @since 3.0
*/
- public ObjectStore(DataRowStore dataRowCache, Map<Object, Persistent>
objectMap) {
+ public ObjectStore(DataRowStore dataRowCache, Map<ObjectId,
ObjectStoreEntry> objectMap) {
setDataRowCache(dataRowCache);
if (objectMap != null) {
this.objectMap = objectMap;
@@ -137,7 +139,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
Collection<GraphDiff> getLifecycleEventInducedChanges() {
return lifecycleEventInducedChanges != null
? lifecycleEventInducedChanges
- : Collections.<GraphDiff>emptyList();
+ : Collections.emptyList();
}
void registerLifecycleEventInducedChange(GraphDiff diff) {
@@ -165,11 +167,13 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
if (objectDiff == null) {
- Persistent object = objectMap.get(nodeId);
- if (object == null) {
+ ObjectStoreEntry entry = objectMap.get((ObjectId)nodeId);
+ if (entry == null || !entry.hasObject()) {
throw new CayenneRuntimeException("No object is registered in
context with Id %s", nodeId);
}
+ Persistent object = entry.persistent();
+
if (object.getPersistenceState() == PersistenceState.COMMITTED) {
object.setPersistenceState(PersistenceState.MODIFIED);
@@ -196,7 +200,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
}
}
- objectDiff = new ObjectDiff(object);
+ objectDiff = new ObjectDiff(entry);
objectDiff.setDiffId(++currentDiffId);
changes.put(nodeId, objectDiff);
}
@@ -214,7 +218,13 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* @since 1.2
*/
public int registeredObjectsCount() {
- return objectMap.size();
+ AtomicInteger counter = new AtomicInteger();
+ objectMap.forEach((id, obj) -> {
+ if(obj.hasObject()){
+ counter.incrementAndGet();
+ }
+ });
+ return counter.get();
}
/**
@@ -283,7 +293,7 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
*/
// this method is exactly the same as "objectsInvalidated", only
additionally it
// throws out registered objects
- public synchronized void objectsUnregistered(Collection objects) {
+ public synchronized void objectsUnregistered(Collection<?> objects) {
if (objects.isEmpty()) {
return;
}
@@ -298,9 +308,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);
@@ -314,10 +321,10 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
// send an event for removed snapshots
getDataRowCache().processSnapshotChanges(
this,
- Collections.<ObjectId, DataRow>emptyMap(),
- Collections.<ObjectId>emptyList(),
+ Collections.emptyMap(),
+ Collections.emptyList(),
ids,
- Collections.<ObjectId>emptyList());
+ Collections.emptyList());
}
}
@@ -386,11 +393,11 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
for (Object id : changes.keySet()) {
- Persistent object = objectMap.get(id);
+ ObjectStoreEntry object = objectMap.get((ObjectId)id);
// assume that no new or deleted objects are present (as otherwise
commit
// wouldn't have been phantom).
- object.setPersistenceState(PersistenceState.COMMITTED);
+
object.persistent().setPersistenceState(PersistenceState.COMMITTED);
}
// clear caches
@@ -405,8 +412,10 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
public void postprocessAfterCommit(GraphDiff parentChanges) {
// scan through changed objects, set persistence state to committed
- for (Object id : changes.keySet()) {
- Persistent object = objectMap.get(id);
+ for (Object key : changes.keySet()) {
+ ObjectId id = (ObjectId) key;
+ // persistent object for the diff should always exist
+ Persistent object = objectMap.get(id).persistent();
switch (object.getPersistenceState()) {
case PersistenceState.DELETED:
@@ -503,7 +512,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 EntryIterator(objectMap.values().iterator());
}
/**
@@ -524,9 +533,9 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
public synchronized List<Persistent> objectsInState(int state) {
List<Persistent> filteredObjects = new ArrayList<>();
- for (Persistent object : objectMap.values()) {
- if (object.getPersistenceState() == state) {
- filteredObjects.add(object);
+ for (ObjectStoreEntry entry : objectMap.values()) {
+ if (entry.hasObject() && entry.persistent().getPersistenceState()
== state) {
+ filteredObjects.add(entry.persistent());
}
}
@@ -586,11 +595,21 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
}
void processIdChange(Object nodeId, Object newId) {
- Persistent object = objectMap.remove(nodeId);
-
- if (object != null) {
- object.setObjectId((ObjectId) newId);
- objectMap.put(newId, object);
+ ObjectStoreEntry entry = objectMap.remove((ObjectId)nodeId);
+
+ if (entry != null) {
+ ObjectId id = (ObjectId) newId;
+ entry.persistent().setObjectId(id);
+ objectMap.merge(id, entry, (oldValue, newValue) -> {
+ if(oldValue.trackedFlattenedPaths != null) {
+ if(newValue.trackedFlattenedPaths != null) {
+
newValue.trackedFlattenedPaths.putAll(oldValue.trackedFlattenedPaths);
+ } else {
+ newValue.trackedFlattenedPaths =
oldValue.trackedFlattenedPaths;
+ }
+ }
+ return newValue;
+ });
ObjectDiff change = changes.remove(nodeId);
if (change != null) {
@@ -598,12 +617,6 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
}
}
- if(trackedFlattenedPaths != null) {
- Map<CayennePath, ObjectId> paths =
trackedFlattenedPaths.remove(nodeId);
- if(paths != null) {
- trackedFlattenedPaths.put(newId, paths);
- }
- }
}
/**
@@ -614,9 +627,10 @@ 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);
+ ObjectStoreEntry entry = objectMap.get(nodeId);
- if (object != null) {
+ if (entry != null && entry.hasObject()) {
+ Persistent object = entry.persistent();
DataContextDelegate delegate;
switch (object.getPersistenceState()) {
@@ -712,11 +726,14 @@ 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 Persistent object = objectMap.get(oid);
+ ObjectStoreEntry entry = objectMap.get(oid);
- if (object == null || object.getPersistenceState() !=
PersistenceState.COMMITTED) {
+ if (entry == null
+ || !entry.hasObject()
+ || entry.persistent().getPersistenceState() !=
PersistenceState.COMMITTED) {
continue;
}
+ Persistent object = entry.persistent();
// for now break all "independent" object relationships...
// in the future we may want to be more precise and go after
modified
@@ -766,11 +783,12 @@ 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...
- Persistent object = objectMap.get(nodeId);
+ ObjectStoreEntry entry = objectMap.get(nodeId);
// no object, or HOLLOW object require no processing
- if (object != null) {
+ if (entry != null && entry.hasObject()) {
+ Persistent object = entry.persistent();
int state = object.getPersistenceState();
if (state != PersistenceState.HOLLOW) {
@@ -844,13 +862,12 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* @since 1.2
*/
@Override
- 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);
+ public synchronized Persistent getNode(Object nodeId) {
+ ObjectStoreEntry entry = objectMap.get((ObjectId) nodeId);
+ if(entry == null) {
+ return null;
+ }
+ return entry.persistent();
}
/**
@@ -861,7 +878,13 @@ 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, entry) -> {
+ if(entry.hasObject()) {
+ values.add(entry.persistent());
+ }
+ });
+ return values;
}
/**
@@ -869,15 +892,15 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
*/
@Override
public synchronized void registerNode(Object nodeId, Object nodeObject) {
- objectMap.put(nodeId, (Persistent) nodeObject);
+ objectMap.put((ObjectId)nodeId, new ObjectStoreEntry((Persistent)
nodeObject));
}
/**
* @since 1.2
*/
@Override
- public synchronized Object unregisterNode(Object nodeId) {
- Object object = getNode(nodeId);
+ public synchronized Persistent unregisterNode(Object nodeId) {
+ Persistent object = getNode(nodeId);
if (object != null) {
objectsUnregistered(Collections.singleton(object));
}
@@ -975,52 +998,28 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
registerDiff(nodeId, diff);
}
- /**
- * Check that flattened path for given object ID has data row in DB.
- * @since 4.1
- */
- boolean hasFlattenedPath(ObjectId objectId, CayennePath path) {
- if(trackedFlattenedPaths == null) {
- return false;
- }
- return trackedFlattenedPaths
- .getOrDefault(objectId,
Collections.emptyMap()).containsKey(path);
- }
-
/**
* @since 4.2
*/
public ObjectId getFlattenedId(ObjectId objectId, CayennePath path) {
- if(trackedFlattenedPaths == null) {
- return null;
- }
-
- return trackedFlattenedPaths
- .getOrDefault(objectId, Collections.emptyMap()).get(path);
+ ObjectStoreEntry entry = objectMap.get(objectId);
+ return entry == null ? null : entry.getFlattenedId(path);
}
/**
* @since 4.2
*/
public Collection<ObjectId> getFlattenedIds(ObjectId objectId) {
- if(trackedFlattenedPaths == null) {
- return Collections.emptyList();
- }
-
- return trackedFlattenedPaths
- .getOrDefault(objectId, Collections.emptyMap()).values();
+ ObjectStoreEntry entry = objectMap.get(objectId);
+ return entry == null ? null : entry.getFlattenedIds();
}
/**
* @since 5.0
*/
- public Map<CayennePath,ObjectId> getFlattenedPathIdMap(ObjectId objectId) {
- if(trackedFlattenedPaths == null) {
- return Collections.emptyMap();
- }
-
- return trackedFlattenedPaths
- .getOrDefault(objectId, Collections.emptyMap());
+ public Map<CayennePath, ObjectId> getFlattenedPathIdMap(ObjectId objectId)
{
+ ObjectStoreEntry entry = objectMap.get(objectId);
+ return entry == null ? null : entry.getFlattenedPathIdMap();
}
/**
@@ -1028,12 +1027,8 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
* @since 4.1
*/
public void markFlattenedPath(ObjectId objectId, CayennePath path,
ObjectId id) {
- if(trackedFlattenedPaths == null) {
- trackedFlattenedPaths = new ConcurrentHashMap<>();
- }
- trackedFlattenedPaths
- .computeIfAbsent(objectId, o -> new ConcurrentHashMap<>())
- .put(path, id);
+ objectMap.computeIfAbsent(objectId, objId -> new
ObjectStoreEntry(null))
+ .markFlattenedPath(path, id);
}
// an ObjectIdQuery optimized for retrieval of multiple snapshots - it can
be reset
@@ -1077,4 +1072,28 @@ public class ObjectStore implements Serializable,
SnapshotEventListener, GraphMa
throw new UnsupportedOperationException();
}
}
+
+ static class EntryIterator implements Iterator<Persistent> {
+
+ final Iterator<ObjectStoreEntry> iterator;
+
+ EntryIterator(Iterator<ObjectStoreEntry> iterator) {
+ this.iterator = iterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public Persistent next() {
+ return iterator.next().persistent();
+ }
+
+ @Override
+ public void remove() {
+ iterator.remove();
+ }
+ }
}
diff --git
a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreEntry.java
b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreEntry.java
new file mode 100644
index 000000000..8aa0a3261
--- /dev/null
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreEntry.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ * 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.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.path.CayennePath;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @since 5.0
+ */
+public class ObjectStoreEntry implements Serializable {
+
+ final protected Persistent persistent;
+ protected Map<CayennePath, ObjectId> trackedFlattenedPaths;
+
+ public ObjectStoreEntry(Persistent persistent) {
+ this.persistent = persistent;
+ }
+
+ public Persistent persistent() {
+ return persistent;
+ }
+
+ public boolean hasObject() {
+ return persistent != null;
+ }
+
+ public void markFlattenedPath(CayennePath path, ObjectId objectId) {
+ if (trackedFlattenedPaths == null) {
+ trackedFlattenedPaths = new ConcurrentHashMap<>();
+ }
+ trackedFlattenedPaths.put(path, objectId);
+ }
+
+ public ObjectId getFlattenedId(CayennePath path) {
+ return trackedFlattenedPaths == null ? null :
trackedFlattenedPaths.get(path);
+ }
+
+ public Collection<ObjectId> getFlattenedIds() {
+ return trackedFlattenedPaths == null ? Collections.emptyList() :
trackedFlattenedPaths.values();
+ }
+
+ public Map<CayennePath, ObjectId> getFlattenedPathIdMap() {
+ return trackedFlattenedPaths == null ? Collections.emptyMap() :
trackedFlattenedPaths;
+ }
+}
diff --git
a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
index 3043fa403..0208cf5ea 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
@@ -70,8 +70,7 @@ public class ObjectStoreGraphDiff implements GraphDiff {
boolean noop = true;
// build a new collection for validation as validation methods may
- // result in
- // ObjectStore modifications
+ // result in ObjectStore modifications
Collection<Validating> objectsToValidate = null;
@@ -81,12 +80,12 @@ public class ObjectStoreGraphDiff implements GraphDiff {
noop = false;
- if (diff.getObject() instanceof Validating) {
+ if (diff.object() instanceof Validating) {
if (objectsToValidate == null) {
objectsToValidate = new ArrayList<>();
}
- objectsToValidate.add((Validating) diff.getObject());
+ objectsToValidate.add((Validating) diff.object());
}
}
diff --git
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextPrefetchMultistepIT.java
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextPrefetchMultistepIT.java
index 7bc9eab7b..897d60f53 100644
---
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextPrefetchMultistepIT.java
+++
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextPrefetchMultistepIT.java
@@ -113,7 +113,7 @@ public class DataContextPrefetchMultistepIT extends
RuntimeCase {
// get garbage collected, and we won't be able to detect them
// so ensure ObjectStore uses a regular map just for this test
- context.getObjectStore().objectMap = new HashMap<Object, Persistent>();
+ context.getObjectStore().objectMap = new HashMap<>();
// Check the target ArtistExhibit objects do not exist yet
diff --git
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
index 441a9f8e9..face100de 100644
---
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
+++
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
@@ -21,8 +21,8 @@ package org.apache.cayenne.access;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
-import org.apache.cayenne.Persistent;
import org.apache.cayenne.runtime.CayenneRuntime;
import org.apache.cayenne.configuration.DefaultRuntimeProperties;
import org.apache.cayenne.di.Inject;
@@ -152,7 +152,7 @@ public class DataContextSerializationIT extends RuntimeCase
{
new DefaultRuntimeProperties(domain.getProperties()),
domain.getEventManager());
- Map<Object, Persistent> map = new HashMap<>();
+ Map<ObjectId, ObjectStoreEntry> map = new HashMap<>();
DataContext localCacheContext = new DataContext(domain, new
ObjectStore(
snapshotCache,
diff --git
a/cayenne/src/test/java/org/apache/cayenne/access/ObjectStoreTest.java
b/cayenne/src/test/java/org/apache/cayenne/access/ObjectStoreTest.java
index e79770498..b25336196 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/ObjectStoreTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/ObjectStoreTest.java
@@ -42,7 +42,7 @@ public class ObjectStoreTest {
@Before
public void before() {
DataRowStore sharedCache = mock(DataRowStore.class);
- this.objectStore = new ObjectStore(sharedCache, new HashMap<Object,
Persistent>());
+ this.objectStore = new ObjectStore(sharedCache, new HashMap<>());
}
@Test