mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Fixed ALF-10699: Nodes not getting put into new transactions during various operations
- This concludes the bug and more of the node cache refactor - This final part contains: - parentAssocsCache and other node caches are now immutable (at least for the shared cache) - Remove some of the cache double-checks associated with parentAssocsCache - TODO: Simplify getNodeRefStatus and replace with cache read-through for index trackers - TODO: Node archive performance - TODO: Inverse parentAssocsCache is broken, so it needs fixing (minor) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31223 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -119,10 +119,12 @@
|
|||||||
<resultMap id="result_ChildAssoc" type="ChildAssoc">
|
<resultMap id="result_ChildAssoc" type="ChildAssoc">
|
||||||
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
<result property="parentNode.id" column="parentNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
<result property="parentNode.id" column="parentNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
|
<result property="parentNode.version" column="parentNodeVersion" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
<result property="parentNode.store.protocol" column="parentNodeProtocol" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="parentNode.store.protocol" column="parentNodeProtocol" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="parentNode.store.identifier" column="parentNodeIdentifier" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="parentNode.store.identifier" column="parentNodeIdentifier" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="parentNode.uuid" column="parentNodeUuid" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="parentNode.uuid" column="parentNodeUuid" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="childNode.id" column="childNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
<result property="childNode.id" column="childNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
|
<result property="childNode.version" column="childNodeVersion" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
<result property="childNode.store.protocol" column="childNodeProtocol" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="childNode.store.protocol" column="childNodeProtocol" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="childNode.store.identifier" column="childNodeIdentifier" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="childNode.store.identifier" column="childNodeIdentifier" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="childNode.uuid" column="childNodeUuid" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="childNode.uuid" column="childNodeUuid" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
@@ -880,10 +882,12 @@
|
|||||||
select
|
select
|
||||||
assoc.id as id,
|
assoc.id as id,
|
||||||
parentNode.id as parentNodeId,
|
parentNode.id as parentNodeId,
|
||||||
|
parentNode.version as parentNodeVersion,
|
||||||
parentStore.protocol as parentNodeProtocol,
|
parentStore.protocol as parentNodeProtocol,
|
||||||
parentStore.identifier as parentNodeIdentifier,
|
parentStore.identifier as parentNodeIdentifier,
|
||||||
parentNode.uuid as parentNodeUuid,
|
parentNode.uuid as parentNodeUuid,
|
||||||
childNode.id as childNodeId,
|
childNode.id as childNodeId,
|
||||||
|
childNode.version as childNodeVersion,
|
||||||
childStore.protocol as childNodeProtocol,
|
childStore.protocol as childNodeProtocol,
|
||||||
childStore.identifier as childNodeIdentifier,
|
childStore.identifier as childNodeIdentifier,
|
||||||
childNode.uuid as childNodeUuid,
|
childNode.uuid as childNodeUuid,
|
||||||
@@ -895,10 +899,6 @@
|
|||||||
assoc.is_primary as is_primary,
|
assoc.is_primary as is_primary,
|
||||||
assoc.assoc_index as assoc_index
|
assoc.assoc_index as assoc_index
|
||||||
</sql>
|
</sql>
|
||||||
<sql id="select_ChildAssocTxnId_Results">
|
|
||||||
<include refid="alfresco.node.select_ChildAssoc_Results"/>
|
|
||||||
,childNode.transaction_id as childNodeTxnId
|
|
||||||
</sql>
|
|
||||||
<sql id="select_ChildAssoc_FromSimple">
|
<sql id="select_ChildAssoc_FromSimple">
|
||||||
from
|
from
|
||||||
alf_child_assoc assoc
|
alf_child_assoc assoc
|
||||||
@@ -1140,7 +1140,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="select_ParentAssocsOfChild" parameterType="ChildAssoc" resultMap="result_ChildAssocTxnId">
|
<select id="select_ParentAssocsOfChild" parameterType="ChildAssoc" resultMap="result_ChildAssocTxnId">
|
||||||
<include refid="alfresco.node.select_ChildAssocTxnId_Results"/>
|
<include refid="alfresco.node.select_ChildAssoc_Results"/>
|
||||||
<include refid="alfresco.node.select_ChildAssoc_FromSimple"/>
|
<include refid="alfresco.node.select_ChildAssoc_FromSimple"/>
|
||||||
where
|
where
|
||||||
childNode.id = #{childNode.id}
|
childNode.id = #{childNode.id}
|
||||||
|
@@ -167,11 +167,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
private EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable> propertiesCache;
|
private EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable> propertiesCache;
|
||||||
/**
|
/**
|
||||||
* Cache for the Node parent assocs:<br/>
|
* Cache for the Node parent assocs:<br/>
|
||||||
* KEY: ID<br/>
|
* KEY: NodeVersionKey<br/>
|
||||||
* VALUE: ParentAssocs<br/>
|
* VALUE: ParentAssocs<br/>
|
||||||
* VALUE KEY: ChildByNameKey<br/s>
|
* VALUE KEY: ChildByNameKey<br/s>
|
||||||
*/
|
*/
|
||||||
private EntityLookupCache<Long, ParentAssocsInfo, ChildByNameKey> parentAssocsCache;
|
private EntityLookupCache<NodeVersionKey, ParentAssocsInfo, ChildByNameKey> parentAssocsCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Set up various instance-specific members such as caches and locks.
|
* Constructor. Set up various instance-specific members such as caches and locks.
|
||||||
@@ -186,7 +186,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
nodesCache = new EntityLookupCache<Long, Node, NodeRef>(new NodesCacheCallbackDAO());
|
nodesCache = new EntityLookupCache<Long, Node, NodeRef>(new NodesCacheCallbackDAO());
|
||||||
aspectsCache = new EntityLookupCache<NodeVersionKey, Set<QName>, Serializable>(new AspectsCallbackDAO());
|
aspectsCache = new EntityLookupCache<NodeVersionKey, Set<QName>, Serializable>(new AspectsCallbackDAO());
|
||||||
propertiesCache = new EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable>(new PropertiesCallbackDAO());
|
propertiesCache = new EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable>(new PropertiesCallbackDAO());
|
||||||
parentAssocsCache = new EntityLookupCache<Long, ParentAssocsInfo, ChildByNameKey>(new ParentAssocsCallbackDAO());
|
parentAssocsCache = new EntityLookupCache<NodeVersionKey, ParentAssocsInfo, ChildByNameKey>(new ParentAssocsCallbackDAO());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -338,9 +338,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
*
|
*
|
||||||
* @param parentAssocsCache the cache
|
* @param parentAssocsCache the cache
|
||||||
*/
|
*/
|
||||||
public void setParentAssocsCache(SimpleCache<Long, ParentAssocsInfo> parentAssocsCache)
|
public void setParentAssocsCache(SimpleCache<NodeVersionKey, ParentAssocsInfo> parentAssocsCache)
|
||||||
{
|
{
|
||||||
this.parentAssocsCache = new EntityLookupCache<Long, ParentAssocsInfo, ChildByNameKey>(
|
this.parentAssocsCache = new EntityLookupCache<NodeVersionKey, ParentAssocsInfo, ChildByNameKey>(
|
||||||
parentAssocsCache,
|
parentAssocsCache,
|
||||||
CACHE_REGION_PARENT_ASSOCS,
|
CACHE_REGION_PARENT_ASSOCS,
|
||||||
new ParentAssocsCallbackDAO());
|
new ParentAssocsCallbackDAO());
|
||||||
@@ -434,90 +434,61 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
* Cache helpers
|
* Cache helpers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
private void clearCaches()
|
||||||
* {@inheritDoc #invalidateCachesByNodeId(Long, Long, List)}
|
|
||||||
*/
|
|
||||||
private void invalidateCachesByNodeId(
|
|
||||||
Long parentNodeId,
|
|
||||||
Long childNodeId,
|
|
||||||
EntityLookupCache<Long, ? extends Object, ? extends Serializable> cache)
|
|
||||||
{
|
{
|
||||||
invalidateCachesByNodeId(
|
nodesCache.clear();
|
||||||
parentNodeId,
|
aspectsCache.clear();
|
||||||
childNodeId,
|
propertiesCache.clear();
|
||||||
Collections.<EntityLookupCache<Long, ? extends Object, ? extends Serializable>>singletonList(cache));
|
parentAssocsCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate cache entries for given nodes. If the parent node is provided,
|
* Invalidate cache entries for all children of a give node. This usually applies
|
||||||
* then all children of that parent will be retrieved and their cache entries will
|
* where the child associations or nodes are modified en-masse.
|
||||||
* be removed; this usually applies where the child associations or nodes are
|
|
||||||
* modified en-masse.
|
|
||||||
*
|
*
|
||||||
* @param parentNodeId the parent node of all child nodes to be invalidated (may be <tt>null</tt>)
|
* @param parentNodeId the parent node of all child nodes to be invalidated (may be <tt>null</tt>)
|
||||||
* @param childNodeId the specific child node to invalidate (may be <tt>null</tt>)
|
|
||||||
* @param caches caches to invalidate by node id, which must use a <tt>Long</tt> as the key
|
|
||||||
*/
|
*/
|
||||||
private void invalidateCachesByNodeId(
|
private void invalidateNodeChildrenCaches(Long parentNodeId)
|
||||||
Long parentNodeId,
|
|
||||||
Long childNodeId,
|
|
||||||
final List<EntityLookupCache<Long, ? extends Object, ? extends Serializable>> caches)
|
|
||||||
{
|
{
|
||||||
if (childNodeId != null)
|
// Select all children
|
||||||
|
ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
|
||||||
{
|
{
|
||||||
for (EntityLookupCache<Long, ? extends Object, ? extends Serializable> cache : caches)
|
private int count = 0;
|
||||||
|
private boolean isClearOn = false;
|
||||||
|
|
||||||
|
public boolean preLoadNodes()
|
||||||
{
|
{
|
||||||
cache.removeByKey(childNodeId);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (parentNodeId != null)
|
public boolean handle(
|
||||||
{
|
Pair<Long, ChildAssociationRef> childAssocPair,
|
||||||
// Select all children
|
Pair<Long, NodeRef> parentNodePair,
|
||||||
ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
|
Pair<Long, NodeRef> childNodePair)
|
||||||
{
|
{
|
||||||
private int count = 0;
|
if (isClearOn)
|
||||||
private boolean isClearOn = false;
|
|
||||||
|
|
||||||
public boolean preLoadNodes()
|
|
||||||
{
|
{
|
||||||
|
// We have already decided to drop ALL cache entries
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else if (count >= 1000)
|
||||||
public boolean handle(
|
|
||||||
Pair<Long, ChildAssociationRef> childAssocPair,
|
|
||||||
Pair<Long, NodeRef> parentNodePair,
|
|
||||||
Pair<Long, NodeRef> childNodePair)
|
|
||||||
{
|
{
|
||||||
if (isClearOn)
|
// That's enough. Instead of walking thousands of entries
|
||||||
{
|
// we just drop the cache at this stage
|
||||||
// We have already decided to drop ALL cache entries
|
AbstractNodeDAOImpl.this.clearCaches();
|
||||||
return false;
|
isClearOn = true;
|
||||||
}
|
return false; // No more, please
|
||||||
else if (count >= 1000)
|
|
||||||
{
|
|
||||||
// That's enough. Instead of walking thousands of entries
|
|
||||||
// we just drop the cache at this stage
|
|
||||||
for (EntityLookupCache<Long, ? extends Object, ? extends Serializable> cache : caches)
|
|
||||||
{
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
||||||
isClearOn = true;
|
|
||||||
return false; // No more, please
|
|
||||||
}
|
|
||||||
count++;
|
|
||||||
for (EntityLookupCache<Long, ? extends Object, ? extends Serializable> cache : caches)
|
|
||||||
{
|
|
||||||
cache.removeByKey(childNodePair.getFirst());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
count++;
|
||||||
|
invalidateNodeCaches(childNodePair.getFirst());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void done()
|
public void done()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
selectChildAssocs(parentNodeId, null, null, null, null, null, callback);
|
selectChildAssocs(parentNodeId, null, null, null, null, null, callback);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -532,18 +503,38 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
if (nodePair != null)
|
if (nodePair != null)
|
||||||
{
|
{
|
||||||
NodeVersionKey nodeVersionKey = nodePair.getSecond().getNodeVersionKey();
|
NodeVersionKey nodeVersionKey = nodePair.getSecond().getNodeVersionKey();
|
||||||
// Properties
|
invalidateNodeCaches(nodeVersionKey, true, true, true);
|
||||||
propertiesCache.removeByKey(nodeVersionKey);
|
|
||||||
// Aspects
|
|
||||||
aspectsCache.removeByKey(nodeVersionKey);
|
|
||||||
// Parent Assocs
|
|
||||||
parentAssocsCache.removeByKey(nodeId);
|
|
||||||
}
|
}
|
||||||
invalidateCachesByNodeId(nodeId, null, parentAssocsCache);
|
|
||||||
// Finally remove the node reference
|
// Finally remove the node reference
|
||||||
nodesCache.removeByKey(nodeId);
|
nodesCache.removeByKey(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate specific node caches using an exact key
|
||||||
|
*
|
||||||
|
* @param nodeVersionKey the node ID-VERSION key to use
|
||||||
|
*/
|
||||||
|
private void invalidateNodeCaches(
|
||||||
|
NodeVersionKey nodeVersionKey,
|
||||||
|
boolean invalidateNodeAspectsCache,
|
||||||
|
boolean invalidateNodePropertiesCache,
|
||||||
|
boolean invalidateParentAssocsCache)
|
||||||
|
{
|
||||||
|
if (invalidateNodeAspectsCache)
|
||||||
|
{
|
||||||
|
aspectsCache.removeByKey(nodeVersionKey);
|
||||||
|
}
|
||||||
|
if (invalidateNodePropertiesCache)
|
||||||
|
{
|
||||||
|
propertiesCache.removeByKey(nodeVersionKey);
|
||||||
|
}
|
||||||
|
if (invalidateParentAssocsCache)
|
||||||
|
{
|
||||||
|
parentAssocsCache.removeByKey(nodeVersionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Transactions
|
* Transactions
|
||||||
*/
|
*/
|
||||||
@@ -1082,9 +1073,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// There will be no other parent assocs
|
// There will be no other parent assocs
|
||||||
boolean isRoot = false;
|
boolean isRoot = false;
|
||||||
boolean isStoreRoot = nodeTypeQName.equals(ContentModel.TYPE_STOREROOT);
|
boolean isStoreRoot = nodeTypeQName.equals(ContentModel.TYPE_STOREROOT);
|
||||||
ParentAssocsInfo parentAssocsInfo = new ParentAssocsInfo(node.getTransaction().getId(), isRoot, isStoreRoot,
|
ParentAssocsInfo parentAssocsInfo = new ParentAssocsInfo(isRoot, isStoreRoot, assoc);
|
||||||
assoc);
|
setParentAssocsCached(nodeId, parentAssocsInfo);
|
||||||
parentAssocsCache.setValue(nodeId, parentAssocsInfo);
|
|
||||||
|
|
||||||
if (isDebugEnabled)
|
if (isDebugEnabled)
|
||||||
{
|
{
|
||||||
@@ -1256,9 +1246,6 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Store
|
// Store
|
||||||
if (!childStore.getId().equals(newParentStore.getId()))
|
if (!childStore.getId().equals(newParentStore.getId()))
|
||||||
{
|
{
|
||||||
// Clear out parent assocs cache; make sure child nodes are updated, too
|
|
||||||
invalidateCachesByNodeId(childNodeId, childNodeId, parentAssocsCache);
|
|
||||||
|
|
||||||
// Remove the cm:auditable aspect from the source node
|
// Remove the cm:auditable aspect from the source node
|
||||||
// Remove the cm:auditable aspect from the old node as the new one will get new values as required
|
// Remove the cm:auditable aspect from the old node as the new one will get new values as required
|
||||||
Set<Long> aspectIdsToDelete = qnameDAO.convertQNamesToIds(
|
Set<Long> aspectIdsToDelete = qnameDAO.convertQNamesToIds(
|
||||||
@@ -1276,11 +1263,14 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
childNode.getAclId(),
|
childNode.getAclId(),
|
||||||
false,
|
false,
|
||||||
auditableProps);
|
auditableProps);
|
||||||
|
Long newChildNodeId = newChildNode.getId();
|
||||||
moveNodeData(
|
moveNodeData(
|
||||||
childNode.getId(),
|
childNode.getId(),
|
||||||
newChildNode.getId());
|
newChildNodeId);
|
||||||
// The new node has cache entries that will need updating to the new values
|
// The new node will have new data not present in the cache, yet
|
||||||
invalidateNodeCaches(newChildNode.getId());
|
// TODO: Look to move the data in a cache-efficient way
|
||||||
|
invalidateNodeCaches(newChildNodeId);
|
||||||
|
invalidateNodeChildrenCaches(newChildNodeId);
|
||||||
// Now update the original to be 'deleted'
|
// Now update the original to be 'deleted'
|
||||||
NodeUpdateEntity childNodeUpdate = new NodeUpdateEntity();
|
NodeUpdateEntity childNodeUpdate = new NodeUpdateEntity();
|
||||||
childNodeUpdate.setId(childNodeId);
|
childNodeUpdate.setId(childNodeId);
|
||||||
@@ -1295,16 +1285,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Update the entity.
|
// Update the entity.
|
||||||
// Note: We don't use delete here because that will attempt to clean everything up again.
|
// Note: We don't use delete here because that will attempt to clean everything up again.
|
||||||
updateNodeImpl(childNode, childNodeUpdate);
|
updateNodeImpl(childNode, childNodeUpdate);
|
||||||
|
// There is no need to invalidate the caches as the touched node's version will have progressed
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Ensure that the child node reflects the current txn and auditable data
|
// Touch the node; make sure parent assocs are invalidated
|
||||||
touchNodeImpl(childNodeId);
|
touchNode(childNodeId, null, false, false, true);
|
||||||
|
|
||||||
// The moved node's reference has not changed, so just remove the cache entry to
|
|
||||||
// it's immediate parent. All children of the moved node will still point to the
|
|
||||||
// correct node.
|
|
||||||
invalidateCachesByNodeId(null, childNodeId, parentAssocsCache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Long newChildNodeId = newChildNode.getId();
|
final Long newChildNodeId = newChildNode.getId();
|
||||||
@@ -1406,21 +1392,31 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the node's transaction and <b>cm:auditable</b> properties only.
|
* Updates the node's transaction and <b>cm:auditable</b> properties while
|
||||||
*/
|
* providing a convenient method to control cache entry invalidation.
|
||||||
private boolean touchNodeImpl(Long nodeId)
|
* <p/>
|
||||||
{
|
* Not all 'touch' signals actually produce a change: the node may already have been touched
|
||||||
return touchNodeImpl(nodeId, null);
|
* in the current transaction. In this case, the required caches are explicitly invalidated
|
||||||
}
|
* as requested.<br/>
|
||||||
/**
|
* It is more complicated when the node is modified. If the node is modified against a previous
|
||||||
* Updates the node's transaction and <b>cm:auditable</b> properties only.
|
* transaction then all cache entries are left untrusted and not pulled forward. But if the
|
||||||
*
|
* node is modified but in the same transaction, then the cache entries are considered good and
|
||||||
* @param auditableProps optionally override the <b>cm:auditable</b> values
|
* pull forward against the current version of the node ... <b>unless</b> the cache was specicially
|
||||||
|
* tagged for invalidation.
|
||||||
*
|
*
|
||||||
|
* @param nodeId the ID of the node (must refer to a live node)
|
||||||
|
* @param auditableProps optionally override the <b>cm:auditable</b> values
|
||||||
|
* @param invalidateNodeAspectsCache <tt>true</tt> if the node's cached aspects are unreliable
|
||||||
|
* @param invalidateNodePropertiesCache <tt>true</tt> if the node's cached properties are unreliable
|
||||||
|
* @param invalidateParentAssocsCache <tt>true</tt> if the node's cached parent assocs are unreliable
|
||||||
*
|
*
|
||||||
* @see #updateNodeImpl(NodeEntity, NodeUpdateEntity)
|
* @see #updateNodeImpl(NodeEntity, NodeUpdateEntity)
|
||||||
*/
|
*/
|
||||||
private boolean touchNodeImpl(Long nodeId, AuditablePropertiesEntity auditableProps)
|
private boolean touchNode(
|
||||||
|
Long nodeId, AuditablePropertiesEntity auditableProps,
|
||||||
|
boolean invalidateNodeAspectsCache,
|
||||||
|
boolean invalidateNodePropertiesCache,
|
||||||
|
boolean invalidateParentAssocsCache)
|
||||||
{
|
{
|
||||||
Node node = null;
|
Node node = null;
|
||||||
try
|
try
|
||||||
@@ -1433,11 +1429,44 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// We do nothing w.r.t. touching
|
// We do nothing w.r.t. touching
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeUpdateEntity nodeUpdate = new NodeUpdateEntity();
|
NodeUpdateEntity nodeUpdate = new NodeUpdateEntity();
|
||||||
nodeUpdate.setId(nodeId);
|
nodeUpdate.setId(nodeId);
|
||||||
nodeUpdate.setAuditableProperties(auditableProps);
|
nodeUpdate.setAuditableProperties(auditableProps);
|
||||||
// Update it
|
// Update it
|
||||||
return updateNodeImpl(node, nodeUpdate);
|
boolean updatedNode = updateNodeImpl(node, nodeUpdate);
|
||||||
|
// Handle the cache invalidation requests
|
||||||
|
Node newNode = getNodeNotNull(nodeId);
|
||||||
|
NodeVersionKey nodeVersionKey = node.getNodeVersionKey();
|
||||||
|
if (updatedNode)
|
||||||
|
{
|
||||||
|
NodeVersionKey newNodeVersionKey = newNode.getNodeVersionKey();
|
||||||
|
// The version will have moved on, effectively rendering our caches invalid.
|
||||||
|
// Copy over caches that DON'T need invalidating
|
||||||
|
if (!invalidateNodeAspectsCache)
|
||||||
|
{
|
||||||
|
copyNodeAspectsCached(nodeVersionKey, newNodeVersionKey);
|
||||||
|
}
|
||||||
|
if (!invalidateNodePropertiesCache)
|
||||||
|
{
|
||||||
|
copyNodePropertiesCached(nodeVersionKey, newNodeVersionKey);
|
||||||
|
}
|
||||||
|
if (!invalidateParentAssocsCache)
|
||||||
|
{
|
||||||
|
copyParentAssocsCached(nodeVersionKey, newNodeVersionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The node was not touched. By definition it MUST be in the current transaction.
|
||||||
|
// We invalidate the caches as specifically requested
|
||||||
|
invalidateNodeCaches(
|
||||||
|
nodeVersionKey,
|
||||||
|
invalidateNodeAspectsCache,
|
||||||
|
invalidateNodePropertiesCache,
|
||||||
|
invalidateParentAssocsCache);
|
||||||
|
}
|
||||||
|
return updatedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1563,11 +1592,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Update the caches
|
// Update the caches
|
||||||
nodeUpdate.lock();
|
nodeUpdate.lock();
|
||||||
nodesCache.setValue(nodeId, nodeUpdate);
|
nodesCache.setValue(nodeId, nodeUpdate);
|
||||||
if (nodeUpdate.isUpdateTypeQNameId() || nodeUpdate.isUpdateDeleted())
|
// The node's version has moved on so no need to invalidate caches
|
||||||
{
|
// TODO: Should we copy values between the cache keys?
|
||||||
// The association references will all be wrong
|
|
||||||
invalidateCachesByNodeId(nodeId, nodeId, parentAssocsCache);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
@@ -1602,13 +1628,40 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
primaryParentNodeId,
|
primaryParentNodeId,
|
||||||
optionalOldSharedAlcIdInAdditionToNull,
|
optionalOldSharedAlcIdInAdditionToNull,
|
||||||
newSharedAclId);
|
newSharedAclId);
|
||||||
invalidateCachesByNodeId(primaryParentNodeId, null, nodesCache);
|
invalidateNodeChildrenCaches(primaryParentNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteNode(Long nodeId)
|
public void deleteNode(Long nodeId)
|
||||||
{
|
{
|
||||||
Node node = getNodeNotNull(nodeId);
|
Node node = getNodeNotNull(nodeId);
|
||||||
Long aclId = node.getAclId(); // Need this later
|
// Gather data for later
|
||||||
|
Long aclId = node.getAclId();
|
||||||
|
Set<QName> nodeAspects = getNodeAspects(nodeId);
|
||||||
|
|
||||||
|
// Finally mark the node as deleted
|
||||||
|
NodeUpdateEntity nodeUpdate = new NodeUpdateEntity();
|
||||||
|
nodeUpdate.setId(nodeId);
|
||||||
|
// ACL
|
||||||
|
nodeUpdate.setAclId(null);
|
||||||
|
nodeUpdate.setUpdateAclId(true);
|
||||||
|
// Deleted
|
||||||
|
nodeUpdate.setDeleted(true);
|
||||||
|
nodeUpdate.setUpdateDeleted(true);
|
||||||
|
// Use a 'deleted' type QName
|
||||||
|
Long deletedQNameId = qnameDAO.getOrCreateQName(ContentModel.TYPE_DELETED).getFirst();
|
||||||
|
nodeUpdate.setTypeQNameId(deletedQNameId);
|
||||||
|
nodeUpdate.setUpdateTypeQNameId(true);
|
||||||
|
|
||||||
|
boolean updated = updateNodeImpl(node, nodeUpdate);
|
||||||
|
if (!updated)
|
||||||
|
{
|
||||||
|
invalidateNodeCaches(nodeId);
|
||||||
|
// Should not be attempting to delete a deleted node
|
||||||
|
throw new ConcurrencyFailureException(
|
||||||
|
"Failed to delete an existing live node: \n" +
|
||||||
|
" Before: " + node + "\n" +
|
||||||
|
" Update: " + nodeUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up content data
|
// Clean up content data
|
||||||
Set<QName> contentQNames = new HashSet<QName>(dictionaryService.getAllProperties(DataTypeDefinition.CONTENT));
|
Set<QName> contentQNames = new HashSet<QName>(dictionaryService.getAllProperties(DataTypeDefinition.CONTENT));
|
||||||
@@ -1618,76 +1671,24 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Delete content usage deltas
|
// Delete content usage deltas
|
||||||
usageDAO.deleteDeltas(nodeId);
|
usageDAO.deleteDeltas(nodeId);
|
||||||
|
|
||||||
// Finally mark the node as deleted
|
|
||||||
NodeUpdateEntity nodeUpdate = new NodeUpdateEntity();
|
|
||||||
nodeUpdate.setId(nodeId);
|
|
||||||
// Version
|
|
||||||
nodeUpdate.setVersion(node.getVersion());
|
|
||||||
// Transaction
|
|
||||||
TransactionEntity txn = getCurrentTransaction();
|
|
||||||
nodeUpdate.setTransaction(txn);
|
|
||||||
nodeUpdate.setUpdateTransaction(true);
|
|
||||||
// ACL
|
|
||||||
nodeUpdate.setAclId(null);
|
|
||||||
nodeUpdate.setUpdateAclId(true);
|
|
||||||
// Deleted
|
|
||||||
nodeUpdate.setDeleted(true);
|
|
||||||
nodeUpdate.setUpdateDeleted(true);
|
|
||||||
// Use a 'deleted' type QName
|
|
||||||
Long deletedQNameId = qnameDAO.getOrCreateQName(ContentModel.TYPE_DELETED).getFirst();
|
|
||||||
nodeUpdate.setUpdateTypeQNameId(true);
|
|
||||||
nodeUpdate.setTypeQNameId(deletedQNameId);
|
|
||||||
|
|
||||||
// Update cm:auditable
|
|
||||||
Set<QName> nodeAspects = getNodeAspects(nodeId);
|
|
||||||
if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE))
|
|
||||||
{
|
|
||||||
AuditablePropertiesEntity auditableProps = node.getAuditableProperties();
|
|
||||||
if (auditableProps == null)
|
|
||||||
{
|
|
||||||
auditableProps = new AuditablePropertiesEntity();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auditableProps = new AuditablePropertiesEntity(auditableProps); // Create unlocked instance
|
|
||||||
}
|
|
||||||
auditableProps.setAuditValues(null, null, false, 1000L);
|
|
||||||
nodeUpdate.setAuditableProperties(auditableProps);
|
|
||||||
nodeUpdate.setUpdateAuditableProperties(true);
|
|
||||||
}
|
|
||||||
if (nodeAspects.contains(ContentModel.ASPECT_ROOT))
|
if (nodeAspects.contains(ContentModel.ASPECT_ROOT))
|
||||||
{
|
{
|
||||||
allRootNodesCache.remove(node.getNodePair().getSecond().getStoreRef());
|
allRootNodesCache.remove(node.getNodePair().getSecond().getStoreRef());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove aspects
|
// Remove aspects
|
||||||
deleteNodeAspects(nodeId, null);
|
deleteNodeAspects(nodeId, null);
|
||||||
setNodeAspectsCached(nodeId, Collections.<QName>emptySet());
|
|
||||||
|
|
||||||
// Remove properties
|
// Remove properties
|
||||||
deleteNodeProperties(nodeId, (Set<Long>) null);
|
deleteNodeProperties(nodeId, (Set<Long>) null);
|
||||||
setNodePropertiesCached(nodeId, Collections.<QName,Serializable>emptyMap());
|
|
||||||
|
|
||||||
// Remove associations
|
// Remove associations
|
||||||
invalidateCachesByNodeId(nodeId, nodeId, parentAssocsCache);
|
|
||||||
deleteNodeAssocsToAndFrom(nodeId);
|
deleteNodeAssocsToAndFrom(nodeId);
|
||||||
deleteChildAssocsToAndFrom(nodeId);
|
deleteChildAssocsToAndFrom(nodeId);
|
||||||
|
|
||||||
// Remove subscriptions
|
// Remove subscriptions
|
||||||
deleteSubscriptions(nodeId);
|
deleteSubscriptions(nodeId);
|
||||||
|
|
||||||
int count = updateNode(nodeUpdate);
|
|
||||||
if (count != 1)
|
|
||||||
{
|
|
||||||
// Drop cached values in case of stale cache data
|
|
||||||
nodesCache.removeByValue(node);
|
|
||||||
|
|
||||||
throw new ConcurrencyFailureException("Failed to update node: " + nodeUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove value from the cache
|
|
||||||
nodesCache.removeByKey(nodeId);
|
|
||||||
|
|
||||||
// Remove ACLs
|
// Remove ACLs
|
||||||
if (aclId != null)
|
if (aclId != null)
|
||||||
{
|
{
|
||||||
@@ -2068,8 +2069,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
Map<QName, Serializable> cachedProps = getNodePropertiesCached(nodeId);
|
Map<QName, Serializable> cachedProps = getNodePropertiesCached(nodeId);
|
||||||
cachedProps.keySet().removeAll(propertyQNames);
|
cachedProps.keySet().removeAll(propertyQNames);
|
||||||
setNodePropertiesCached(nodeId, cachedProps);
|
setNodePropertiesCached(nodeId, cachedProps);
|
||||||
// Touch to bring into current txn
|
// Touch the node; all caches are fine
|
||||||
touchNodeImpl(nodeId);
|
touchNode(nodeId, null, false, false, false);
|
||||||
}
|
}
|
||||||
// Done
|
// Done
|
||||||
return deleteCount > 0;
|
return deleteCount > 0;
|
||||||
@@ -2106,8 +2107,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
|
policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||||
// Send this to the node update
|
// Touch the node; all caches are fine
|
||||||
return touchNodeImpl(nodeId, auditableProps);
|
return touchNode(nodeId, auditableProps, false, false, false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -2153,6 +2154,18 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
propertiesCache.setValue(nodeVersionKey, Collections.unmodifiableMap(properties));
|
propertiesCache.setValue(nodeVersionKey, Collections.unmodifiableMap(properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to copy cache values from one key to another
|
||||||
|
*/
|
||||||
|
private void copyNodePropertiesCached(NodeVersionKey from, NodeVersionKey to)
|
||||||
|
{
|
||||||
|
Map<QName, Serializable> cacheEntry = propertiesCache.getValue(from);
|
||||||
|
if (cacheEntry != null)
|
||||||
|
{
|
||||||
|
propertiesCache.setValue(to, cacheEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shallow-copies to a new map except for maps and collections that are binary serialized
|
* Shallow-copies to a new map except for maps and collections that are binary serialized
|
||||||
*/
|
*/
|
||||||
@@ -2285,72 +2298,67 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
{
|
{
|
||||||
executeBatch();
|
executeBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually update the cache
|
// Manually update the cache
|
||||||
Set<QName> newAspectQNames = new HashSet<QName>(existingAspectQNames);
|
Set<QName> newAspectQNames = new HashSet<QName>(existingAspectQNames);
|
||||||
newAspectQNames.addAll(aspectQNamesToAdd);
|
newAspectQNames.addAll(aspectQNamesToAdd);
|
||||||
setNodeAspectsCached(nodeId, newAspectQNames);
|
setNodeAspectsCached(nodeId, newAspectQNames);
|
||||||
|
|
||||||
// If we are adding the sys:aspect_root, then the parent assocs cache is unreliable
|
if (aspectQNamesToAdd.contains(ContentModel.ASPECT_ROOT))
|
||||||
if (newAspectQNames.contains(ContentModel.ASPECT_ROOT))
|
|
||||||
{
|
{
|
||||||
Pair <Long, NodeRef> nodePair = getNodePair(nodeId);
|
// This is a special case. The presence of the aspect affects the path
|
||||||
invalidateCachesByNodeId(null, nodeId, parentAssocsCache);
|
// calculations, which are stored in the parent assocs cache
|
||||||
allRootNodesCache.remove(nodePair.getSecond().getStoreRef());
|
touchNode(nodeId, null, false, false, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(nodeId, null, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch to bring into current txn
|
|
||||||
touchNodeImpl(nodeId);
|
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeNodeAspects(Long nodeId)
|
public boolean removeNodeAspects(Long nodeId)
|
||||||
{
|
{
|
||||||
// Get existing
|
|
||||||
Set<QName> existingAspectQNames = getNodeAspectsCached(nodeId);
|
|
||||||
// If we are removing the sys:aspect_root, then the parent assocs cache is unreliable
|
|
||||||
if (existingAspectQNames.contains(ContentModel.ASPECT_ROOT))
|
|
||||||
{
|
|
||||||
invalidateCachesByNodeId(null, nodeId, parentAssocsCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just delete all the node's aspects
|
// Just delete all the node's aspects
|
||||||
int deleteCount = deleteNodeAspects(nodeId, null);
|
int deleteCount = deleteNodeAspects(nodeId, null);
|
||||||
// Touch to bring into current txn
|
|
||||||
touchNodeImpl(nodeId);
|
|
||||||
|
|
||||||
// Manually update the cache
|
// Manually update the cache
|
||||||
setNodeAspectsCached(nodeId, Collections.<QName>emptySet());
|
setNodeAspectsCached(nodeId, Collections.<QName>emptySet());
|
||||||
|
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(nodeId, null, false, false, false);
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return deleteCount > 0;
|
return deleteCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeNodeAspects(Long nodeId, Set<QName> aspectQNames)
|
public boolean removeNodeAspects(Long nodeId, Set<QName> aspectQNames)
|
||||||
{
|
{
|
||||||
|
if (aspectQNames.size() == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Get the current aspects
|
// Get the current aspects
|
||||||
Set<QName> existingAspectQNames = getNodeAspects(nodeId);
|
Set<QName> existingAspectQNames = getNodeAspects(nodeId);
|
||||||
// Now remove each aspect
|
// Now remove each aspect
|
||||||
Set<Long> aspectQNameIdsToRemove = qnameDAO.convertQNamesToIds(aspectQNames, false);
|
Set<Long> aspectQNameIdsToRemove = qnameDAO.convertQNamesToIds(aspectQNames, false);
|
||||||
int deleteCount = deleteNodeAspects(nodeId, aspectQNameIdsToRemove);
|
int deleteCount = deleteNodeAspects(nodeId, aspectQNameIdsToRemove);
|
||||||
|
if (deleteCount == 0)
|
||||||
// If we are removing the sys:aspect_root, then the parent assocs cache is unreliable
|
|
||||||
if (aspectQNames.contains(ContentModel.ASPECT_ROOT))
|
|
||||||
{
|
{
|
||||||
Pair <Long, NodeRef> nodePair = getNodePair(nodeId);
|
return false;
|
||||||
invalidateCachesByNodeId(null, nodeId, parentAssocsCache);
|
|
||||||
allRootNodesCache.remove(nodePair.getSecond().getStoreRef());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch to bring into current txn
|
|
||||||
touchNodeImpl(nodeId);
|
|
||||||
|
|
||||||
// Manually update the cache
|
// Manually update the cache
|
||||||
Set<QName> newAspectQNames = new HashSet<QName>(existingAspectQNames);
|
Set<QName> newAspectQNames = new HashSet<QName>(existingAspectQNames);
|
||||||
newAspectQNames.removeAll(aspectQNames);
|
newAspectQNames.removeAll(aspectQNames);
|
||||||
setNodeAspectsCached(nodeId, newAspectQNames);
|
setNodeAspectsCached(nodeId, newAspectQNames);
|
||||||
|
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(nodeId, null, false, false, false);
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return deleteCount > 0;
|
return deleteCount > 0;
|
||||||
}
|
}
|
||||||
@@ -2394,6 +2402,18 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
aspectsCache.setValue(nodeVersionKey, Collections.unmodifiableSet(aspects));
|
aspectsCache.setValue(nodeVersionKey, Collections.unmodifiableSet(aspects));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to copy cache values from one key to another
|
||||||
|
*/
|
||||||
|
private void copyNodeAspectsCached(NodeVersionKey from, NodeVersionKey to)
|
||||||
|
{
|
||||||
|
Set<QName> cacheEntry = aspectsCache.getValue(from);
|
||||||
|
if (cacheEntry != null)
|
||||||
|
{
|
||||||
|
aspectsCache.setValue(to, cacheEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to cache node aspects. The DAO callback only does the simple {@link #findByKey(Long)}.
|
* Callback to cache node aspects. The DAO callback only does the simple {@link #findByKey(Long)}.
|
||||||
*
|
*
|
||||||
@@ -2447,8 +2467,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
throw new IllegalArgumentException("Index is 1-based, or -1 to indicate 'next value'.");
|
throw new IllegalArgumentException("Index is 1-based, or -1 to indicate 'next value'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch to bring into current txn and ensure concurrency is maintained on the nodes
|
// Touch the node; all caches are fine
|
||||||
touchNodeImpl(sourceNodeId);
|
touchNode(sourceNodeId, null, false, false, false);
|
||||||
|
|
||||||
// Resolve type QName
|
// Resolve type QName
|
||||||
Long assocTypeQNameId = qnameDAO.getOrCreateQName(assocTypeQName).getFirst();
|
Long assocTypeQNameId = qnameDAO.getOrCreateQName(assocTypeQName).getFirst();
|
||||||
@@ -2493,19 +2513,26 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Never existed
|
// Never existed
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Touch to bring into current txn
|
|
||||||
touchNodeImpl(sourceNodeId);
|
|
||||||
|
|
||||||
Long assocTypeQNameId = assocTypeQNamePair.getFirst();
|
Long assocTypeQNameId = assocTypeQNamePair.getFirst();
|
||||||
return deleteNodeAssoc(sourceNodeId, targetNodeId, assocTypeQNameId);
|
int deleted = deleteNodeAssoc(sourceNodeId, targetNodeId, assocTypeQNameId);
|
||||||
|
if (deleted > 0)
|
||||||
|
{
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(sourceNodeId, null, false, false, false);
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int removeNodeAssocsToAndFrom(Long nodeId)
|
public int removeNodeAssocsToAndFrom(Long nodeId)
|
||||||
{
|
{
|
||||||
// Touch to bring into current txn
|
int deleted = deleteNodeAssocsToAndFrom(nodeId);
|
||||||
touchNodeImpl(nodeId);
|
if (deleted > 0)
|
||||||
|
{
|
||||||
return deleteNodeAssocsToAndFrom(nodeId);
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(nodeId, null, false, false, false);
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int removeNodeAssocsToAndFrom(Long nodeId, Set<QName> assocTypeQNames)
|
public int removeNodeAssocsToAndFrom(Long nodeId, Set<QName> assocTypeQNames)
|
||||||
@@ -2516,10 +2543,14 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Never existed
|
// Never existed
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Touch to bring into current txn
|
|
||||||
touchNodeImpl(nodeId);
|
|
||||||
|
|
||||||
return deleteNodeAssocsToAndFrom(nodeId, assocTypeQNameIds);
|
int deleted = deleteNodeAssocsToAndFrom(nodeId, assocTypeQNameIds);
|
||||||
|
if (deleted > 0)
|
||||||
|
{
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(nodeId, null, false, false, false);
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -2704,9 +2735,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
String childNodeName)
|
String childNodeName)
|
||||||
{
|
{
|
||||||
ParentAssocsInfo parentAssocInfo = getParentAssocsCached(childNodeId);
|
ParentAssocsInfo parentAssocInfo = getParentAssocsCached(childNodeId);
|
||||||
|
// Create it
|
||||||
ChildAssocEntity assoc = newChildAssocImpl(
|
ChildAssocEntity assoc = newChildAssocImpl(
|
||||||
parentNodeId, childNodeId, false, assocTypeQName, assocQName, childNodeName);
|
parentNodeId, childNodeId, false, assocTypeQName, assocQName, childNodeName);
|
||||||
Long assocId = assoc.getId();
|
Long assocId = assoc.getId();
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(childNodeId, null, false, false, false);
|
||||||
// update cache
|
// update cache
|
||||||
parentAssocInfo = parentAssocInfo.addAssoc(assocId, assoc, getCurrentTransactionId());
|
parentAssocInfo = parentAssocInfo.addAssoc(assocId, assoc, getCurrentTransactionId());
|
||||||
setParentAssocsCached(childNodeId, parentAssocInfo);
|
setParentAssocsCached(childNodeId, parentAssocInfo);
|
||||||
@@ -2724,14 +2758,17 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Update cache
|
// Update cache
|
||||||
Long childNodeId = assoc.getChildNode().getId();
|
Long childNodeId = assoc.getChildNode().getId();
|
||||||
ParentAssocsInfo parentAssocInfo = getParentAssocsCached(childNodeId);
|
ParentAssocsInfo parentAssocInfo = getParentAssocsCached(childNodeId);
|
||||||
parentAssocInfo = parentAssocInfo.removeAssoc(assocId, getCurrentTransactionId());
|
|
||||||
setParentAssocsCached(childNodeId, parentAssocInfo);
|
|
||||||
// Delete it
|
// Delete it
|
||||||
int count = deleteChildAssocById(assocId);
|
int count = deleteChildAssocById(assocId);
|
||||||
if (count != 1)
|
if (count != 1)
|
||||||
{
|
{
|
||||||
throw new ConcurrencyFailureException("Child association not deleted: " + assocId);
|
throw new ConcurrencyFailureException("Child association not deleted: " + assocId);
|
||||||
}
|
}
|
||||||
|
// Touch the node; all caches are fine
|
||||||
|
touchNode(childNodeId, null, false, false, false);
|
||||||
|
// Update cache
|
||||||
|
parentAssocInfo = parentAssocInfo.removeAssoc(assocId, getCurrentTransactionId());
|
||||||
|
setParentAssocsCached(childNodeId, parentAssocInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int setChildAssocIndex(Long parentNodeId, Long childNodeId, QName assocTypeQName, QName assocQName, int index)
|
public int setChildAssocIndex(Long parentNodeId, Long childNodeId, QName assocTypeQName, QName assocQName, int index)
|
||||||
@@ -2739,7 +2776,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
int count = updateChildAssocIndex(parentNodeId, childNodeId, assocTypeQName, assocQName, index);
|
int count = updateChildAssocIndex(parentNodeId, childNodeId, assocTypeQName, assocQName, index);
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
invalidateCachesByNodeId(null, childNodeId, parentAssocsCache);
|
// Touch the node; parent assocs are out of sync
|
||||||
|
touchNode(childNodeId, null, false, false, true);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@@ -2771,7 +2809,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
Integer count = childAssocRetryingHelper.doWithRetry(callback);
|
Integer count = childAssocRetryingHelper.doWithRetry(callback);
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
invalidateCachesByNodeId(null, childNodeId, parentAssocsCache);
|
// Touch the node; parent assocs are out of sync
|
||||||
|
touchNode(childNodeId, null, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDebugEnabled)
|
if (isDebugEnabled)
|
||||||
@@ -2951,15 +2990,27 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Take out the reverse-entity lookup, which is broken for non-primary assocs
|
||||||
public Pair<Long, ChildAssociationRef> getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName)
|
public Pair<Long, ChildAssociationRef> getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName)
|
||||||
{
|
{
|
||||||
ChildByNameKey valueKey = new ChildByNameKey(parentNodeId, assocTypeQName, childName);
|
ChildByNameKey valueKey = new ChildByNameKey(parentNodeId, assocTypeQName, childName);
|
||||||
|
|
||||||
// cache-only operation: try reverse lookup on parentAssocs (note: for primary assoc only)
|
// cache-only operation: try reverse lookup on parentAssocs (note: for primary assoc only)
|
||||||
Long childNodeId = parentAssocsCache.getKey(valueKey);
|
NodeVersionKey childNodeVersionKey = parentAssocsCache.getKey(valueKey);
|
||||||
if (childNodeId != null)
|
if (childNodeVersionKey != null)
|
||||||
{
|
{
|
||||||
Pair<Long, ParentAssocsInfo> value = parentAssocsCache.getByKey(childNodeId);
|
Long childNodeId = childNodeVersionKey.getNodeId();
|
||||||
|
NodeVersionKey nodeVersionKeyInCache = getNodeNotNull(childNodeId).getNodeVersionKey();
|
||||||
|
if (!nodeVersionKeyInCache.equals(childNodeVersionKey))
|
||||||
|
{
|
||||||
|
// The child node linked in the cache does not match our current node entry
|
||||||
|
invalidateNodeCaches(childNodeId);
|
||||||
|
throw new ConcurrencyFailureException(
|
||||||
|
"Child node found in parent-child lookup does not match current node entry: \n" +
|
||||||
|
" Child node from parentAssocsCache: " + childNodeVersionKey + "\n" +
|
||||||
|
" Child node from nodeCache: " + nodeVersionKeyInCache);
|
||||||
|
}
|
||||||
|
Pair<NodeVersionKey, ParentAssocsInfo> value = parentAssocsCache.getByKey(childNodeVersionKey);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
ChildAssocEntity assoc = value.getSecond().getPrimaryParentAssoc();
|
ChildAssocEntity assoc = value.getSecond().getPrimaryParentAssoc();
|
||||||
@@ -2980,8 +3031,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
ChildAssocEntity assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName);
|
ChildAssocEntity assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName);
|
||||||
if (assoc != null)
|
if (assoc != null)
|
||||||
{
|
{
|
||||||
|
childNodeVersionKey = assoc.getChildNode().getNodeVersionKey();
|
||||||
// additional lookup to populate cache - note: also pulls in 2ndary assocs
|
// additional lookup to populate cache - note: also pulls in 2ndary assocs
|
||||||
parentAssocsCache.getByKey(assoc.getChildNode().getId());
|
parentAssocsCache.getByKey(childNodeVersionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return assoc == null ? null : assoc.getPair(qnameDAO);
|
return assoc == null ? null : assoc.getPair(qnameDAO);
|
||||||
@@ -3387,51 +3439,14 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
*/
|
*/
|
||||||
private ParentAssocsInfo getParentAssocsCached(Long nodeId)
|
private ParentAssocsInfo getParentAssocsCached(Long nodeId)
|
||||||
{
|
{
|
||||||
// We try to protect here against 'skew' between a cached node and its parent associations
|
NodeVersionKey nodeVersionKey = getNodeNotNull(nodeId).getNodeVersionKey();
|
||||||
// Unfortunately due to overlapping DB transactions and consistent read behaviour a thread
|
Pair<NodeVersionKey, ParentAssocsInfo> cacheEntry = parentAssocsCache.getByKey(nodeVersionKey);
|
||||||
// can end up loading old associations and succeed in committing them to the shared cache
|
if (cacheEntry == null)
|
||||||
// without any conflicts
|
|
||||||
|
|
||||||
// Allow for a single retry after cache validation
|
|
||||||
for (int i = 0; i < 2; i++)
|
|
||||||
{
|
{
|
||||||
Pair<Long, ParentAssocsInfo> cacheEntry = parentAssocsCache.getByKey(nodeId);
|
invalidateNodeCaches(nodeId);
|
||||||
if (cacheEntry == null)
|
throw new DataIntegrityViolationException("Invalid node ID: " + nodeId);
|
||||||
{
|
|
||||||
throw new DataIntegrityViolationException("Invalid node ID: " + nodeId);
|
|
||||||
}
|
|
||||||
Node child = getNodeNotNull(nodeId);
|
|
||||||
ParentAssocsInfo parentAssocsInfo = cacheEntry.getSecond();
|
|
||||||
// Validate that we aren't pairing up a cached node with historic parent associations from an old
|
|
||||||
// transaction (or the other way around)
|
|
||||||
Long txnId = parentAssocsInfo.getTxnId();
|
|
||||||
Long childTxnId = child.getTransaction().getId();
|
|
||||||
if (txnId != null && !txnId.equals(childTxnId))
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("Stale cached node #" + nodeId
|
|
||||||
+ " detected loading parent associations. Cached transaction ID: "
|
|
||||||
+ child.getTransaction().getId() + ", actual transaction ID: " + txnId);
|
|
||||||
}
|
|
||||||
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE
|
|
||||||
|| !getCurrentTransaction().getId().equals(childTxnId))
|
|
||||||
{
|
|
||||||
// Force a reload of the node and its parent assocs
|
|
||||||
invalidateNodeCaches(nodeId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The node is for the current transaction, so only invalidate the parent assocs
|
|
||||||
invalidateCachesByNodeId(null, nodeId, parentAssocsCache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return parentAssocsInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw new DataIntegrityViolationException("Stale cache detected for Node #" + nodeId);
|
return cacheEntry.getSecond();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3439,7 +3454,20 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
*/
|
*/
|
||||||
private void setParentAssocsCached(Long nodeId, ParentAssocsInfo parentAssocs)
|
private void setParentAssocsCached(Long nodeId, ParentAssocsInfo parentAssocs)
|
||||||
{
|
{
|
||||||
parentAssocsCache.setValue(nodeId, parentAssocs);
|
NodeVersionKey nodeVersionKey = getNodeNotNull(nodeId).getNodeVersionKey();
|
||||||
|
parentAssocsCache.setValue(nodeVersionKey, parentAssocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to copy cache values from one key to another
|
||||||
|
*/
|
||||||
|
private void copyParentAssocsCached(NodeVersionKey from, NodeVersionKey to)
|
||||||
|
{
|
||||||
|
ParentAssocsInfo cacheEntry = parentAssocsCache.getValue(from);
|
||||||
|
if (cacheEntry != null)
|
||||||
|
{
|
||||||
|
parentAssocsCache.setValue(to, cacheEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3448,15 +3476,16 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
* @author Derek Hulley
|
* @author Derek Hulley
|
||||||
* @since 3.4
|
* @since 3.4
|
||||||
*/
|
*/
|
||||||
private class ParentAssocsCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, ParentAssocsInfo, ChildByNameKey>
|
private class ParentAssocsCallbackDAO extends EntityLookupCallbackDAOAdaptor<NodeVersionKey, ParentAssocsInfo, ChildByNameKey>
|
||||||
{
|
{
|
||||||
public Pair<Long, ParentAssocsInfo> createValue(ParentAssocsInfo value)
|
public Pair<NodeVersionKey, ParentAssocsInfo> createValue(ParentAssocsInfo value)
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException("Nodes are created independently.");
|
throw new UnsupportedOperationException("Nodes are created independently.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<Long, ParentAssocsInfo> findByKey(Long nodeId)
|
public Pair<NodeVersionKey, ParentAssocsInfo> findByKey(NodeVersionKey nodeVersionKey)
|
||||||
{
|
{
|
||||||
|
Long nodeId = nodeVersionKey.getNodeId();
|
||||||
// Find out if it is a root or store root
|
// Find out if it is a root or store root
|
||||||
boolean isRoot = hasNodeAspect(nodeId, ContentModel.ASPECT_ROOT);
|
boolean isRoot = hasNodeAspect(nodeId, ContentModel.ASPECT_ROOT);
|
||||||
boolean isStoreRoot = getNodeType(nodeId).equals(ContentModel.TYPE_STOREROOT);
|
boolean isStoreRoot = getNodeType(nodeId).equals(ContentModel.TYPE_STOREROOT);
|
||||||
@@ -3464,14 +3493,31 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Select all the parent associations
|
// Select all the parent associations
|
||||||
List<ChildAssocEntity> assocs = selectParentAssocs(nodeId);
|
List<ChildAssocEntity> assocs = selectParentAssocs(nodeId);
|
||||||
|
|
||||||
// Retrieve the transaction ID from the DB for validation purposes - prevents skew between a cached node and
|
|
||||||
// its parent assocs
|
|
||||||
Long txnId = assocs.isEmpty() ? null : assocs.get(0).getChildNode().getTransaction().getId();
|
|
||||||
|
|
||||||
// Build the cache object
|
// Build the cache object
|
||||||
ParentAssocsInfo value = new ParentAssocsInfo(txnId, isRoot, isStoreRoot, assocs);
|
ParentAssocsInfo value = new ParentAssocsInfo(isRoot, isStoreRoot, assocs);
|
||||||
|
|
||||||
|
// Now check if we are seeing the correct version of the node
|
||||||
|
if (assocs.isEmpty())
|
||||||
|
{
|
||||||
|
// No results. We can draw no conclusions.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChildAssocEntity childAssoc = assocs.get(0);
|
||||||
|
// What is the real (at least to this txn) version of the child node?
|
||||||
|
NodeVersionKey childNodeVersionKeyFromDb = childAssoc.getChildNode().getNodeVersionKey();
|
||||||
|
if (!childNodeVersionKeyFromDb.equals(nodeVersionKey))
|
||||||
|
{
|
||||||
|
// This method was called with a stale version
|
||||||
|
invalidateNodeCaches(nodeId);
|
||||||
|
throw new DataIntegrityViolationException(
|
||||||
|
"Detected stale node entry: " + nodeVersionKey +
|
||||||
|
" (now " + childNodeVersionKeyFromDb + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return new Pair<Long, ParentAssocsInfo>(nodeId, value);
|
return new Pair<NodeVersionKey, ParentAssocsInfo>(nodeVersionKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -3487,9 +3533,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<Long, ParentAssocsInfo> findByValue(ParentAssocsInfo value)
|
public Pair<NodeVersionKey, ParentAssocsInfo> findByValue(ParentAssocsInfo value)
|
||||||
{
|
{
|
||||||
return findByKey(value.getPrimaryParentAssoc().getChildNode().getId());
|
return findByKey(value.getPrimaryParentAssoc().getChildNode().getNodeVersionKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3750,10 +3796,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
*/
|
*/
|
||||||
public void clear()
|
public void clear()
|
||||||
{
|
{
|
||||||
nodesCache.clear();
|
clearCaches();
|
||||||
aspectsCache.clear();
|
|
||||||
propertiesCache.clear();
|
|
||||||
parentAssocsCache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -3789,44 +3832,6 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
List<NodeRef.Status> nodeStatuses = new ArrayList<NodeRef.Status>(nodes.size());
|
List<NodeRef.Status> nodeStatuses = new ArrayList<NodeRef.Status>(nodes.size());
|
||||||
for (NodeEntity node : nodes)
|
for (NodeEntity node : nodes)
|
||||||
{
|
{
|
||||||
Long nodeId = node.getId();
|
|
||||||
Node cached = nodesCache.getValue(nodeId);
|
|
||||||
ParentAssocsInfo cachedParents = parentAssocsCache.getValue(nodeId);
|
|
||||||
if (cached != null && !txnId.equals(cached.getTransaction().getId()))
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("Stale cached node #" + nodeId
|
|
||||||
+ " detected during transaction tracking. Cached transaction ID: "
|
|
||||||
+ cached.getTransaction().getId() + ", actual transaction ID: " + txnId);
|
|
||||||
}
|
|
||||||
invalidateNodeCaches(nodeId);
|
|
||||||
}
|
|
||||||
else if (cachedParents != null && !txnId.equals(cachedParents.getTxnId()))
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("Stale cached parent associations for node #" + nodeId
|
|
||||||
+ " detected during transaction tracking. Cached transaction ID: "
|
|
||||||
+ cachedParents.getTxnId() + ", actual transaction ID: " + txnId);
|
|
||||||
}
|
|
||||||
invalidateNodeCaches(nodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's possible that a noderef has been remapped (e.g. node moved store) so make sure we don't have a stale
|
|
||||||
// mapping for this noderef either
|
|
||||||
Long oldNodeId = nodesCache.getKey(node.getNodeRef());
|
|
||||||
if (oldNodeId != null && !(oldNodeId.equals(nodeId)))
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("Stale cached noderef " + node.getNodeRef()
|
|
||||||
+ " detected during transaction tracking. Cached node ID: "
|
|
||||||
+ oldNodeId + ", actual node ID: " + nodeId);
|
|
||||||
}
|
|
||||||
invalidateNodeCaches(oldNodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeStatuses.add(node.getNodeStatus());
|
nodeStatuses.add(node.getNodeStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,7 +44,6 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
private static Set<Long> warnedDuplicateParents = new HashSet<Long>(3);
|
private static Set<Long> warnedDuplicateParents = new HashSet<Long>(3);
|
||||||
|
|
||||||
private final Long txnId;
|
|
||||||
private final boolean isRoot;
|
private final boolean isRoot;
|
||||||
private final boolean isStoreRoot;
|
private final boolean isStoreRoot;
|
||||||
private final Long primaryAssocId;
|
private final Long primaryAssocId;
|
||||||
@@ -53,16 +52,15 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
/**
|
/**
|
||||||
* Constructor to provide clean initial version of a Node's parent association
|
* Constructor to provide clean initial version of a Node's parent association
|
||||||
*/
|
*/
|
||||||
ParentAssocsInfo(Long txnId, boolean isRoot, boolean isStoreRoot, ChildAssocEntity parent)
|
ParentAssocsInfo(boolean isRoot, boolean isStoreRoot, ChildAssocEntity parent)
|
||||||
{
|
{
|
||||||
this(txnId, isRoot, isStoreRoot, Collections.singletonList(parent));
|
this(isRoot, isStoreRoot, Collections.singletonList(parent));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Constructor to provide clean initial version of a Node's parent associations
|
* Constructor to provide clean initial version of a Node's parent associations
|
||||||
*/
|
*/
|
||||||
ParentAssocsInfo(Long txnId, boolean isRoot, boolean isStoreRoot, List<? extends ChildAssocEntity> parents)
|
ParentAssocsInfo(boolean isRoot, boolean isStoreRoot, List<? extends ChildAssocEntity> parents)
|
||||||
{
|
{
|
||||||
this.txnId = txnId;
|
|
||||||
this.isRoot = isRoot;
|
this.isRoot = isRoot;
|
||||||
this.isStoreRoot = isStoreRoot;
|
this.isStoreRoot = isStoreRoot;
|
||||||
Long primaryAssocId = null;
|
Long primaryAssocId = null;
|
||||||
@@ -107,13 +105,11 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
* Private constructor used to copy existing values.
|
* Private constructor used to copy existing values.
|
||||||
*/
|
*/
|
||||||
private ParentAssocsInfo(
|
private ParentAssocsInfo(
|
||||||
Long txnId,
|
|
||||||
boolean isRoot,
|
boolean isRoot,
|
||||||
boolean isStoreRoot,
|
boolean isStoreRoot,
|
||||||
Map<Long, ChildAssocEntity> parentAssocsById,
|
Map<Long, ChildAssocEntity> parentAssocsById,
|
||||||
Long primaryAssocId)
|
Long primaryAssocId)
|
||||||
{
|
{
|
||||||
this.txnId = txnId;
|
|
||||||
this.isRoot = isRoot;
|
this.isRoot = isRoot;
|
||||||
this.isStoreRoot = isStoreRoot;
|
this.isStoreRoot = isStoreRoot;
|
||||||
this.parentAssocsById = Collections.unmodifiableMap(parentAssocsById);
|
this.parentAssocsById = Collections.unmodifiableMap(parentAssocsById);
|
||||||
@@ -125,8 +121,7 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
{
|
{
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append("ParentAssocsInfo ")
|
builder.append("ParentAssocsInfo ")
|
||||||
.append("[txnId=").append(txnId)
|
.append("[isRoot=").append(isRoot)
|
||||||
.append(", isRoot=").append(isRoot)
|
|
||||||
.append(", isStoreRoot=").append(isStoreRoot)
|
.append(", isStoreRoot=").append(isStoreRoot)
|
||||||
.append(", parentAssocsById=").append(parentAssocsById)
|
.append(", parentAssocsById=").append(parentAssocsById)
|
||||||
.append(", primaryAssocId=").append(primaryAssocId)
|
.append(", primaryAssocId=").append(primaryAssocId)
|
||||||
@@ -134,11 +129,6 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getTxnId()
|
|
||||||
{
|
|
||||||
return txnId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRoot()
|
public boolean isRoot()
|
||||||
{
|
{
|
||||||
return isRoot;
|
return isRoot;
|
||||||
@@ -161,25 +151,25 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
public ParentAssocsInfo changeIsRoot(boolean isRoot, Long txnId)
|
public ParentAssocsInfo changeIsRoot(boolean isRoot, Long txnId)
|
||||||
{
|
{
|
||||||
return new ParentAssocsInfo(txnId, isRoot, this.isRoot, parentAssocsById, primaryAssocId);
|
return new ParentAssocsInfo(isRoot, this.isRoot, parentAssocsById, primaryAssocId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentAssocsInfo changeIsStoreRoot(boolean isStoreRoot, Long txnId)
|
public ParentAssocsInfo changeIsStoreRoot(boolean isStoreRoot, Long txnId)
|
||||||
{
|
{
|
||||||
return new ParentAssocsInfo(txnId, this.isRoot, isStoreRoot, parentAssocsById, primaryAssocId);
|
return new ParentAssocsInfo(this.isRoot, isStoreRoot, parentAssocsById, primaryAssocId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentAssocsInfo addAssoc(Long assocId, ChildAssocEntity parentAssoc, Long txnId)
|
public ParentAssocsInfo addAssoc(Long assocId, ChildAssocEntity parentAssoc, Long txnId)
|
||||||
{
|
{
|
||||||
Map<Long, ChildAssocEntity> parentAssocs = new HashMap<Long, ChildAssocEntity>(parentAssocsById);
|
Map<Long, ChildAssocEntity> parentAssocs = new HashMap<Long, ChildAssocEntity>(parentAssocsById);
|
||||||
parentAssocs.put(parentAssoc.getId(), parentAssoc);
|
parentAssocs.put(parentAssoc.getId(), parentAssoc);
|
||||||
return new ParentAssocsInfo(txnId, isRoot, isStoreRoot, parentAssocs, primaryAssocId);
|
return new ParentAssocsInfo(isRoot, isStoreRoot, parentAssocs, primaryAssocId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentAssocsInfo removeAssoc(Long assocId, Long txnId)
|
public ParentAssocsInfo removeAssoc(Long assocId, Long txnId)
|
||||||
{
|
{
|
||||||
Map<Long, ChildAssocEntity> parentAssocs = new HashMap<Long, ChildAssocEntity>(parentAssocsById);
|
Map<Long, ChildAssocEntity> parentAssocs = new HashMap<Long, ChildAssocEntity>(parentAssocsById);
|
||||||
parentAssocs.remove(assocId);
|
parentAssocs.remove(assocId);
|
||||||
return new ParentAssocsInfo(txnId, isRoot, isStoreRoot, parentAssocs, primaryAssocId);
|
return new ParentAssocsInfo(isRoot, isStoreRoot, parentAssocs, primaryAssocId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user