mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
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:
@@ -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)
|
||||
{
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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
|
||||
|
@@ -225,6 +225,7 @@ public class Repository01TestSuite extends TestSuite
|
||||
suite.addTestSuite(org.alfresco.repo.node.archive.LargeArchiveAndRestoreTest.class);
|
||||
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.node.cleanup.TransactionCleanupTest.class));
|
||||
suite.addTestSuite(org.alfresco.repo.node.db.DbNodeServiceImplTest.class);
|
||||
suite.addTestSuite(org.alfresco.repo.node.db.DbNodeServiceImplPropagationTest.class);
|
||||
}
|
||||
|
||||
static void tests36(TestSuite suite) // Fails with previous tests
|
||||
|
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.node.db;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.dictionary.DictionaryComponent;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
import org.alfresco.repo.domain.node.NodeDAO;
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.BaseSpringTest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.node.db.DbNodeServiceImpl#propagateTimeStamp
|
||||
*
|
||||
* @author sergey.shcherbovich
|
||||
*/
|
||||
|
||||
public class DbNodeServiceImplPropagationTest extends BaseSpringTest
|
||||
{
|
||||
private TransactionService txnService;
|
||||
private NodeDAO nodeDAO;
|
||||
private NodeService nodeService;
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
protected DictionaryService dictionaryService;
|
||||
|
||||
private UserTransaction txn = null;
|
||||
|
||||
@Override
|
||||
protected void onSetUpInTransaction() throws Exception
|
||||
{
|
||||
super.onSetUpInTransaction();
|
||||
txnService = (TransactionService) applicationContext.getBean("transactionComponent");
|
||||
nodeDAO = (NodeDAO) applicationContext.getBean("nodeDAO");
|
||||
nodeService = (NodeService) applicationContext.getBean("dbNodeService");
|
||||
|
||||
authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent");
|
||||
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) applicationContext.getBean("dictionaryDAO");
|
||||
// load the system model
|
||||
ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
|
||||
InputStream modelStream = cl.getResourceAsStream("alfresco/model/contentModel.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
// load the test model
|
||||
modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
DictionaryComponent dictionary = new DictionaryComponent();
|
||||
dictionary.setDictionaryDAO(dictionaryDao);
|
||||
dictionaryService = loadModel(applicationContext);
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTearDownInTransaction() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
super.onTearDownInTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the test model required for building the node graphs
|
||||
*/
|
||||
public static DictionaryService loadModel(ApplicationContext applicationContext)
|
||||
{
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) applicationContext.getBean("dictionaryDAO");
|
||||
// load the system model
|
||||
ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
|
||||
InputStream modelStream = cl.getResourceAsStream("alfresco/model/contentModel.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
// load the test model
|
||||
modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
DictionaryComponent dictionary = new DictionaryComponent();
|
||||
dictionary.setDictionaryDAO(dictionaryDao);
|
||||
// done
|
||||
return dictionary;
|
||||
}
|
||||
/**
|
||||
* Tests that the auditable modification details (modified by, modified at)
|
||||
* get correctly propagated to the parent, where appropriate, when children
|
||||
* are added or removed.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testAuditablePropagation() throws Exception
|
||||
{
|
||||
String fullyAuthenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
|
||||
final QName TYPE_NOT_AUDITABLE = ContentModel.TYPE_CONTAINER;
|
||||
final QName TYPE_AUDITABLE = ContentModel.TYPE_CONTENT;
|
||||
final QName ASSOC_NOT_AUDITABLE = ContentModel.ASSOC_CHILDREN;
|
||||
final QName ASSOC_AUDITABLE = ContentModel.ASSOC_CONTAINS;
|
||||
|
||||
// create a first store directly
|
||||
StoreRef storeRef = nodeService.createStore(
|
||||
StoreRef.PROTOCOL_WORKSPACE,
|
||||
"Test_" + System.currentTimeMillis());
|
||||
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
|
||||
|
||||
Map<QName, ChildAssociationRef> assocRefs = BaseNodeServiceTest.buildNodeGraph(nodeService, rootNodeRef);
|
||||
// UserTransaction txn = null;
|
||||
Date modifiedAt = null;
|
||||
|
||||
// Get the root node to test against
|
||||
ChildAssociationRef n2pn4Ref = assocRefs.get(QName.createQName(BaseNodeServiceTest.NAMESPACE, "n2_p_n4"));
|
||||
final NodeRef n2Ref = n2pn4Ref.getParentRef();
|
||||
final long n2Id = nodeDAO.getNodePair(n2Ref).getFirst();
|
||||
|
||||
// Doesn't start out auditable
|
||||
assertFalse("Shouldn't be auditable in " + nodeService.getAspects(n2Ref),
|
||||
nodeService.getAspects(n2Ref).contains(ContentModel.ASPECT_AUDITABLE));
|
||||
|
||||
QName typeBefore = nodeService.getType(n2Ref);
|
||||
|
||||
// Get onto our own transactions
|
||||
setComplete();
|
||||
endTransaction();
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
// Create a non-auditable child, parent won't update
|
||||
NodeRef naC = nodeService.createNode(n2Ref, ASSOC_NOT_AUDITABLE,
|
||||
QName.createQName("not-auditable"), TYPE_NOT_AUDITABLE).getChildRef();
|
||||
logger.debug("Created non-auditable child " + naC);
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
// Parent hasn't been updated
|
||||
assertNull(nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED));
|
||||
assertNull(nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Create an auditable child, parent won't update either as still not auditable
|
||||
NodeRef adC = nodeService.createNode(n2Ref, ASSOC_NOT_AUDITABLE,
|
||||
QName.createQName("is-auditable"), TYPE_AUDITABLE).getChildRef();
|
||||
nodeService.addAspect(adC, ContentModel.ASPECT_AUDITABLE, null);
|
||||
logger.debug("Created auditable child " + naC + " of non-auditable parent " + n2Ref);
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
// Parent hasn't been updated, but auditable child has
|
||||
assertNull(nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED));
|
||||
assertNull(nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
assertNotNull(nodeService.getProperty(adC, ContentModel.PROP_MODIFIED));
|
||||
assertNotNull(nodeService.getProperty(adC, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Make the parent auditable, and give it a special modified by
|
||||
nodeService.addAspect(n2Ref, ContentModel.ASPECT_AUDITABLE, null);
|
||||
nodeService.setType(n2Ref, ContentModel.TYPE_FOLDER);
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
Date modified = new Date();
|
||||
nodeDAO.setModifiedProperties(n2Id, modified, "TestModifier");
|
||||
txn = restartAuditableTxn(txn);
|
||||
assertEquals(modified.getTime(), ((Date)nodeDAO.getNodeProperty(n2Id, ContentModel.PROP_MODIFIED)).getTime());
|
||||
assertEquals("TestModifier", nodeDAO.getNodeProperty(n2Id, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Delete the non-auditable child
|
||||
// No change to the parent as non-auditable child
|
||||
logger.debug("Deleting non-auditable child " + naC + " of auditable parent " + n2Ref);
|
||||
nodeService.addAspect(naC, ContentModel.ASPECT_TEMPORARY, null);
|
||||
nodeService.deleteNode(naC);
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
assertEquals(modified.getTime(), ((Date)nodeDAO.getNodeProperty(n2Id, ContentModel.PROP_MODIFIED)).getTime());
|
||||
assertEquals("TestModifier", nodeDAO.getNodeProperty(n2Id, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Add an auditable child, parent will be updated
|
||||
adC = nodeService.createNode(n2Ref, ASSOC_AUDITABLE,
|
||||
QName.createQName("is-auditable"), TYPE_AUDITABLE).getChildRef();
|
||||
final long adCId = nodeDAO.getNodePair(adC).getFirst();
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
modifiedAt = (Date)nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED);
|
||||
assertNotNull(modifiedAt);
|
||||
assertEquals((double)new Date().getTime(), (double)modifiedAt.getTime(), 10000d);
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Set well-known modified details on both nodes
|
||||
nodeDAO.setModifiedProperties(n2Id, new Date(Integer.MIN_VALUE), "TestModifierPrnt");
|
||||
nodeDAO.setModifiedProperties(adCId, new Date(Integer.MIN_VALUE), "TestModifierChld");
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
// Now delete the auditable child
|
||||
// The parent's modified date will change, but not the modified by, as the child
|
||||
// has been deleted so the child's modified-by can't be read
|
||||
logger.debug("Deleting auditable child " + adC + " of auditable parent " + n2Ref);
|
||||
nodeService.deleteNode(adC);
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
// Parent's date was updated, but not the modifier, since child was deleted
|
||||
// which means the child's modifier wasn't available to read
|
||||
modifiedAt = (Date)nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED);
|
||||
assertNotNull(modifiedAt);
|
||||
assertEquals((double)new Date().getTime(), (double)modifiedAt.getTime(), 10000d);
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Set well-known modified detail on our parent again
|
||||
modified = new Date();
|
||||
nodeDAO.setModifiedProperties(n2Id, modified, "ModOn2");
|
||||
txn = restartAuditableTxn(txn);
|
||||
assertEquals("ModOn2", nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Add two auditable children, both with special modifiers
|
||||
// Only the first child's update in a transaction will be used
|
||||
NodeRef ac1 = nodeService.createNode(n2Ref, ASSOC_AUDITABLE,
|
||||
QName.createQName("is-auditable-1"), TYPE_AUDITABLE).getChildRef();
|
||||
NodeRef ac2 = nodeService.createNode(n2Ref, ASSOC_AUDITABLE,
|
||||
QName.createQName("is-auditable-2"), TYPE_AUDITABLE).getChildRef();
|
||||
final long ac1Id = nodeDAO.getNodePair(ac1).getFirst();
|
||||
final long ac2Id = nodeDAO.getNodePair(ac2).getFirst();
|
||||
|
||||
// Manually set different modifiers on the children, so that
|
||||
// we can test to see if they propagate properly
|
||||
nodeDAO.setModifiedProperties(ac1Id, new Date(), "ModAC1");
|
||||
nodeDAO.setModifiedProperties(ac2Id, new Date(), "ModAC2");
|
||||
// Ensure the parent is "old", so that the propagation can take place
|
||||
nodeDAO.setModifiedProperties(n2Id, new Date(Integer.MIN_VALUE), "ModOn2");
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
// Check that only the first reached the parent
|
||||
assertNotNull(nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED));
|
||||
assertNotNull(nodeService.getProperty(ac1, ContentModel.PROP_MODIFIED));
|
||||
assertNotNull(nodeService.getProperty(ac2, ContentModel.PROP_MODIFIED));
|
||||
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(ac1, ContentModel.PROP_MODIFIER));
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(ac2, ContentModel.PROP_MODIFIER));
|
||||
|
||||
// Updates won't apply if the parent is newer than the child
|
||||
Date now = new Date();
|
||||
long futureShift = 4000l;
|
||||
Date future = new Date(now.getTime()+futureShift);
|
||||
nodeDAO.setModifiedProperties(n2Id, future, "TestModifierPrnt");
|
||||
|
||||
NodeRef ac3 = nodeService.createNode(n2Ref, ASSOC_AUDITABLE,
|
||||
QName.createQName("is-auditable-3"), TYPE_AUDITABLE).getChildRef();
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
assertEquals("TestModifierPrnt", nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(ac3, ContentModel.PROP_MODIFIER));
|
||||
|
||||
modifiedAt = (Date)nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED);
|
||||
assertEquals((double)future.getTime(), (double)modifiedAt.getTime(), 1000d);
|
||||
modifiedAt = (Date)nodeService.getProperty(ac3, ContentModel.PROP_MODIFIED);
|
||||
assertEquals((double)now.getTime(), (double)modifiedAt.getTime(), 1000d);
|
||||
|
||||
// Parent-Child association needs to be a suitable kind to trigger
|
||||
nodeService.setType(n2Ref, typeBefore);
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(futureShift);
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
}
|
||||
|
||||
modified = new Date();
|
||||
nodeDAO.setModifiedProperties(n2Id, modified, "TestModifier");
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
assertEquals("TestModifier", nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
|
||||
NodeRef ac4 = nodeService.createNode(n2Ref, ASSOC_NOT_AUDITABLE,
|
||||
QName.createQName("is-auditable-4"), TYPE_AUDITABLE).getChildRef();
|
||||
|
||||
txn = restartAuditableTxn(txn);
|
||||
|
||||
assertEquals("TestModifier", nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIER));
|
||||
assertEquals(fullyAuthenticatedUser, nodeService.getProperty(ac4, ContentModel.PROP_MODIFIER));
|
||||
|
||||
modifiedAt = (Date)nodeService.getProperty(n2Ref, ContentModel.PROP_MODIFIED);
|
||||
assertEquals(modified.getTime(), modifiedAt.getTime());
|
||||
modifiedAt = (Date)nodeService.getProperty(ac4, ContentModel.PROP_MODIFIED);
|
||||
assertEquals((double)new Date().getTime(), (double)modifiedAt.getTime(), 3000d);
|
||||
|
||||
setComplete();
|
||||
endTransaction();
|
||||
|
||||
startNewTransaction();
|
||||
|
||||
}
|
||||
|
||||
private UserTransaction restartAuditableTxn(UserTransaction txn) throws Exception
|
||||
{
|
||||
if (txn != null)
|
||||
txn.commit();
|
||||
txn = txnService.getUserTransaction();
|
||||
txn.begin();
|
||||
|
||||
// Wait long enough that AuditablePropertiesEntity.setAuditModified
|
||||
// will recognize subsequent changes as needing new audit entries
|
||||
try
|
||||
{
|
||||
Thread.sleep(1250L);
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
}
|
||||
|
||||
return txn;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user