From 85e99b826ed2896a90ed9aeb03c28a40f9232f2f Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Fri, 11 Apr 2014 04:04:33 +0000 Subject: [PATCH] RM-1413: DispositionLifecycleJobExecuter does not execute the disposition action * corrected logic error in job implementation * unit test * the disposition actions to execute are now configured via Spring configuration git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@67170 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../org_alfresco_module_rm/log4j.properties | 5 + .../org_alfresco_module_rm/rm-job-context.xml | 11 +- .../job/DispositionLifecycleJobExecuter.java | 166 +++++++++---- ...spositionLifecycleJobExecuterUnitTest.java | 231 ++++++++++++++++++ .../test/AllUnitTestSuite.java | 2 + .../test/util/BaseUnitTest.java | 67 ++++- 6 files changed, 420 insertions(+), 62 deletions(-) create mode 100644 rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties index ee1920224c..ce1c98b6b9 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties @@ -21,6 +21,11 @@ log4j.logger.org.alfresco.module.org_alfresco_module_rm.patch=info # #log4j.logger.org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService=debug +# +# Job debug +# +#log4j.logger.org.alfresco.module.org_alfresco_module_rm.job=debug + # # Behaviour debug # diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml index 072f0cce70..704b1015fd 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml @@ -72,6 +72,15 @@ + + + + + cutoff + retain + + + @@ -86,7 +95,7 @@ - 0 0/15 * * * ? + 0/30 * * * * ? diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java index 650b54a783..0dc25b859c 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java @@ -40,38 +40,110 @@ import org.apache.commons.logging.LogFactory; /** * The Disposition Lifecycle Job Finds all disposition action nodes which are - * for "retain" or "cutOff" actions Where asOf > now OR + * for disposition actions specified Where asOf > now OR * dispositionEventsEligible = true; * * Runs the cut off or retain action for - * elligible records. + * eligible records. * * @author mrogers + * @author Roy Wetherall */ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecuter { + /** logger */ private static Log logger = LogFactory.getLog(DispositionLifecycleJobExecuter.class); + + /** list of disposition actions to automatically execute */ + private List dispositionActions; + + /** query string */ + private String query; + /** records management action service */ private RecordsManagementActionService recordsManagementActionService; + /** node service */ private NodeService nodeService; + /** search service */ private SearchService searchService; + /** + * List of disposition actions to automatically execute when eligible. + * + * @param dispositionActions disposition actions + */ + public void setDispositionActions(List dispositionActions) + { + this.dispositionActions = dispositionActions; + } + + /** + * @param recordsManagementActionService records management action service + */ public void setRecordsManagementActionService(RecordsManagementActionService recordsManagementActionService) { this.recordsManagementActionService = recordsManagementActionService; } + /** + * @param nodeService node service + */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } + /** + * @param searchService search service + */ public void setSearchService(SearchService searchService) { this.searchService = searchService; } + + /** + * Get the search query string. + * + * @return job query string + */ + private String getQuery() + { + if (query == null) + { + StringBuilder sb = new StringBuilder(); + + sb.append("+TYPE:\"rma:dispositionAction\" "); + sb.append("+(@rma\\:dispositionAction:("); + + boolean bFirst = true; + for (String dispositionAction : dispositionActions) + { + if (bFirst) + { + bFirst = false; + } + else + { + sb.append(" OR "); + } + + sb.append("\"").append(dispositionAction).append("\""); + } + + sb.append("))"); + sb.append("+ISNULL:\"rma:dispositionActionCompletedAt\" "); + sb.append("+( "); + sb.append("@rma\\:dispositionEventsEligible:true "); + sb.append("OR @rma\\:dispositionAsOf:[MIN TO NOW] "); + sb.append(") "); + + query = sb.toString(); + } + + return query; + } /** * @see org.alfresco.module.org_alfresco_module_rm.job.RecordsManagementJobExecuter#execute() @@ -81,64 +153,58 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute try { logger.debug("Job Starting"); - - StringBuilder sb = new StringBuilder(); - sb.append("+TYPE:\"rma:dispositionAction\" "); - sb.append("+(@rma\\:dispositionAction:(\"cutoff\" OR \"retain\"))"); - sb.append("+ISNULL:\"rma:dispositionActionCompletedAt\" "); - sb.append("+( "); - sb.append("@rma\\:dispositionEventsEligible:true "); - sb.append("OR @rma\\:dispositionAsOf:[MIN TO NOW] "); - sb.append(") "); - - String query = sb.toString(); - - ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_LUCENE, query); - List resultNodes = results.getNodeRefs(); - results.close(); - - - for (NodeRef node : resultNodes) + + if (dispositionActions != null && !dispositionActions.isEmpty()) { - final NodeRef currentNode = node; - - RetryingTransactionCallback processTranCB = new RetryingTransactionCallback() + // execute search + ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, getQuery()); + List resultNodes = results.getNodeRefs(); + results.close(); + + if (logger.isDebugEnabled()) { - public Boolean execute() throws Throwable + logger.debug("Processing " + resultNodes.size() + " nodes"); + } + + // process search results + for (NodeRef node : resultNodes) + { + final NodeRef currentNode = node; + + RetryingTransactionCallback processTranCB = new RetryingTransactionCallback() { - final String dispAction = (String) nodeService.getProperty(currentNode, - RecordsManagementModel.PROP_DISPOSITION_ACTION); - - // Run "retain" and "cutoff" actions. - - if (dispAction != null && (dispAction.equalsIgnoreCase("cutoff") || - dispAction.equalsIgnoreCase("retain"))) + public Boolean execute() throws Throwable { - ChildAssociationRef parent = nodeService.getPrimaryParent(currentNode); - if (parent.getTypeQName().equals(RecordsManagementModel.ASSOC_NEXT_DISPOSITION_ACTION)) + final String dispAction = (String) nodeService.getProperty(currentNode, RecordsManagementModel.PROP_DISPOSITION_ACTION); + + // Run disposition action + if (dispAction != null && dispositionActions.contains(dispAction)) { - Map props = new HashMap(1); - props.put(RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK, Boolean.FALSE); - recordsManagementActionService.executeRecordsManagementAction(parent.getParentRef(), dispAction, props); - if (logger.isDebugEnabled()) + ChildAssociationRef parent = nodeService.getPrimaryParent(currentNode); + if (parent.getTypeQName().equals(RecordsManagementModel.ASSOC_NEXT_DISPOSITION_ACTION)) { - logger.debug("Processed action: " + dispAction + "on" + parent); + Map props = new HashMap(1); + props.put(RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK, Boolean.FALSE); + + // execute disposition action + recordsManagementActionService.executeRecordsManagementAction(parent.getParentRef(), dispAction, props); + + if (logger.isDebugEnabled()) + { + logger.debug("Processed action: " + dispAction + "on" + parent); + } } } - return null; + + return Boolean.TRUE; } - return Boolean.TRUE; + }; + + // if exists + if (nodeService.exists(currentNode)) + { + retryingTransactionHelper.doInTransaction(processTranCB); } - }; - - /** - * Now do the work, one action in each transaction - */ - - if (!nodeService.exists(currentNode)) - { - retryingTransactionHelper.doInTransaction(processTranCB); } } diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java new file mode 100644 index 0000000000..8d82002c19 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java @@ -0,0 +1,231 @@ +/* + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.job; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.anyMap; +import static org.mockito.Mockito.contains; + +import java.util.Collections; +import java.util.List; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** + * Disposition lifecycle job execution unit test. + * + * @author Roy Wetherall + * @since 2.2 + */ +public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest +{ + /** disposition actions */ + private static final String CUTOFF = "cutoff"; + private static final String RETAIN = "retain"; + private static final String DESTROY = "destroy"; + + /** test query snipit */ + private static final String QUERY = "\"" + CUTOFF + "\" OR \"" + RETAIN + "\""; + + /** mocked result set */ + @Mock ResultSet mockedResultSet; + + /** disposition lifecycle job executer */ + @InjectMocks DispositionLifecycleJobExecuter executer; + + /** + * @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest#before() + */ + @Override + @Before + public void before() + { + super.before(); + + // setup data + List dispositionActions = buildList(CUTOFF, RETAIN); + executer.setDispositionActions(dispositionActions); + + // setup interactions + doReturn(mockedResultSet).when(mockedSearchService).query(eq(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), eq(SearchService.LANGUAGE_LUCENE), anyString()); + } + + /** + * Helper method to verify that the query has been executed and closed + */ + private void verifyQuery() + { + verify(mockedSearchService, times(1)).query(eq(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), eq(SearchService.LANGUAGE_LUCENE), contains(QUERY)); + verify(mockedResultSet, times(1)).getNodeRefs(); + verify(mockedResultSet, times(1)).close(); + } + + /** + * When the are no results in query. + */ + @Test + public void noResultsInQuery() + { + // given + doReturn(Collections.EMPTY_LIST).when(mockedResultSet).getNodeRefs(); + + // when + executer.executeImpl(); + + // then + + // ensure the query is executed and closed + verifyQuery(); + + // ensure nothing else happens becuase we have no results + verifyZeroInteractions(mockedNodeService, mockedRecordFolderService, mockedRetryingTransactionHelper); + } + + /** + * When the disposition actions do not match those that can be processed automatically. + */ + @SuppressWarnings("unchecked") + @Test + public void dispositionActionDoesNotMatch() + { + // test data + NodeRef node1 = generateNodeRef(); + NodeRef node2 = generateNodeRef(); + List nodeRefs = buildList(node1, node2); + + // given + doReturn(nodeRefs).when(mockedResultSet).getNodeRefs(); + doReturn(DESTROY).when(mockedNodeService).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION); + doReturn(DESTROY).when(mockedNodeService).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION); + + // when + executer.executeImpl(); + + // then + + // ensure the query is executed and closed + verifyQuery(); + + // ensure work is executed in transaction for each node processed + verify(mockedNodeService, times(2)).exists(any(NodeRef.class)); + verify(mockedRetryingTransactionHelper, times(2)).doInTransaction(any(RetryingTransactionCallback.class)); + + // ensure each node is process correctly + verify(mockedNodeService, times(1)).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION); + verify(mockedNodeService, times(1)).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION); + + // ensure no more interactions + verifyNoMoreInteractions(mockedNodeService); + verifyZeroInteractions(mockedRecordsManagementActionService); + + } + + /** + * When a node does not exist + */ + @Test + public void nodeDoesNotExist() + { + // test data + NodeRef node1 = generateNodeRef(null, false); + List nodeRefs = buildList(node1); + + // given + doReturn(nodeRefs).when(mockedResultSet).getNodeRefs(); + + // when + executer.executeImpl(); + + // then + + // ensure the query is executed and closed + verifyQuery(); + + // ensure the node exist check is made for the node + verify(mockedNodeService, times(1)).exists(any(NodeRef.class)); + + // ensure no more interactions + verifyNoMoreInteractions(mockedNodeService); + verifyZeroInteractions(mockedRecordsManagementActionService, mockedRetryingTransactionHelper); + } + + /** + * When there are disposition actions eligible for processing + */ + @SuppressWarnings("unchecked") + @Test + public void dispositionActionProcessed() + { + // test data + NodeRef node1 = generateNodeRef(); + NodeRef node2 = generateNodeRef(); + List nodeRefs = buildList(node1, node2); + NodeRef parent = generateNodeRef(); + ChildAssociationRef parentAssoc = new ChildAssociationRef(ASSOC_NEXT_DISPOSITION_ACTION, parent, generateQName(), generateNodeRef()); + + // given + doReturn(nodeRefs).when(mockedResultSet).getNodeRefs(); + doReturn(CUTOFF).when(mockedNodeService).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION); + doReturn(RETAIN).when(mockedNodeService).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION); + doReturn(parentAssoc).when(mockedNodeService).getPrimaryParent(any(NodeRef.class)); + + // when + executer.executeImpl(); + + // then + + // ensure the query is executed and closed + verifyQuery(); + + // ensure work is executed in transaction for each node processed + verify(mockedNodeService, times(2)).exists(any(NodeRef.class)); + verify(mockedRetryingTransactionHelper, times(2)).doInTransaction(any(RetryingTransactionCallback.class)); + + // ensure each node is process correctly + // node1 + verify(mockedNodeService, times(1)).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION); + verify(mockedNodeService, times(1)).getPrimaryParent(node1); + verify(mockedRecordsManagementActionService, times(1)).executeRecordsManagementAction(eq(parent), eq(CUTOFF), anyMap()); + // node2 + verify(mockedNodeService, times(1)).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION); + verify(mockedNodeService, times(1)).getPrimaryParent(node2); + verify(mockedRecordsManagementActionService, times(1)).executeRecordsManagementAction(eq(parent), eq(RETAIN), anyMap()); + + // ensure no more interactions + verifyNoMoreInteractions(mockedNodeService, mockedRecordsManagementActionService); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/AllUnitTestSuite.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/AllUnitTestSuite.java index 509802fe48..e16ff323bd 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/AllUnitTestSuite.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/AllUnitTestSuite.java @@ -21,6 +21,7 @@ package org.alfresco.module.org_alfresco_module_rm.test; import org.alfresco.module.org_alfresco_module_rm.capability.declarative.condition.HoldCapabilityConditionUnitTest; import org.alfresco.module.org_alfresco_module_rm.forms.RecordsManagementTypeFormFilterUnitTest; import org.alfresco.module.org_alfresco_module_rm.hold.HoldServiceImplUnitTest; +import org.alfresco.module.org_alfresco_module_rm.job.DispositionLifecycleJobExecuterUnitTest; import org.alfresco.module.org_alfresco_module_rm.jscript.app.evaluator.FrozenEvaluatorUnitTest; import org.alfresco.module.org_alfresco_module_rm.jscript.app.evaluator.TransferEvaluatorUnitTest; import org.alfresco.module.org_alfresco_module_rm.record.RecordMetadataBootstrapUnitTest; @@ -44,6 +45,7 @@ import org.junit.runners.Suite.SuiteClasses; { RecordMetadataBootstrapUnitTest.class, RecordsManagementTypeFormFilterUnitTest.class, + DispositionLifecycleJobExecuterUnitTest.class, // services RecordServiceImplUnitTest.class, diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseUnitTest.java index 4c3afa8474..9389e5b40f 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseUnitTest.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; import org.alfresco.module.org_alfresco_module_rm.identifier.IdentifierService; @@ -38,11 +39,14 @@ import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; 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.cmr.search.SearchService; import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; @@ -78,18 +82,21 @@ public class BaseUnitTest implements RecordsManagementModel protected NodeRef record; /** core service mocks */ - @Mock(name="nodeService") protected NodeService mockedNodeService; - @Mock(name="dictionaryService") protected DictionaryService mockedDictionaryService; - @Mock(name="namespaceService") protected NamespaceService mockedNamespaceService; - @Mock(name="identifierService") protected IdentifierService mockedIdentifierService; - @Mock(name="permissionService") protected PermissionService mockedPermissionService; - @Mock(name="ownableService") protected OwnableService mockedOwnableService; + @Mock(name="nodeService") protected NodeService mockedNodeService; + @Mock(name="dictionaryService") protected DictionaryService mockedDictionaryService; + @Mock(name="namespaceService") protected NamespaceService mockedNamespaceService; + @Mock(name="identifierService") protected IdentifierService mockedIdentifierService; + @Mock(name="permissionService") protected PermissionService mockedPermissionService; + @Mock(name="ownableService") protected OwnableService mockedOwnableService; + @Mock(name="searchService") protected SearchService mockedSearchService; + @Mock(name="retryingTransactionHelper") protected RetryingTransactionHelper mockedRetryingTransactionHelper; /** rm service mocks */ - @Mock(name="filePlanService") protected FilePlanService mockedFilePlanService; - @Mock(name="recordFolderService") protected RecordFolderService mockedRecordFolderService; - @Mock(name="recordService") protected RecordService mockedRecordService; - @Mock(name="holdService") protected HoldService mockedHoldService; + @Mock(name="filePlanService") protected FilePlanService mockedFilePlanService; + @Mock(name="recordFolderService") protected RecordFolderService mockedRecordFolderService; + @Mock(name="recordService") protected RecordService mockedRecordService; + @Mock(name="holdService") protected HoldService mockedHoldService; + @Mock(name="recordsManagementActionService") protected RecordsManagementActionService mockedRecordsManagementActionService; /** application context mock */ @Mock(name="applicationContext") protected ApplicationContext mockedApplicationContext; @@ -101,6 +108,7 @@ public class BaseUnitTest implements RecordsManagementModel /** * Test method setup */ + @SuppressWarnings("unchecked") @Before public void before() { @@ -108,6 +116,19 @@ public class BaseUnitTest implements RecordsManagementModel // setup application context doReturn(mockedNodeService).when(mockedApplicationContext).getBean("nodeService"); + + // setup retrying transaction helper + Answer doInTransactionAnswer = new Answer() + { + @SuppressWarnings("rawtypes") + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + RetryingTransactionCallback callback = (RetryingTransactionCallback)invocation.getArguments()[0]; + return callback.execute(); + } + }; + doAnswer(doInTransactionAnswer).when(mockedRetryingTransactionHelper).doInTransaction(any(RetryingTransactionCallback.class)); // setup file plan filePlan = generateNodeRef(TYPE_FILE_PLAN); @@ -217,9 +238,22 @@ public class BaseUnitTest implements RecordsManagementModel * the content type provided */ protected NodeRef generateNodeRef(QName type) + { + return generateNodeRef(type, true); + } + + /** + * Helper method to generate a node reference of a particular type with a given existence characteristic. + * + * @param type content type qualified name + * @param exists indicates whether this node should behave like a node that exists or not + * @return {@link NodeRef} node reference that behaves like a node that exists (or not) in the spaces store with + * the content type provided + */ + protected NodeRef generateNodeRef(QName type, boolean exists) { NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()); - when(mockedNodeService.exists(eq(nodeRef))).thenReturn(true); + when(mockedNodeService.exists(eq(nodeRef))).thenReturn(exists); if (type != null) { when(mockedNodeService.getType(eq(nodeRef))).thenReturn(type); @@ -294,4 +328,15 @@ public class BaseUnitTest implements RecordsManagementModel }).when(service).runAs(any(RunAsWork.class), anyString()); } + + @SuppressWarnings("unchecked") + protected List buildList(T ... values) + { + List result = new ArrayList(values.length); + for (T value : values) + { + result.add(value); + } + return result; + } }