From 07059aaaed380f261d47c8fb6f99b6f75a41278b Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Tue, 19 Nov 2019 20:56:26 +1100 Subject: [PATCH] Added web script to do record disposition schedule updates --- .../patch/rm-patch-v24-context.xml | 1 + .../rm-webscript-context.xml | 15 + .../rm-updaterecordschedule.get.desc.xml | 13 + .../rm-updaterecordschedule.get.json.ftl | 30 ++ .../v24/RMv24DispositionInheritancePatch.java | 127 +++--- .../schedule/UpdateRecordScheduleGet.java | 387 ++++++++++++++++++ 6 files changed, 502 insertions(+), 71 deletions(-) create mode 100644 rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.desc.xml create mode 100644 rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.json.ftl create mode 100644 rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/schedule/UpdateRecordScheduleGet.java diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/patch/rm-patch-v24-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/patch/rm-patch-v24-context.xml index 3ddc8db447..ff8119c138 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/patch/rm-patch-v24-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/patch/rm-patch-v24-context.xml @@ -21,6 +21,7 @@ + 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 d4f8b73a05..30477644fa 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 @@ -665,4 +665,19 @@ parent="rmBaseWebscript"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.desc.xml b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.desc.xml new file mode 100644 index 0000000000..7d1df216f7 --- /dev/null +++ b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.desc.xml @@ -0,0 +1,13 @@ + + Updates Record Schedules based on Hierarchical Disposition Instructions + + URL parameter maxRecordFolders is optional, and represents the maximum number of record folders that should be processed. If not specified maxRecordFolders will be set to 10000
+ URL parameter recordFolder is optional, and represents the nodeRef of a record folder who's records should be processed. If specified then maxRecordFolders will be ignored.
+ ]]> +
+ /api/rm/rm-updateRecordSchedule?maxRecordFolders={maxRecordFolders?}&recordFolder={recordFolder?} + argument + admin + required +
\ No newline at end of file diff --git a/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.json.ftl b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.json.ftl new file mode 100644 index 0000000000..2b395291a4 --- /dev/null +++ b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/schedules/rm-updaterecordschedule.get.json.ftl @@ -0,0 +1,30 @@ +<#-- + #%L + Alfresco Records Management Module + %% + Copyright (C) 2005 - 2019 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}" +} diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v24/RMv24DispositionInheritancePatch.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v24/RMv24DispositionInheritancePatch.java index 3e73564d2d..b604cec8d4 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v24/RMv24DispositionInheritancePatch.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v24/RMv24DispositionInheritancePatch.java @@ -27,18 +27,13 @@ package org.alfresco.module.org_alfresco_module_rm.patch.v24; -import java.io.Serializable; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASPECT_DISPOSITION_LIFECYCLE; +import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASPECT_DISPOSITION_PROCESSED; + +import java.util.List; -import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; -import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; import org.alfresco.module.org_alfresco_module_rm.patch.AbstractModulePatch; import org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; @@ -48,20 +43,11 @@ import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.*; - /** - * RM v2.4 patch that ensures that file plan root containers do not inherited rules, because this is no longer enforced - * in the service code anymore. - *

- * See https://issues.alfresco.com/jira/browse/RM-3154 - * - * @author Roy Wetherall - * @since 2.4 + * */ public class RMv24DispositionInheritancePatch extends AbstractModulePatch { @@ -127,65 +113,64 @@ public class RMv24DispositionInheritancePatch extends AbstractModulePatch public void applyInternal() { Long maxNodeId = nodeDAO.getMaxNodeId(); - - //int totalFolders = recordsManagementQueryDAO.getRecordFoldersWithSchedulesCount(); int batchCount = 0; - //logger.info("Folders to update: "+ totalFolders); - transactionService.getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public String execute() throws Throwable - { - qnameDAO.getOrCreateQName(ASPECT_DISPOSITION_PROCESSED); - return null; - } - }, false, true); + + qnameDAO.getOrCreateQName(ASPECT_DISPOSITION_PROCESSED); + for (Long i = 0L; i < maxNodeId; i += BATCH_SIZE) { - final Long finali = i; - int updatedRecords = transactionService.getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - public Integer execute() throws Throwable - { - int recordCount = 0; - List folders = recordsManagementQueryDAO.getRecordFoldersWithSchedules(finali, finali + BATCH_SIZE); - for (NodeRef folder : folders) - { - behaviourFilter.disableBehaviour(folder); - if (LOGGER.isDebugEnabled()) - { - logger.info("Checking folder: " + folder); - } - DispositionSchedule schedule = dispositionService.getDispositionSchedule(folder); - if (schedule.isRecordLevelDisposition()) - { - List records = recordService.getRecords(folder); - for (NodeRef record : records) - { - if (!nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE)) - { - if (LOGGER.isDebugEnabled()) - { - logger.info("updating record: " + record); - } - behaviourFilter.disableBehaviour(record); - dispositionService.updateNextDispositionAction(record, schedule); - recordCount ++; - behaviourFilter.enableBehaviour(record); - } - } - } - nodeService.addAspect(folder, ASPECT_DISPOSITION_PROCESSED, null); - behaviourFilter.enableBehaviour(folder); - } - return recordCount; - } - }, false, true); + int updatedRecords = 0; + List folders = recordsManagementQueryDAO.getRecordFoldersWithSchedules(i, i + BATCH_SIZE); + for (NodeRef folder : folders) + { + updatedRecords = updatedRecords + updateRecordFolder(folder); + } + batchCount ++; logger.info("Records updated: "+ updatedRecords); logger.info("Completed batch "+ batchCount+" of "+ (Math.ceil(maxNodeId/BATCH_SIZE)+1)); } } + + private int updateRecordFolder(final NodeRef recordFolder) + { + return transactionService.getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Integer execute() throws Throwable + { + int recordCount = 0; + + behaviourFilter.disableBehaviour(recordFolder); + if (LOGGER.isDebugEnabled()) + { + logger.info("Checking folder: " + recordFolder); + } + DispositionSchedule schedule = dispositionService.getDispositionSchedule(recordFolder); + if (schedule.isRecordLevelDisposition()) + { + List records = recordService.getRecords(recordFolder); + for (NodeRef record : records) + { + if (!nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE)) + { + if (LOGGER.isDebugEnabled()) + { + logger.info("updating record: " + record); + } + behaviourFilter.disableBehaviour(record); + dispositionService.updateNextDispositionAction(record, schedule); + recordCount ++; + behaviourFilter.enableBehaviour(record); + } + } + } + nodeService.addAspect(recordFolder, ASPECT_DISPOSITION_PROCESSED, null); + behaviourFilter.enableBehaviour(recordFolder); + + return recordCount; + } + }, false, true); + } } diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/schedule/UpdateRecordScheduleGet.java b/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/schedule/UpdateRecordScheduleGet.java new file mode 100644 index 0000000000..4a650b8b27 --- /dev/null +++ b/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/schedule/UpdateRecordScheduleGet.java @@ -0,0 +1,387 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2019 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.schedule; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; +import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO; +import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.webscripts.AbstractWebScript; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Format; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; + +/** + * Webscript used to update records that are missing their schedule information + * + * @author Roy Wetherall + */ +public class UpdateRecordScheduleGet extends AbstractWebScript implements RecordsManagementModel +{ + /** logger */ + private static Log logger = LogFactory.getLog(UpdateRecordScheduleGet.class); + + /** parameters */ + private static final String PARAM_MAX_RECORD_FOLDERS = "maxRecordFolders"; + private static final String PARAM_RECORD_FOLDER = "recordFolder"; + + private static final String SUCCESS_STATUS = "success"; + private static final String MODEL_STATUS = "responsestatus"; + private static final String MODEL_MESSAGE = "message"; + private static final String MESSAGE_ALL_TEMPLATE = "Updated {0} records with updated disposition instructions."; + + /** services */ + private NodeService nodeService; + private DispositionService dispositionService; + private RecordService recordService; + private TransactionService transactionService; + private RecordsManagementQueryDAO recordsManagementQueryDAO; + private BehaviourFilter behaviourFilter; + private NodeDAO nodeDAO; + private QNameDAO qnameDAO; + + /** service setters */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setRecordsManagementQueryDAO(RecordsManagementQueryDAO recordsManagementQueryDAO) + { + this.recordsManagementQueryDAO = recordsManagementQueryDAO; + } + + public void setRecordService(RecordService recordService) + { + this.recordService = recordService; + } + + public void setDispositionService(DispositionService dispositionService) + { + this.dispositionService = dispositionService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /** + * Build web script model + */ + protected Map buildModel(WebScriptRequest req, WebScriptResponse res) throws IOException + { + Map model = new HashMap(); + + int maxRecordFolders = getMaxRecordFolders(req); + NodeRef recordFolder = getRecordFolder(req); + + int processedRecords = 0; + + if (recordFolder != null) + { + // Process the specified record folder + processedRecords = processedRecords + updateRecordFolder(recordFolder); + } + else + { + int processedRecordFolders = 0; + int queryBatchSize = 10000; + Long maxNodeId = nodeDAO.getMaxNodeId(); + + qnameDAO.getOrCreateQName(ASPECT_DISPOSITION_PROCESSED); + + for (Long i = 0L; i < maxNodeId; i += queryBatchSize) + { + List folders = recordsManagementQueryDAO.getRecordFoldersWithSchedules(i, i + queryBatchSize); + for (NodeRef folder : folders) + { + processedRecords = processedRecords + updateRecordFolder(folder); + processedRecordFolders++; + + if (processedRecordFolders >= maxRecordFolders) + { + // stop processing since we have meet our limit + break; + } + } + + if (processedRecordFolders >= maxRecordFolders) + { + // stop processing since we have meet our limit + break; + } + } + } + + String message = MessageFormat.format(MESSAGE_ALL_TEMPLATE, processedRecords); + + model.put(MODEL_STATUS, SUCCESS_STATUS); + model.put(MODEL_MESSAGE, message); + logger.info(message); + + return model; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.web.scripts.content.StreamContent#execute(org.springframework.extensions.webscripts. + * WebScriptRequest, org.springframework.extensions.webscripts.WebScriptResponse) + */ + @Override + public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + { + // retrieve requested format + String format = req.getFormat(); + + try + { + String mimetype = getContainer().getFormatRegistry().getMimeType(req.getAgent(), format); + if (mimetype == null) + { + throw new WebScriptException("Web Script format '" + format + "' is not registered"); + } + + // construct model for script / template + Status status = new Status(); + Cache cache = new Cache(getDescription().getRequiredCache()); + + Map model = buildModel(req, res); + + if (model == null) { return; } + model.put("status", status); + model.put("cache", cache); + + Map templateModel = createTemplateParameters(req, res, model); + + // render output + int statusCode = status.getCode(); + if (statusCode != HttpServletResponse.SC_OK && !req.forceSuccessStatus()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Force success status header in response: " + req.forceSuccessStatus()); + logger.debug("Setting status " + statusCode); + } + res.setStatus(statusCode); + } + + // apply location + String location = status.getLocation(); + if (location != null && location.length() > 0) + { + if (logger.isDebugEnabled()) logger.debug("Setting location to " + location); + res.setHeader(WebScriptResponse.HEADER_LOCATION, location); + } + + // apply cache + res.setCache(cache); + + String callback = null; + if (getContainer().allowCallbacks()) + { + callback = req.getJSONCallback(); + } + if (format.equals(WebScriptResponse.JSON_FORMAT) && callback != null) + { + if (logger.isDebugEnabled()) logger.debug("Rendering JSON callback response: content type=" + + Format.JAVASCRIPT.mimetype() + ", status=" + statusCode + ", callback=" + callback); + + // NOTE: special case for wrapping JSON results in a javascript function callback + res.setContentType(Format.JAVASCRIPT.mimetype() + ";charset=UTF-8"); + res.getWriter().write((callback + "(")); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Rendering response: content type=" + mimetype + ", status=" + statusCode); + + res.setContentType(mimetype + ";charset=UTF-8"); + } + + // render response according to requested format + renderFormatTemplate(format, templateModel, res.getWriter()); + + if (format.equals(WebScriptResponse.JSON_FORMAT) && callback != null) + { + // NOTE: special case for wrapping JSON results in a javascript function callback + res.getWriter().write(")"); + } + } + catch (Throwable e) + { + if (logger.isDebugEnabled()) + { + StringWriter stack = new StringWriter(); + e.printStackTrace(new PrintWriter(stack)); + logger.debug("Caught exception; decorating with appropriate status template : " + stack.toString()); + } + + throw createStatusException(e, req, res); + } + } + + protected void renderFormatTemplate(String format, Map model, Writer writer) + { + format = (format == null) ? "" : format; + + String templatePath = getDescription().getId() + "." + format; + + if (logger.isDebugEnabled()) logger.debug("Rendering template '" + templatePath + "'"); + + renderTemplate(templatePath, model, writer); + } + + protected int getMaxRecordFolders(WebScriptRequest req) + { + String valueStr = req.getParameter(PARAM_MAX_RECORD_FOLDERS); + int value = Integer.MAX_VALUE; + if (StringUtils.isNotBlank(valueStr)) + { + try + { + value = Integer.parseInt(valueStr); + } + catch(NumberFormatException ex) + { + //do nothing here, the value will remain 0L in this case + } + } + return value; + } + + protected NodeRef getRecordFolder(WebScriptRequest req) + { + String valueStr = req.getParameter(PARAM_RECORD_FOLDER); + NodeRef value = null; + if (StringUtils.isNotBlank(valueStr)) + { + value = new NodeRef(valueStr); + } + + return value; + } + + private int updateRecordFolder(final NodeRef recordFolder) + { + return transactionService.getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Integer execute() throws Throwable + { + int recordCount = 0; + + behaviourFilter.disableBehaviour(recordFolder); + if (logger.isDebugEnabled()) + { + logger.info("Checking folder: " + recordFolder); + } + DispositionSchedule schedule = dispositionService.getDispositionSchedule(recordFolder); + if (schedule.isRecordLevelDisposition()) + { + List records = recordService.getRecords(recordFolder); + for (NodeRef record : records) + { + if (!nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE)) + { + if (logger.isDebugEnabled()) + { + logger.info("updating record: " + record); + } + behaviourFilter.disableBehaviour(record); + dispositionService.updateNextDispositionAction(record, schedule); + recordCount ++; + behaviourFilter.enableBehaviour(record); + } + } + } + nodeService.addAspect(recordFolder, ASPECT_DISPOSITION_PROCESSED, null); + behaviourFilter.enableBehaviour(recordFolder); + + return recordCount; + } + }, false, true); + } +}