Merged HEAD-BUG-FIX (5.0/Cloud) to HEAD (5.0/Cloud)

83892: Merged FEATURE2 to HEAD-BUG-FIX (5.0)
      82450, 82478, 83318, 83442 : ACE-898 : Share uses "ModifiedBy" which is not always correct for folders
         - Propagate cm:modifier and cm:modified. Feature related test


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@84595 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2014-09-18 17:16:45 +00:00
parent d9363413c3
commit 03952f8551
5 changed files with 471 additions and 16 deletions

View File

@@ -2454,6 +2454,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
@Override
public boolean setModifiedDate(Long nodeId, Date modifiedDate)
{
return setModifiedProperties(nodeId, modifiedDate, null);
}
@Override
public boolean setModifiedProperties(Long nodeId, Date modifiedDate, String modifiedBy) {
// Do nothing if the node is not cm:auditable
if (!hasNodeAspect(nodeId, ContentModel.ASPECT_AUDITABLE))
{
@@ -2469,13 +2474,17 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
{
// The properties should be present
auditableProps = new AuditablePropertiesEntity();
auditableProps.setAuditValues(null, modifiedDate, true, 1000L);
auditableProps.setAuditValues(modifiedBy, modifiedDate, true, 1000L);
dateChanged = true;
}
else
{
auditableProps = new AuditablePropertiesEntity(auditableProps);
dateChanged = auditableProps.setAuditModified(modifiedDate, 1000L);
if (dateChanged)
{
auditableProps.setAuditModifier(modifiedBy);
}
}
if (dateChanged)
{

View File

@@ -354,9 +354,22 @@ public interface NodeDAO extends NodeBulkLoader
* @param nodeId the node to change
* @param modifiedDate the date to set for <b>cm:modified</b>
* @return Returns <tt>true</tt> if the <b>cm:modified</b> property was actually set
* @deprecated Use {@link #setModifiedProperties(Long, Date, String)} to also change the <b>cm:modifier</b> property
*/
public boolean setModifiedDate(Long nodeId, Date date);
/**
* Pull the <b>cm:modified</b> up to the current time without changing any other
* <b>cm:auditable</b> properties. The change may be done in the current transaction
* or in a later transaction.
*
* @param nodeId the node to change
* @param modifiedDate the date to set for <b>cm:modified</b>
* @param modifiedBy the name to set for <b>cm:modifier</b>
* @return Returns <tt>true</tt> if the <b>cm:modified</b> and <b>cm:modifier</b> properties were actually set
*/
public boolean setModifiedProperties(Long nodeId, Date modifiedDate, String modifiedBy);
/*
* Aspects
*/

View File

@@ -2913,7 +2913,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
/**
* Propagate, if necessary, a <b>cm:modified</b> timestamp change to the parent of the
* given association. The parent node has to be <b>cm:auditable</b> and the association
* given association, along with the <b>cm:modifier</b> of who changed it.
* The parent node has to be <b>cm:auditable</b> and the association
* has to be marked for propagation as well.
*
* @param assocRef the association to propagate along
@@ -2928,44 +2929,79 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
AssociationDefinition assocDef = dictionaryService.getAssociation(assocRef.getTypeQName());
if (assocDef == null || !assocDef.isChild())
{
if (logger.isDebugEnabled())
{
logger.debug("Not propagating cm:auditable for unknown association type " + assocRef.getTypeQName());
}
return;
}
ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef;
if (!childAssocDef.getPropagateTimestamps())
{
if (logger.isDebugEnabled())
{
logger.debug("Not propagating cm:auditable for association type " + childAssocDef.getName());
}
return;
}
// The dictionary says propagate. Now get the parent node and prompt the touch.
NodeRef parentNodeRef = assocRef.getParentRef();
// Do not propagate if the cm:auditable behaviour is off
if (!policyBehaviourFilter.isEnabled(parentNodeRef, ContentModel.ASPECT_AUDITABLE))
{
if (logger.isDebugEnabled())
{
logger.debug("Not propagating cm:auditable for non-auditable parent on " + assocRef);
}
return;
}
Pair<Long, NodeRef> parentNodePair = getNodePairNotNull(parentNodeRef);
Long parentNodeId = parentNodePair.getFirst();
// Get the ID of the child that triggered this update
NodeRef childNodeRef = assocRef.getChildRef();
Pair<Long, NodeRef> childNodePair = getNodePairNotNull(childNodeRef);
Long childNodeId = childNodePair.getFirst();
// If we have already modified a particular parent node in the current txn,
// it is not necessary to start a new transaction to tweak the cm:modified date.
// But if the parent node was NOT touched, then doing so in this transaction would
// create excessive concurrency and retries; in latter case we defer to a small,
// post-commit isolated transaction.
if (TransactionalResourceHelper.getSet(KEY_AUDITABLE_PROPAGATION_PRE).contains(parentNodeId))
if (TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION_PRE).containsKey(parentNodeId))
{
// It is already registered in the current transaction.
// Modified By will be taken from the previous node to touch it
if (logger.isDebugEnabled())
{
logger.debug("Update of cm:auditable already requested for " + parentNodePair);
}
return;
}
if (nodeDAO.isInCurrentTxn(parentNodeId))
{
// The parent and child are in the same transaction
TransactionalResourceHelper.getSet(KEY_AUDITABLE_PROPAGATION_PRE).add(parentNodeId);
TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION_PRE).put(parentNodeId, childNodeId);
// Make sure that it is not processed after the transaction
TransactionalResourceHelper.getSet(KEY_AUDITABLE_PROPAGATION_POST).remove(parentNodeId);
TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION_POST).remove(parentNodeId);
if (logger.isDebugEnabled())
{
logger.debug("Performing in-transaction cm:auditable update for " + parentNodePair + " from " + childNodePair);
}
}
else
{
TransactionalResourceHelper.getSet(KEY_AUDITABLE_PROPAGATION_POST).add(parentNodeId);
TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION_POST).put(parentNodeId, childNodeId);
if (logger.isDebugEnabled())
{
logger.debug("Requesting later cm:auditable update for " + parentNodePair + " from " + childNodePair);
}
}
// Bind a listener for post-transaction manipulation
@@ -2976,7 +3012,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
private static final String KEY_AUDITABLE_PROPAGATION_POST = "node.auditable.propagation.post";
private AuditableTransactionListener auditableTransactionListener = new AuditableTransactionListener();
/**
* Wrapper to set the <b>cm:modified</b> time on individual nodes.
* Wrapper to set the <b>cm:modified</b> time and <b>cm:modifier</b> on
* individual nodes.
*
* @author Derek Hulley
* @since 3.4.6
@@ -2992,7 +3029,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
throw new IllegalStateException("Attempting to modify parent cm:modified in read-only txn.");
}
Set<Long> parentNodeIds = TransactionalResourceHelper.getSet(KEY_AUDITABLE_PROPAGATION_PRE);
Map<Long,Long> parentNodeIds = TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION_PRE);
if (parentNodeIds.size() == 0)
{
return;
@@ -3005,7 +3042,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
@Override
public void afterCommit()
{
Set<Long> parentNodeIds = TransactionalResourceHelper.getSet(KEY_AUDITABLE_PROPAGATION_POST);
Map<Long,Long> parentNodeIds = TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION_POST);
if (parentNodeIds.size() == 0)
{
return;
@@ -3015,16 +3052,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
}
/**
* @param parentNodeIds the parent node IDs that need to be touched for <b>cm:modified</b>
* @param parentNodeIds the parent node IDs that need to be touched for <b>cm:modified</b>, and the updating child node from which to get the <b>cm:modifier</b> from
* @param modifiedDate the date to set
* @param useCurrentTxn <tt>true</tt> to use the current transaction
*/
private void process(final Set<Long> parentNodeIds, Date modifiedDate, boolean useCurrentTxn)
private void process(final Map<Long,Long> parentNodeIds, Date modifiedDate, boolean useCurrentTxn)
{
// Walk through the IDs
for (Long parentNodeId: parentNodeIds)
for (Long parentNodeId: parentNodeIds.keySet())
{
processSingle(parentNodeId, modifiedDate, useCurrentTxn);
processSingle(parentNodeId, parentNodeIds.get(parentNodeId), modifiedDate, useCurrentTxn);
}
}
@@ -3032,10 +3069,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
* Touch a single node in a new, writable txn
*
* @param parentNodeId the parent node to touch
* @param childNodeId the child node from which to get the <b>cm:modifier</b> from
* @param modifiedDate the date to set
* @param useCurrentTxn <tt>true</tt> to use the current transaction
*/
private void processSingle(final Long parentNodeId, final Date modifiedDate, boolean useCurrentTxn)
private void processSingle(final Long parentNodeId, final Long childNodeId, final Date modifiedDate, boolean useCurrentTxn)
{
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setMaxRetries(1);
@@ -3044,6 +3082,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
@Override
public Void execute() throws Throwable
{
// Get the details of the parent, and check it's valid to update
Pair<Long, NodeRef> parentNodePair = nodeDAO.getNodePair(parentNodeId);
if (parentNodePair == null)
{
@@ -3055,12 +3094,47 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
}
NodeRef parentNodeRef = parentNodePair.getSecond();
// Fetch the modification details from the child, as best we can
Pair<Long, NodeRef> childNodePair = nodeDAO.getNodePair(childNodeId);
String modifiedByToPropagate = null;
Date modifiedDateToPropagate = modifiedDate;
if (childNodePair == null)
{
// Child has gone away, can't fetch details from children's properties
modifiedByToPropagate = AuthenticationUtil.getFullyAuthenticatedUser();
}
else if (!nodeDAO.hasNodeAspect(childNodeId, ContentModel.ASPECT_AUDITABLE))
{
// Child isn't auditable, can't fetch details
return null;
}
else
{
// Get the child's modification details
modifiedByToPropagate = (String)nodeDAO.getNodeProperty(childNodeId, ContentModel.PROP_MODIFIER);
modifiedDateToPropagate = (Date)nodeDAO.getNodeProperty(childNodeId, ContentModel.PROP_MODIFIED);
}
// Did another child get there first?
Date parentModifiedAt = (Date)nodeDAO.getNodeProperty(parentNodeId, ContentModel.PROP_MODIFIED);
if (parentModifiedAt != null && modifiedDateToPropagate != null
&& parentModifiedAt.getTime() > modifiedDateToPropagate.getTime())
{
// Parent was modified more recently, don't update
if(logger.isDebugEnabled())
{
logger.debug("Parent " + parentNodeRef + " was modified more recently than child " +
childNodePair + " so not propogating auditable details");
}
return null;
}
// Invoke policy behaviour
invokeBeforeUpdateNode(parentNodeRef);
// Touch the node; it is cm:auditable
boolean changed = nodeDAO.setModifiedDate(parentNodeId, modifiedDate);
boolean changed = nodeDAO.setModifiedProperties(parentNodeId, modifiedDate, modifiedByToPropagate);
if (changed)
{
// Invoke policy behaviour