mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2026-09-16 18:13:17 +00:00
[ACS-12244] Do not throw error when rma:cutOffDate is being cleaned as part of Uncutoff action (#4244)
This commit is contained in:
+41
@@ -27,13 +27,16 @@
|
||||
|
||||
package org.alfresco.module.org_alfresco_module_rm.action.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.module.org_alfresco_module_rm.action.RMActionExecuterAbstractBase;
|
||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
|
||||
@@ -47,6 +50,9 @@ public class UnCutoffAction extends RMActionExecuterAbstractBase
|
||||
/** I18N */
|
||||
private static final String MSG_UNDO_NOT_LAST = "rm.action.undo-not-last";
|
||||
|
||||
/** Transaction resource key used to track nodes whose cut off is being undone in the current transaction */
|
||||
private static final String KEY_UNDO_CUT_OFF_NODE_REFS = "UnCutoffAction.undoCutOffNodeRefs";
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef)
|
||||
*/
|
||||
@@ -67,6 +73,7 @@ public class UnCutoffAction extends RMActionExecuterAbstractBase
|
||||
}
|
||||
|
||||
// Remove the cutoff aspect and add the uncutoff aspect
|
||||
markUndoCutOffInProgress(actionedUponNodeRef);
|
||||
getNodeService().removeAspect(actionedUponNodeRef, ASPECT_CUT_OFF);
|
||||
getNodeService().addAspect(actionedUponNodeRef, ASPECT_UNCUT_OFF, null);
|
||||
if (getRecordFolderService().isRecordFolder(actionedUponNodeRef))
|
||||
@@ -74,6 +81,7 @@ public class UnCutoffAction extends RMActionExecuterAbstractBase
|
||||
List<NodeRef> records = getRecordService().getRecords(actionedUponNodeRef);
|
||||
for (NodeRef record : records)
|
||||
{
|
||||
markUndoCutOffInProgress(record);
|
||||
getNodeService().removeAspect(record, ASPECT_CUT_OFF);
|
||||
getNodeService().addAspect(record, ASPECT_UNCUT_OFF, null);
|
||||
}
|
||||
@@ -96,4 +104,37 @@ public class UnCutoffAction extends RMActionExecuterAbstractBase
|
||||
getNodeService().setProperty(da.getNodeRef(), PROP_DISPOSITION_ACTION_COMPLETED_BY, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the given node as having its cut off undone in the current transaction, so that {@link #isUndoCutOffInProgress(NodeRef)} can identify the resulting property change as legitimate.
|
||||
*
|
||||
* @param nodeRef
|
||||
* the node whose cut off is being undone
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void markUndoCutOffInProgress(NodeRef nodeRef)
|
||||
{
|
||||
Set<NodeRef> nodeRefs = (Set<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_UNDO_CUT_OFF_NODE_REFS);
|
||||
if (nodeRefs == null)
|
||||
{
|
||||
nodeRefs = new HashSet<>();
|
||||
AlfrescoTransactionSupport.bindResource(KEY_UNDO_CUT_OFF_NODE_REFS, nodeRefs);
|
||||
}
|
||||
nodeRefs.add(nodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given node's cut off is currently being undone by this action, in the current transaction. Unlike checking for the presence of the uncut off aspect, this is only true for the exact operation that clears the cut off date, and not for any node that has been undone in the past.
|
||||
*
|
||||
* @param nodeRef
|
||||
* the node to check
|
||||
*
|
||||
* @return true if the node's cut off is being undone in the current transaction, false otherwise
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isUndoCutOffInProgress(NodeRef nodeRef)
|
||||
{
|
||||
Set<NodeRef> nodeRefs = (Set<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_UNDO_CUT_OFF_NODE_REFS);
|
||||
return nodeRefs != null && nodeRefs.contains(nodeRef);
|
||||
}
|
||||
}
|
||||
|
||||
+42
-7
@@ -35,7 +35,11 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.module.org_alfresco_module_rm.action.impl.UnCutoffAction;
|
||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
|
||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
|
||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
|
||||
@@ -80,6 +84,8 @@ public class DispositionProperty extends BaseBehaviourBean
|
||||
/** Set of disposition actions this property does not apply to */
|
||||
private Set<String> excludedDispositionActions;
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(DispositionProperty.class);
|
||||
|
||||
/**
|
||||
* @param namespaceService
|
||||
* namespace service
|
||||
@@ -234,15 +240,16 @@ public class DispositionProperty extends BaseBehaviourBean
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (shouldThrowError(nodeRef, before))
|
||||
{
|
||||
throw new AlfrescoRuntimeException(
|
||||
"Error updating property " + propertyName.toPrefixString(namespaceService) +
|
||||
" to null, because property is being used to determine a disposition date.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw an exception if the property is being 'cleared'
|
||||
if (before.get(propertyName) != null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException(
|
||||
"Error updating property " + propertyName.toPrefixString(namespaceService) +
|
||||
" to null, because property is being used to determine a disposition date.");
|
||||
}
|
||||
LOGGER.debug("Property " + propertyName.toPrefixString(namespaceService) +
|
||||
" is being cleared, but this is allowed because the node is being uncutoff.");
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -267,4 +274,32 @@ public class DispositionProperty extends BaseBehaviourBean
|
||||
|
||||
return !Objects.equals(beforeValue, afterValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this property is the cut off date being legitimately cleared as part of an {@link UnCutoffAction} action in the current transaction.
|
||||
*
|
||||
* @param nodeRef
|
||||
* the node whose property is being cleared
|
||||
*
|
||||
* @return true if this is the cut off date being cleared by an {@link UnCutoffAction} action, false otherwise
|
||||
*/
|
||||
private boolean isUnCutOff(NodeRef nodeRef)
|
||||
{
|
||||
return PROP_CUT_OFF_DATE.equals(propertyName) && UnCutoffAction.isUndoCutOffInProgress(nodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether an exception should be thrown in case the property is being 'cleared'
|
||||
*
|
||||
* @param nodeRef
|
||||
* the node whose property is being cleared
|
||||
* @param before
|
||||
* the property values before the update
|
||||
*
|
||||
* @return true if an exception should be thrown, false otherwise
|
||||
*/
|
||||
private boolean shouldThrowError(NodeRef nodeRef, Map<QName, Serializable> before)
|
||||
{
|
||||
return before.get(propertyName) != null && !isUnCutOff(nodeRef);
|
||||
}
|
||||
}
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Records Management Module
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.action.impl;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.module.org_alfresco_module_rm.action.BaseActionUnitTest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
/**
|
||||
* Unit test for {@link UnCutoffAction}.
|
||||
* <p>
|
||||
* Coverage for the collaboration with {@code DispositionProperty} lives in
|
||||
* {@code DispositionPropertyUnitTest}, since that is the class whose contract is under test there.
|
||||
*/
|
||||
public class UnCutoffActionUnitTest extends BaseActionUnitTest
|
||||
{
|
||||
@InjectMocks
|
||||
private UnCutoffAction action;
|
||||
|
||||
@Mock
|
||||
private DispositionAction lastCompletedDispositionAction;
|
||||
|
||||
@Test
|
||||
public void undoCutOffMarksFolderAndRecordInProgress()
|
||||
{
|
||||
setupCutOff(recordFolder);
|
||||
setupLastCompletedCutOff(recordFolder);
|
||||
|
||||
action.executeImpl(getMockedAction(), recordFolder);
|
||||
|
||||
verify(mockedNodeService).removeAspect(recordFolder, ASPECT_CUT_OFF);
|
||||
verify(mockedNodeService).addAspect(recordFolder, ASPECT_UNCUT_OFF, null);
|
||||
verify(mockedNodeService).removeAspect(record, ASPECT_CUT_OFF);
|
||||
verify(mockedNodeService).addAspect(record, ASPECT_UNCUT_OFF, null);
|
||||
|
||||
assertTrue(UnCutoffAction.isUndoCutOffInProgress(recordFolder));
|
||||
assertTrue(UnCutoffAction.isUndoCutOffInProgress(record));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nodeNotTouchedIsNotInProgress()
|
||||
{
|
||||
NodeRef untouched = generateNodeRef();
|
||||
|
||||
assertFalse(UnCutoffAction.isUndoCutOffInProgress(untouched));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nodeNotCutOffDoesNothing()
|
||||
{
|
||||
doReturn(true).when(mockedNodeService).hasAspect(recordFolder, ASPECT_DISPOSITION_LIFECYCLE);
|
||||
doReturn(false).when(mockedNodeService).hasAspect(recordFolder, ASPECT_CUT_OFF);
|
||||
|
||||
action.executeImpl(getMockedAction(), recordFolder);
|
||||
|
||||
verify(mockedNodeService, never()).removeAspect(recordFolder, ASPECT_CUT_OFF);
|
||||
assertFalse(UnCutoffAction.isUndoCutOffInProgress(recordFolder));
|
||||
}
|
||||
|
||||
@Test(expected = AlfrescoRuntimeException.class)
|
||||
public void lastActionNotCutOffThrows()
|
||||
{
|
||||
setupCutOff(recordFolder);
|
||||
doReturn("destroy").when(lastCompletedDispositionAction).getName();
|
||||
doReturn(lastCompletedDispositionAction).when(mockedDispositionService).getLastCompletedDispostionAction(recordFolder);
|
||||
|
||||
action.executeImpl(getMockedAction(), recordFolder);
|
||||
}
|
||||
|
||||
private void setupCutOff(NodeRef nodeRef)
|
||||
{
|
||||
doReturn(true).when(mockedNodeService).hasAspect(nodeRef, ASPECT_DISPOSITION_LIFECYCLE);
|
||||
doReturn(true).when(mockedNodeService).hasAspect(nodeRef, ASPECT_CUT_OFF);
|
||||
}
|
||||
|
||||
private void setupLastCompletedCutOff(NodeRef nodeRef)
|
||||
{
|
||||
doReturn("cutoff").when(lastCompletedDispositionAction).getName();
|
||||
doReturn(generateNodeRef()).when(lastCompletedDispositionAction).getNodeRef();
|
||||
doReturn(lastCompletedDispositionAction).when(mockedDispositionService).getLastCompletedDispostionAction(nodeRef);
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Records Management Module
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.disposition.property;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.module.org_alfresco_module_rm.action.impl.UnCutoffAction;
|
||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
|
||||
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
/**
|
||||
* Unit test for {@link DispositionProperty}.
|
||||
*/
|
||||
public class DispositionPropertyUnitTest extends BaseUnitTest
|
||||
{
|
||||
@InjectMocks
|
||||
private DispositionProperty dispositionProperty;
|
||||
|
||||
@InjectMocks
|
||||
private UnCutoffAction unCutoffAction;
|
||||
|
||||
@Before
|
||||
public void setupDispositionProperty()
|
||||
{
|
||||
dispositionProperty.setName("rma:cutOffDate");
|
||||
}
|
||||
|
||||
@Test(expected = AlfrescoRuntimeException.class)
|
||||
public void cutOffDateClearedWithoutUndoCutOffThrows()
|
||||
{
|
||||
NodeRef nodeRef = generateNodeRef();
|
||||
|
||||
dispositionProperty.onUpdateProperties(nodeRef, cutOffDateProperties(new Date()), cutOffDateProperties(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cutOffDateClearedDuringUndoCutOffDoesNotThrow() throws ReflectiveOperationException
|
||||
{
|
||||
NodeRef nodeRef = markUndoCutOffInProgress();
|
||||
|
||||
try
|
||||
{
|
||||
dispositionProperty.onUpdateProperties(nodeRef, cutOffDateProperties(new Date()), cutOffDateProperties(null));
|
||||
}
|
||||
catch (AlfrescoRuntimeException e)
|
||||
{
|
||||
fail("Clearing the cut off date during an undo cut off should not throw, but threw: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = AlfrescoRuntimeException.class)
|
||||
public void clearingADifferentPropertyStillThrowsDuringUndoCutOff() throws ReflectiveOperationException
|
||||
{
|
||||
NodeRef nodeRef = markUndoCutOffInProgress();
|
||||
dispositionProperty.setName("rma:dispositionAsOf");
|
||||
|
||||
Map<QName, Serializable> before = new HashMap<>();
|
||||
before.put(PROP_DISPOSITION_AS_OF, new Date());
|
||||
Map<QName, Serializable> after = new HashMap<>();
|
||||
after.put(PROP_DISPOSITION_AS_OF, null);
|
||||
|
||||
dispositionProperty.onUpdateProperties(nodeRef, before, after);
|
||||
}
|
||||
|
||||
private NodeRef markUndoCutOffInProgress() throws ReflectiveOperationException
|
||||
{
|
||||
NodeRef nodeRef = generateNodeRef();
|
||||
doReturn(true).when(mockedNodeService).hasAspect(nodeRef, ASPECT_DISPOSITION_LIFECYCLE);
|
||||
doReturn(true).when(mockedNodeService).hasAspect(nodeRef, ASPECT_CUT_OFF);
|
||||
doReturn(false).when(mockedRecordFolderService).isRecordFolder(nodeRef);
|
||||
|
||||
DispositionAction lastCompleted = mock(DispositionAction.class);
|
||||
doReturn("cutoff").when(lastCompleted).getName();
|
||||
doReturn(generateNodeRef()).when(lastCompleted).getNodeRef();
|
||||
doReturn(lastCompleted).when(mockedDispositionService).getLastCompletedDispostionAction(nodeRef);
|
||||
|
||||
Method executeImpl = UnCutoffAction.class.getDeclaredMethod("executeImpl", Action.class, NodeRef.class);
|
||||
executeImpl.setAccessible(true);
|
||||
executeImpl.invoke(unCutoffAction, mock(Action.class), nodeRef);
|
||||
|
||||
assertTrue("UnCutoffAction.executeImpl did not mark the node as undergoing an undo cut off - "
|
||||
+ "the guard clause likely short-circuited (stub mismatch), not the onUpdateProperties fix itself",
|
||||
UnCutoffAction.isUndoCutOffInProgress(nodeRef));
|
||||
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
private Map<QName, Serializable> cutOffDateProperties(Date date)
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<>();
|
||||
properties.put(PROP_CUT_OFF_DATE, date);
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user