Added web script to do record disposition schedule updates

This commit is contained in:
Roy Wetherall
2019-11-19 20:56:26 +11:00
parent 239f817286
commit 07059aaaed
6 changed files with 502 additions and 71 deletions

View File

@@ -21,6 +21,7 @@
<property name="description" value="Fix inheritance for existing records in a hold." /> <property name="description" value="Fix inheritance for existing records in a hold." />
<property name="fixesToSchema" value="2501" /> <property name="fixesToSchema" value="2501" />
<property name="targetSchema" value="2502" /> <property name="targetSchema" value="2502" />
<property name="txnReadOnly" value="false" />
<property name="dispositionService" ref="DispositionService" /> <property name="dispositionService" ref="DispositionService" />
<property name="recordService" ref="RecordService" /> <property name="recordService" ref="RecordService" />
<property name="nodeService" ref="nodeService" /> <property name="nodeService" ref="nodeService" />

View File

@@ -665,4 +665,19 @@
parent="rmBaseWebscript"> parent="rmBaseWebscript">
<property name="relationshipService" ref="RelationshipService" /> <property name="relationshipService" ref="RelationshipService" />
</bean> </bean>
<!-- Remove Dynamic Authorities GET webscript -->
<bean id="webscript.org.alfresco.repository.schedules.rm-updaterecordschedule.get"
class="org.alfresco.repo.web.scripts.schedule.UpdateRecordScheduleGet"
parent="webscript">
<property name="nodeDAO" ref="nodeDAO"/>
<property name="qnameDAO" ref="qnameDAO"/>
<property name="nodeService" ref="nodeService"/>
<property name="transactionService" ref="transactionService"/>
<property name="dispositionService" ref="dispositionService"/>
<property name="recordService" ref="recordService"/>
<property name="recordsManagementQueryDAO" ref="recordsManagementQueryDAO"/>
<property name="behaviourFilter" ref="policyBehaviourFilter" />
</bean>
</beans> </beans>

View File

@@ -0,0 +1,13 @@
<webscript>
<shortname>Updates Record Schedules based on Hierarchical Disposition Instructions</shortname>
<description><![CDATA[
Updates records schedules by reviewing disposition instructions defined withing the principle hierarchy.<br/>
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<br/>
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.<br/>
]]>
</description>
<url>/api/rm/rm-updateRecordSchedule?maxRecordFolders={maxRecordFolders?}&amp;recordFolder={recordFolder?}</url>
<format default="json">argument</format>
<authentication>admin</authentication>
<transaction allow="readonly">required</transaction>
</webscript>

View File

@@ -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 <http://www.gnu.org/licenses/>.
#L%
-->
{
"responsestatus" : "${responsestatus?json_string}",
"message" : "${message?json_string}"
}

View File

@@ -27,18 +27,13 @@
package org.alfresco.module.org_alfresco_module_rm.patch.v24; package org.alfresco.module.org_alfresco_module_rm.patch.v24;
import java.io.Serializable; import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASPECT_DISPOSITION_LIFECYCLE;
import java.math.RoundingMode; import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASPECT_DISPOSITION_PROCESSED;
import java.util.ArrayList;
import java.util.Date; import java.util.List;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.DispositionSchedule;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; 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.patch.AbstractModulePatch;
import org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO; import org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService; 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.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.
* <p>
* See https://issues.alfresco.com/jira/browse/RM-3154
* *
* @author Roy Wetherall
* @since 2.4
*/ */
public class RMv24DispositionInheritancePatch extends AbstractModulePatch public class RMv24DispositionInheritancePatch extends AbstractModulePatch
{ {
@@ -127,40 +113,43 @@ public class RMv24DispositionInheritancePatch extends AbstractModulePatch
public void applyInternal() public void applyInternal()
{ {
Long maxNodeId = nodeDAO.getMaxNodeId(); Long maxNodeId = nodeDAO.getMaxNodeId();
//int totalFolders = recordsManagementQueryDAO.getRecordFoldersWithSchedulesCount();
int batchCount = 0; int batchCount = 0;
//logger.info("Folders to update: "+ totalFolders);
transactionService.getRetryingTransactionHelper()
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
qnameDAO.getOrCreateQName(ASPECT_DISPOSITION_PROCESSED); qnameDAO.getOrCreateQName(ASPECT_DISPOSITION_PROCESSED);
return null;
}
}, false, true);
for (Long i = 0L; i < maxNodeId; i += BATCH_SIZE) for (Long i = 0L; i < maxNodeId; i += BATCH_SIZE)
{ {
final Long finali = i; int updatedRecords = 0;
int updatedRecords = transactionService.getRetryingTransactionHelper() List<NodeRef> 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<Integer>() .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Integer>()
{ {
public Integer execute() throws Throwable public Integer execute() throws Throwable
{ {
int recordCount = 0; int recordCount = 0;
List<NodeRef> folders = recordsManagementQueryDAO.getRecordFoldersWithSchedules(finali, finali + BATCH_SIZE);
for (NodeRef folder : folders) behaviourFilter.disableBehaviour(recordFolder);
{
behaviourFilter.disableBehaviour(folder);
if (LOGGER.isDebugEnabled()) if (LOGGER.isDebugEnabled())
{ {
logger.info("Checking folder: " + folder); logger.info("Checking folder: " + recordFolder);
} }
DispositionSchedule schedule = dispositionService.getDispositionSchedule(folder); DispositionSchedule schedule = dispositionService.getDispositionSchedule(recordFolder);
if (schedule.isRecordLevelDisposition()) if (schedule.isRecordLevelDisposition())
{ {
List<NodeRef> records = recordService.getRecords(folder); List<NodeRef> records = recordService.getRecords(recordFolder);
for (NodeRef record : records) for (NodeRef record : records)
{ {
if (!nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE)) if (!nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE))
@@ -176,16 +165,12 @@ public class RMv24DispositionInheritancePatch extends AbstractModulePatch
} }
} }
} }
nodeService.addAspect(folder, ASPECT_DISPOSITION_PROCESSED, null); nodeService.addAspect(recordFolder, ASPECT_DISPOSITION_PROCESSED, null);
behaviourFilter.enableBehaviour(folder); behaviourFilter.enableBehaviour(recordFolder);
}
return recordCount; return recordCount;
} }
}, false, true); }, false, true);
batchCount ++;
logger.info("Records updated: "+ updatedRecords);
logger.info("Completed batch "+ batchCount+" of "+ (Math.ceil(maxNodeId/BATCH_SIZE)+1));
}
} }
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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 <http://www.gnu.org/licenses/>.
*/
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<String, Object> buildModel(WebScriptRequest req, WebScriptResponse res) throws IOException
{
Map<String, Object> model = new HashMap<String, Object>();
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<NodeRef> 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<String, Object> model = buildModel(req, res);
if (model == null) { return; }
model.put("status", status);
model.put("cache", cache);
Map<String, Object> 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<String, Object> 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<Integer>()
{
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<NodeRef> 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);
}
}