diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index 9ea8e7ea8f..d6cbef4544 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -173,6 +173,17 @@ mockito-all test + + org.slf4j + slf4j-log4j12 + test + + + org.slf4j + jul-to-slf4j + 1.7.21 + test + diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties index 7a14ca27f5..83e724394e 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties @@ -55,4 +55,5 @@ log4j.logger.org.alfresco.module.org_alfresco_module_rm.patch=info # # Job debug # -#log4j.logger.org.alfresco.module.org_alfresco_module_rm.job=debug \ No newline at end of file +#log4j.logger.org.alfresco.module.org_alfresco_module_rm.job=debug +log4j.logger.org.alfresco.repo.web.scripts.roles.DynamicAuthoritiesGet=info \ No newline at end of file diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml index 58628e9921..728ac66fc5 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml @@ -587,6 +587,19 @@ + + + + + + + + + + + + Removes dynamic authorities + + URL parameter batchsize is mandatory, and represents the number of records that are processed in one transaction.
+ URL parameter maxProcessedRecords is optional, and represents the maximum number of records that will be processed in one request.
+ ]]> +
+ /api/rm/rm-dynamicauthorities?batchsize={batchsize}&maxProcessedRecords={maxProcessedRecords?} + argument + admin + required + \ No newline at end of file diff --git a/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl new file mode 100644 index 0000000000..8929b9a33c --- /dev/null +++ b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl @@ -0,0 +1,30 @@ +<#-- + #%L + Alfresco Records Management Module + %% + Copyright (C) 2005 - 2016 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 . + #L% +--> +{ + "responsestatus" : "${responsestatus?json_string}", + "message" : "${message?json_string}" +} \ No newline at end of file diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java index 9a1bbe3d8f..1c0d10ea83 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java @@ -143,7 +143,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) */ @Override - protected synchronized void executeImpl(Action action, final NodeRef actionedUponNodeRef) + protected synchronized void executeImpl(final Action action, final NodeRef actionedUponNodeRef) { String actionName = action.getActionDefinitionName(); if (isOkToProceedWithAction(actionedUponNodeRef, actionName)) @@ -165,8 +165,25 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr NodeRef recordFolder = (NodeRef)action.getParameterValue(PARAM_DESTINATION_RECORD_FOLDER); if (recordFolder == null) { - final boolean finaltargetIsUnfiledRecords = targetIsUnfiledRecords; - recordFolder = createOrResolvePath(action, actionedUponNodeRef, finaltargetIsUnfiledRecords); + final boolean finaltargetIsUnfiledRecords = targetIsUnfiledRecords; + recordFolder = retryingTransactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + NodeRef result = null; + try + { + // get the reference to the record folder based on the relative path + result = createOrResolvePath(action, actionedUponNodeRef, finaltargetIsUnfiledRecords); + } + catch (DuplicateChildNodeNameException ex) + { + throw new ConcurrencyFailureException("Cannot create or resolve path.", ex); + } + + return result; + } + }, false, true); } // now we have the reference to the target folder we can do some final checks to see if the action is valid @@ -180,29 +197,30 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr { try { - if(getMode() == CopyMoveLinkFileToActionMode.MOVE) + synchronized (this) { - fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null); - } - else if(getMode() == CopyMoveLinkFileToActionMode.COPY) - { - fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null); - } - else if(getMode() == CopyMoveLinkFileToActionMode.LINK) - { - getRecordService().link(actionedUponNodeRef, finalRecordFolder); + if (getMode() == CopyMoveLinkFileToActionMode.MOVE) + { + fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null); + } + else if (getMode() == CopyMoveLinkFileToActionMode.COPY) + { + fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null); + } + else if (getMode() == CopyMoveLinkFileToActionMode.LINK) + { + getRecordService().link(actionedUponNodeRef, finalRecordFolder); + } } } catch (FileNotFoundException fileNotFound) { - throw new AlfrescoRuntimeException( - "Unable to execute file to action, because the " + (mode == CopyMoveLinkFileToActionMode.MOVE ? "move" : "copy") + " operation failed.", - fileNotFound - ); + throw new AlfrescoRuntimeException("Unable to execute file to action, because the " + (mode == CopyMoveLinkFileToActionMode.MOVE ? "move" : "copy") + " operation failed.", fileNotFound); } - + return null; } + }); } } @@ -389,7 +407,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr */ private NodeRef getChild(NodeRef parent, String childName) { - return getNodeService().getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); + return getNodeService().getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); } /** diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java b/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java new file mode 100644 index 0000000000..5e3020e9aa --- /dev/null +++ b/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java @@ -0,0 +1,267 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 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 . + * #L% + */ +/* + * 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.repo.web.scripts.roles; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * Webscript used for removing dynamic authorities from the records. + * + * @author Silviu Dinuta + * @since 2.3.0.7 + */ +@SuppressWarnings("deprecation") +public class DynamicAuthoritiesGet extends DeclarativeWebScript implements RecordsManagementModel +{ + private static final String MESSAGE_PARAMETER_BATCHSIZE_GREATER_THAN_ZERO = "Parameter batchsize should be a number greater than 0."; + private static final String MESSAGE_PROCESSING_BEGIN = "Processing - BEGIN"; + private static final String MESSAGE_PROCESSING_END = "Processing - END"; + private static final String MESSAGE_PROCESSING_RECORD_END_TEMPLATE = "Processing record {0} - END"; + private static final String MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE = "Processing record {0} - BEGIN"; + private static final String MESSAGE_BATCHSIZE_IS_INVALID = "Parameter batchsize is invalid."; + private static final String MESSAGE_BATCHSIZE_IS_MANDATORY = "Parameter batchsize is mandatory"; + private static final String SUCCESS_STATUS = "success"; + private static final String FAILED_STATUS = "failed"; + /** + * The logger + */ + private static Log logger = LogFactory.getLog(DynamicAuthoritiesGet.class); + private static final String BATCH_SIZE = "batchsize"; + private static final String TOTAL_NUMBER_TO_PROCESS = "maxProcessedRecords"; + private static final String MODEL_STATUS = "responsestatus"; + private static final String MODEL_MESSAGE = "message"; + private static final String MESSAGE_ALL_TEMPLATE = "Processed {0} records."; + private static final String MESSAGE_PARTIAL_TEMPLATE = "Processed first {0} records."; + private static final String MESSAGE_NO_RECORDS_TO_PROCESS = "There where no records to be processed."; + + + /** services */ + private PatchDAO patchDAO; + private NodeDAO nodeDAO; + private QNameDAO qnameDAO; + private NodeService nodeService; + private PermissionService permissionService; + private ExtendedSecurityService extendedSecurityService; + private TransactionService transactionService; + + /** service setters */ + public void setPatchDAO(PatchDAO patchDAO) { this.patchDAO = patchDAO; } + public void setNodeDAO(NodeDAO nodeDAO) { this.nodeDAO = nodeDAO; } + public void setQnameDAO(QNameDAO qnameDAO) { this.qnameDAO = qnameDAO; } + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } + public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } + public void setExtendedSecurityService(ExtendedSecurityService extendedSecurityService) { this.extendedSecurityService = extendedSecurityService; } + public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + Map model = new HashMap(); + String batchSizeStr = req.getParameter(BATCH_SIZE); + String totalToBeProcessedRecordsStr = req.getParameter(TOTAL_NUMBER_TO_PROCESS); + + Long size = 0L; + if (StringUtils.isBlank(batchSizeStr)) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_MANDATORY); + logger.info(MESSAGE_BATCHSIZE_IS_MANDATORY); + return model; + } + try + { + size = Long.parseLong(batchSizeStr); + if(size <= 0) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_PARAMETER_BATCHSIZE_GREATER_THAN_ZERO); + logger.info(MESSAGE_PARAMETER_BATCHSIZE_GREATER_THAN_ZERO); + return model; + } + } + catch(NumberFormatException ex) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_INVALID); + logger.info(MESSAGE_BATCHSIZE_IS_INVALID); + return model; + } + final Long batchSize = size; + // get the max node id and the extended security aspect + Long maxNodeId = patchDAO.getMaxAdmNodeID(); + final Pair recordAspectPair = qnameDAO.getQName(ASPECT_EXTENDED_SECURITY); + if(recordAspectPair == null) + { + model.put(MODEL_STATUS, SUCCESS_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_NO_RECORDS_TO_PROCESS); + logger.info(MESSAGE_NO_RECORDS_TO_PROCESS); + return model; + } + + //default total number of records to be processed to batch size value + Long totalNumberOfRecordsToProcess = batchSize; + if (StringUtils.isNotBlank(totalToBeProcessedRecordsStr)) + { + try + { + totalNumberOfRecordsToProcess = Long.parseLong(totalToBeProcessedRecordsStr); + } + catch(NumberFormatException ex) + { + //do nothing here, the value will remain 0L in this case + } + } + + final Long maxRecordsToProcess = totalNumberOfRecordsToProcess; + final List processedNodes = new ArrayList(); + logger.info(MESSAGE_PROCESSING_BEGIN); + // by batch size + for (Long i = 0L; i < maxNodeId; i+=batchSize) + { + if(maxRecordsToProcess != 0 && processedNodes.size() >= maxRecordsToProcess) + { + break; + } + final Long currentIndex = i; + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // get the nodes with the extended security aspect applied + List nodeIds = patchDAO.getNodesByAspectQNameId(recordAspectPair.getFirst(), currentIndex, currentIndex + batchSize); + + // process each one + for (Long nodeId : nodeIds) + { + if(maxRecordsToProcess != 0 && processedNodes.size() >= maxRecordsToProcess) + { + break; + } + NodeRef record = nodeDAO.getNodePair(nodeId).getSecond(); + String recordName = (String) nodeService.getProperty(record, ContentModel.PROP_NAME); + logger.info(MessageFormat.format(MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE, recordName)); + processNode(record); + logger.info(MessageFormat.format(MESSAGE_PROCESSING_RECORD_END_TEMPLATE, recordName)); + processedNodes.add(record); + } + + return null; + } + }, + false, // read only + true); // requires new + } + logger.info(MESSAGE_PROCESSING_END); + int processedNodesSize = processedNodes.size(); + String message = ""; + if(totalNumberOfRecordsToProcess == 0 || (totalNumberOfRecordsToProcess > 0 && processedNodesSize < totalNumberOfRecordsToProcess)) + { + message = MessageFormat.format(MESSAGE_ALL_TEMPLATE, processedNodesSize); + } + if (totalNumberOfRecordsToProcess > 0 && totalNumberOfRecordsToProcess == processedNodesSize) + { + message = MessageFormat.format(MESSAGE_PARTIAL_TEMPLATE, totalNumberOfRecordsToProcess); + } + model.put(MODEL_STATUS, SUCCESS_STATUS); + model.put(MODEL_MESSAGE, message); + logger.info(message); + return model; + } + + /** + * Process each node + * + * @param nodeRef + */ + @SuppressWarnings({ "unchecked"}) + private void processNode(NodeRef nodeRef) + { + // get the reader/writer data + Map readers = (Map)nodeService.getProperty(nodeRef, PROP_READERS); + Map writers = (Map)nodeService.getProperty(nodeRef, PROP_WRITERS); + + // remove extended security aspect + nodeService.removeAspect(nodeRef, ASPECT_EXTENDED_SECURITY); + + // remove dynamic authority permissions + permissionService.clearPermission(nodeRef, ExtendedReaderDynamicAuthority.EXTENDED_READER); + permissionService.clearPermission(nodeRef, ExtendedWriterDynamicAuthority.EXTENDED_WRITER); + + // if record then ... + if (nodeService.hasAspect(nodeRef, ASPECT_RECORD)) + { + // re-set extended security via API + extendedSecurityService.set(nodeRef, readers.keySet(), writers.keySet()); + } + } +} diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java new file mode 100644 index 0000000000..20c6dc3e4d --- /dev/null +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java @@ -0,0 +1,253 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 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 . + * #L% + */ +/* + * 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.test.integration.issue; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.action.dm.CreateRecordAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.FileToAction; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * System test for RM-3993: Exceptions thrown when concurrently creating identical folder structure + * + * @author Silviu Dinuta + * @since 2.3.0.7 + */ +public class RM3993Test extends BaseRMTestCase +{ + private static final int NUMBER_OF_BATCHES = 4; + private static final int NUMBER_IN_BATCH = 500; + + private RuleService ruleService; + private NodeRef ruleFolder; + private NodeRef nodeRefCategory1; + + @Override + protected void initServices() + { + super.initServices(); + + ruleService = (RuleService) applicationContext.getBean("RuleService"); + } + + @Override + protected boolean isCollaborationSiteTest() + { + return true; + } + + @Override + protected boolean isRecordTest() + { + return true; + } + + /** + * Given that I have auto declare configured And that I have auto file configured to a path where only the record + * folder needs to be created When I add lots of documents in the same transaction Then the rules should fire And + * the documents should be filed in the new record folder + */ + public void testAutoDeclareAutoFileCreateRecordFolderOnly() throws Exception + { + doTestInTransaction(new Test() + { + @Override + public Void run() + { + // create the folder + ruleFolder = fileFolderService.create(documentLibrary, "mytestfolder", ContentModel.TYPE_FOLDER) + .getNodeRef(); + + // create record category + nodeRefCategory1 = filePlanService.createRecordCategory(filePlan, "category1"); + + Action action = actionService.createAction(CreateRecordAction.NAME); + action.setParameterValue(CreateRecordAction.PARAM_FILE_PLAN, filePlan); + + Rule rule = new Rule(); + rule.setRuleType(RuleType.INBOUND); + rule.setTitle("my rule"); + rule.setAction(action); + rule.setExecuteAsynchronously(true); + ruleService.saveRule(ruleFolder, rule); + + Action fileAction = actionService.createAction(FileToAction.NAME); + fileAction.setParameterValue(FileToAction.PARAM_PATH, + "/category1/{node.cm:description}"); + fileAction.setParameterValue(FileToAction.PARAM_CREATE_RECORD_PATH, true); + + Rule fileRule = new Rule(); + fileRule.setRuleType(RuleType.INBOUND); + fileRule.setTitle("my rule"); + fileRule.setAction(fileAction); + fileRule.setExecuteAsynchronously(true); + ruleService.saveRule(filePlanService.getUnfiledContainer(filePlan), fileRule); + + return null; + } + + @Override + public void test(Void result) throws Exception + { + assertFalse(ruleService.getRules(ruleFolder).isEmpty()); + } + }); + + List records = new ArrayList(NUMBER_OF_BATCHES * NUMBER_IN_BATCH); + + for (int i = 0; i < NUMBER_OF_BATCHES; i++) + { + final int finali = i; + records.addAll(doTestInTransaction(new Test>() + { + @Override + public List run() throws Exception + { + List records = new ArrayList(NUMBER_IN_BATCH); + for (int j = 0; j < NUMBER_IN_BATCH; j++) + { + int count = (finali)* NUMBER_IN_BATCH + (j + 1); + String name = "content" + count + ".txt"; + System.out.println(name + " - creating"); + + Random rand = new Random(); + int descInt = rand.nextInt(2)+1; + NodeRef record = createFile(ruleFolder, name, Integer.toString(descInt), ContentModel.TYPE_CONTENT); + records.add(record); + } + return records; + } + })); + } + + try + { + while (!records.isEmpty()) + { + Thread.sleep(1000); + + final Iterator temp = records.iterator(); + doTestInTransaction(new Test() + { + @Override + public Void run() throws Exception + { + while (temp.hasNext()) + { + NodeRef record = temp.next(); + if (nodeService.hasAspect(record, ASPECT_RECORD) && recordService.isFiled(record)) + { + String name = (String) nodeService.getProperty(record, ContentModel.PROP_NAME); + System.out.println(name + " - complete"); + temp.remove(); + } + } + + return null; + } + }); + } + } + catch (Exception exception) + { + exception.printStackTrace(); + throw exception; + } + + Integer numberOfRecords = AuthenticationUtil.runAsSystem(new RunAsWork() + { + + @Override + public Integer doWork() throws Exception + { + List containedRecordFolders = filePlanService.getContainedRecordFolders(nodeRefCategory1); + int numberOfRecords = 0; + for(NodeRef recordFolder : containedRecordFolders) + { + numberOfRecords = numberOfRecords + fileFolderService.list(recordFolder).size(); + } + return numberOfRecords; + } + }); + assertTrue(numberOfRecords == NUMBER_OF_BATCHES * NUMBER_IN_BATCH); + } + + private NodeRef createFile(NodeRef parentNodeRef, String name, String descrption, QName typeQName) + { + Map properties = new HashMap(11); + properties.put(ContentModel.PROP_NAME, (Serializable) name); + properties.put(ContentModel.PROP_DESCRIPTION, (Serializable) descrption); + QName assocQName = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName(name)); + ChildAssociationRef assocRef = nodeService.createNode( + parentNodeRef, + ContentModel.ASSOC_CONTAINS, + assocQName, + typeQName, + properties); + NodeRef nodeRef = assocRef.getChildRef(); + return nodeRef; + } +} \ No newline at end of file diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java new file mode 100644 index 0000000000..51159550ac --- /dev/null +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java @@ -0,0 +1,188 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 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 . + * #L% + */ + +package org.alfresco.module.org_alfresco_module_rm.test.integration.record; + +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.security.AccessStatus; + +/** + * Create Inplace Record Test + * + * @author Roy Wetherall + */ +public class CreateInplaceRecordTest extends BaseRMTestCase +{ + @Override + protected boolean isCollaborationSiteTest() + { + return true; + } + + /** + * Given a document in a collaboration site + * When the document is declared by a site collaborator + * Then the document becomes a record + * And the site users have the appropriate in-place permissions on the record + */ + public void testCreateInplaceRecordFromCollabSite() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + public void given() + { + // Check that the document is not a record + assertFalse("The document should not be a record", recordService.isRecord(dmDocument)); + } + + public void when() + { + // Declare the document as a record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // Declare record + recordService.createRecord(filePlan, dmDocument); + + return null; + } + }, dmCollaborator); + } + + public void then() + { + // Check that the document is a record now + assertTrue("The document should now be a record", recordService.isRecord(dmDocument)); + + // Check that the record is in the unfiled container + + // Check that the record is still a child of the collaboration folder + + // Check that the collaborator has filling permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmCollaborator); + + + // Check that the consumer has read permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmConsumer); + + } + }); + } + + public void testFileInplaceRecordFromCollabSite() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + public void given() + { + // Check that the document is not a record + assertFalse("The document should not be a record", recordService.isRecord(dmDocument)); + + // Declare the document as a record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // Declare record + recordService.createRecord(filePlan, dmDocument); + + return null; + } + }, dmCollaborator); + + // Check that the document is a record + assertTrue("The document should be a record", recordService.isRecord(dmDocument)); + assertFalse("The record should not be filed", recordService.isFiled(dmDocument)); + } + + public void when() throws FileExistsException, FileNotFoundException + { + // file the document to a location in the file plan + fileFolderService.move(dmDocument, rmFolder, null); + } + + public void then() + { + // Check that the document is a record now + assertTrue("The document should be a record", recordService.isRecord(dmDocument)); + assertTrue("The record hsould be filed", recordService.isFiled(dmDocument)); + + // Check that the record is in the unfiled container + // TODO + + // Check that the record is still a child of the collaboration folder + // TODO + + // Check that the collaborator has filling permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmCollaborator); + + + // Check that the consumer has read permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmConsumer); + + } + }); + } +} diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java index c77928b7a1..11020e2c57 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java @@ -119,24 +119,27 @@ public class FileToActionTest extends BaseRMTestCase // create record from document recordService.createRecord(filePlan, dmDocument); - // check things have gone according to plan - assertTrue(recordService.isRecord(dmDocument)); - assertFalse(recordService.isFiled(dmDocument)); - - AuthenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork() - { - public Void doWork() throws Exception - { - // is the unfiled container the primary parent of the filed record - NodeRef parent = nodeService.getPrimaryParent(dmDocument).getParentRef(); - assertEquals(filePlanService.getUnfiledContainer(filePlan), parent); - - // TODO Auto-generated method stub - return null; - }}); - return null; } + + @Override + public void test(Void result) throws Exception + { + AuthenticationUtil.runAs(() -> + { + // check things have gone according to plan + assertTrue(recordService.isRecord(dmDocument)); + assertFalse(recordService.isFiled(dmDocument)); + + // is the unfiled container the primary parent of the filed record + NodeRef parent = nodeService.getPrimaryParent(dmDocument).getParentRef(); + assertEquals(filePlanService.getUnfiledContainer(filePlan), parent); + + return null; + }, + AuthenticationUtil.getAdminUserName()); + } + }, dmCollaborator); } diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java index adb64abe8a..7f80b18167 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java @@ -291,7 +291,8 @@ public class RecordServiceImplTest extends BaseRMTestCase public void test(Void result) { - checkPermissions(READ_RECORDS, AccessStatus.DENIED, // file plan + checkPermissions(READ_RECORDS, + AccessStatus.DENIED, // file plan AccessStatus.DENIED, // unfiled container AccessStatus.DENIED, // record category AccessStatus.DENIED, // record folder @@ -300,7 +301,8 @@ public class RecordServiceImplTest extends BaseRMTestCase assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); - checkPermissions(FILING, AccessStatus.DENIED, // file plan + checkPermissions(FILING, + AccessStatus.DENIED, // file plan AccessStatus.DENIED, // unfiled container AccessStatus.DENIED, // record category AccessStatus.DENIED, // record folder diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java new file mode 100644 index 0000000000..70304fb172 --- /dev/null +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java @@ -0,0 +1,418 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 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 . + * #L% + */ +/* + * 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.repo.web.scripts.roles; + +import static java.util.Collections.emptyMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; +import org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseWebScriptUnitTest; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.extensions.webscripts.DeclarativeWebScript; + +/** + * DynamicAuthoritiesGet Unit Test + * + * @author Silviu Dinuta + */ +@SuppressWarnings("deprecation") +public class DynamicAuthoritiesGetUnitTest extends BaseWebScriptUnitTest implements RecordsManagementModel +{ + /** test data */ + private static final Long ASPECT_ID = 123l; + private static final QName ASPECT = AlfMock.generateQName(); + + /** mocks */ + @Mock + private PatchDAO mockedPatchDAO; + @Mock + private NodeDAO mockedNodeDAO; + @Mock + private QNameDAO mockedQnameDAO; + @Mock + private NodeService mockedNodeService; + @Mock + private PermissionService mockedPermissionService; + @Mock + private ExtendedSecurityService mockedExtendedSecurityService; + @Mock + private TransactionService mockedTransactionService; + @Mock + private RetryingTransactionHelper mockedRetryingTransactionHelper; + + /** test component */ + @InjectMocks + private DynamicAuthoritiesGet webScript; + + @Override + protected DeclarativeWebScript getWebScript() + { + return webScript; + } + + @Override + protected String getWebScriptTemplate() + { + return "alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl"; + } + + /** + * Before test + */ + @SuppressWarnings("unchecked") + @Before + public void before() + { + MockitoAnnotations.initMocks(this); + webScript.setNodeService(mockedNodeService); + webScript.setPermissionService(mockedPermissionService); + webScript.setExtendedSecurityService(mockedExtendedSecurityService); + // 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), anyBoolean(), anyBoolean()); + + when(mockedTransactionService.getRetryingTransactionHelper()).thenReturn(mockedRetryingTransactionHelper); + + // max node id + when(mockedPatchDAO.getMaxAdmNodeID()).thenReturn(500000L); + + // aspect + when(mockedQnameDAO.getQName(ASPECT_EXTENDED_SECURITY)).thenReturn(new Pair(ASPECT_ID, ASPECT)); + } + + /** + * Given that there are no nodes with the extended security aspect When the action is executed Nothing happens + * @throws Exception + */ + @SuppressWarnings({ "unchecked" }) + @Test + public void noNodesWithExtendedSecurity() throws Exception + { + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(Collections.emptyList()); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "3"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + + // Check the JSON result using Jackson to allow easy equality testing. + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 0 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + + + verify(mockedNodeService, never()).getProperty(any(NodeRef.class), eq(PROP_READERS)); + verify(mockedNodeService, never()).getProperty(any(NodeRef.class), eq(PROP_WRITERS)); + verify(mockedNodeService, never()).removeAspect(any(NodeRef.class), eq(ASPECT_EXTENDED_SECURITY)); + verify(mockedPermissionService, never()).clearPermission(any(NodeRef.class), + eq(ExtendedReaderDynamicAuthority.EXTENDED_READER)); + verify(mockedPermissionService, never()).clearPermission(any(NodeRef.class), + eq(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); + verify(mockedExtendedSecurityService, never()).set(any(NodeRef.class), any(Set.class), any(Set.class)); + } + + /** + * Given that there are records with the extended security aspect When the action is executed Then the aspect is + * removed And the dynamic authorities permissions are cleared And extended security is set via the updated API + * @throws Exception + */ + @SuppressWarnings("unchecked") + @Test + public void recordsWithExtendedSecurityAspect() throws Exception + { + List ids = Stream.of(1l, 2l, 3l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(true); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "4"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 3 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + + + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_READERS)); + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_WRITERS)); + verify(mockedNodeService, times(3)).removeAspect(any(NodeRef.class), eq(ASPECT_EXTENDED_SECURITY)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedReaderDynamicAuthority.EXTENDED_READER)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); + verify(mockedExtendedSecurityService, times(3)).set(any(NodeRef.class), any(Set.class), any(Set.class)); + + } + + /** + * Given that there are non-records with the extended security aspect When the web script is executed Then the aspect is + * removed And the dynamic authorities permissions are cleared + * @throws Exception + */ + @SuppressWarnings("unchecked") + @Test + public void nonRecordsWithExtendedSecurityAspect() throws Exception + { + List ids = Stream.of(1l, 2l, 3l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(false); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "4"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 3 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + + + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_READERS)); + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_WRITERS)); + verify(mockedNodeService, times(3)).removeAspect(any(NodeRef.class), eq(ASPECT_EXTENDED_SECURITY)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedReaderDynamicAuthority.EXTENDED_READER)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); + verify(mockedExtendedSecurityService, never()).set(any(NodeRef.class), any(Set.class), any(Set.class)); + } + + @Test + public void missingBatchSizeParameter() throws Exception + { + JSONObject json = executeJSONWebScript(emptyMap()); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"failed\",\"message\":\"Parameter batchsize is mandatory\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void invalidBatchSizeParameter() throws Exception + { + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "dd"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"failed\",\"message\":\"Parameter batchsize is invalid.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void batchSizeShouldBeGraterThanZero() throws Exception + { + when(mockedQnameDAO.getQName(ASPECT_EXTENDED_SECURITY)).thenReturn(null); + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "0"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"failed\",\"message\":\"Parameter batchsize should be a number greater than 0.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void extendedSecurityAspectNotCreated() throws Exception + { + when(mockedQnameDAO.getQName(ASPECT_EXTENDED_SECURITY)).thenReturn(null); + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "3"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"There where no records to be processed.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void processAllRecordsWhenMaxProcessedRecordsIsZero() throws Exception + { + List ids = Stream.of(1l, 2l, 3l,4l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(false); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "0"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 4 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void whenMaxProcessedRecordsIsMissingItDefaultsToBatchSize() throws Exception + { + List ids = Stream.of(1l, 2l, 3l, 4l, 5l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(false); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "4"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed first 4 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } +} \ No newline at end of file