Merge remote-tracking branch 'origin/release/V3.1' into merge-3.2/mergeUp

This commit is contained in:
Ross Gale
2019-12-02 08:08:06 +00:00
13 changed files with 745 additions and 44 deletions

View File

@@ -237,6 +237,14 @@ public interface DispositionService
*/
void updateNextDispositionAction(NodeRef nodeRef);
/**
* Updates the next disposition action
*
* @param nodeRef node reference
* @param dispositionSchedule the schedule to be applied
*/
void updateNextDispositionAction(NodeRef nodeRef, DispositionSchedule dispositionSchedule);
/**
* Refreshes the disposition action details of the given node.
*

View File

@@ -977,8 +977,36 @@ public class DispositionServiceImpl extends ServiceBaseImpl
public Void doWork()
{
// Get this disposition instructions for the node
DispositionSchedule di = getDispositionSchedule(nodeRef);
if (di != null)
DispositionSchedule dispositionSchedule = getDispositionSchedule(nodeRef);
updateNextDispositionAction(nodeRef, dispositionSchedule);
return null;
}
};
AuthenticationUtil.runAsSystem(runAsWork);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#updateNextDispositionAction(NodeRef)
*/
@Override
public void updateNextDispositionAction(final NodeRef nodeRef, final DispositionSchedule dispositionSchedule)
{
RunAsWork<Void> runAsWork = new RunAsWork<Void>()
{
/**
* @see org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork#doWork()
*/
@Override
public Void doWork()
{
if (dispositionSchedule != null)
{
// Get the current action node
NodeRef currentDispositionAction = null;
@@ -997,7 +1025,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
nodeService.moveNode(currentDispositionAction, nodeRef, ASSOC_DISPOSITION_ACTION_HISTORY, ASSOC_DISPOSITION_ACTION_HISTORY);
}
List<DispositionActionDefinition> dispositionActionDefinitions = di.getDispositionActionDefinitions();
List<DispositionActionDefinition> dispositionActionDefinitions = dispositionSchedule.getDispositionActionDefinitions();
DispositionActionDefinition currentDispositionActionDefinition = null;
DispositionActionDefinition nextDispositionActionDefinition = null;
@@ -1013,14 +1041,14 @@ public class DispositionServiceImpl extends ServiceBaseImpl
{
// Get the current action
String currentADId = (String) nodeService.getProperty(currentDispositionAction, PROP_DISPOSITION_ACTION_ID);
currentDispositionActionDefinition = di.getDispositionActionDefinition(currentADId);
currentDispositionActionDefinition = dispositionSchedule.getDispositionActionDefinition(currentADId);
// When the record has multiple disposition schedules the current disposition action may not be found by id
// In this case it will be searched by name
if(currentDispositionActionDefinition == null)
{
String currentADName = (String) nodeService.getProperty(currentDispositionAction, PROP_DISPOSITION_ACTION);
currentDispositionActionDefinition = di.getDispositionActionDefinitionByName(currentADName);
currentDispositionActionDefinition = dispositionSchedule.getDispositionActionDefinitionByName(currentADName);
}
// Get the next disposition action

View File

@@ -288,9 +288,9 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel
QName PROP_COUNT = QName.createQName(RM_URI, "count");
QName ASPECT_SAVED_SEARCH = QName.createQName(RM_URI, "savedSearch");
//Workaround for RM-6788
String GL_URI = "http://www.alfresco.org/model/glacier/1.0";
QName ASPECT_ARCHIVED = QName.createQName(GL_URI, "archived");
QName ASPECT_DISPOSITION_PROCESSED = QName.createQName(RM_URI, "dispositionProcessed");
}

View File

@@ -0,0 +1,88 @@
/*
* #%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%
*/
package org.alfresco.module.org_alfresco_module_rm.query;
/**
* NodeRef Entity - used by {@link RecordsManagementQueryDAOImpl}.
*
* @author Tom Page
* @since 2.5.0.4
*/
public class NodeRefEntity
{
private Long num;
private String protocol;
private String identifier;
private String uuid;
/**
* Default constructor.
*/
public NodeRefEntity()
{
}
public Long getNum()
{
return num;
}
public void setNum(Long num)
{
this.num = num;
}
public String getProtocol()
{
return protocol;
}
public void setProtocol(String protocol)
{
this.protocol = protocol;
}
public String getIdentifier()
{
return identifier;
}
public void setIdentifier(String identifier)
{
this.identifier = identifier;
}
public String getUuid()
{
return uuid;
}
public void setUuid(String uuid)
{
this.uuid = uuid;
}
}

View File

@@ -27,9 +27,11 @@
package org.alfresco.module.org_alfresco_module_rm.query;
import java.util.List;
import java.util.Set;
import org.alfresco.service.cmr.repository.NodeRef;
import java.util.Collection;
import org.alfresco.service.namespace.QName;
@@ -54,6 +56,17 @@ public interface RecordsManagementQueryDAO
int getCountRmaIdentifier(String identifierValue);
/**
* Returns a number of nodeRefs for record folders in the system
* that have the property recordSearchHasDispositionSchedule:true
* (used for MNT-20864)
* @param start long - the first result row to return
* @param end long - the last result row to return
* @return list of node refs
*/
List<NodeRef> getRecordFoldersWithSchedules(Long start, Long end);
/**
* Returns whether a given node contains children with one of the given values for the given property
* Returns distinct property values from children for the given property
*
* @param parent the parent to evaluate
@@ -63,7 +76,7 @@ public interface RecordsManagementQueryDAO
public Set<String> getChildrenStringPropertyValues(NodeRef parent, QName property);
/**
* @param contentUrl the URL of the content url entity
* @param contentUrl the URL of the content url entity
* @return Set<NodeRef> a set of nodes that reference the given content url
*/
Set<NodeRef> getNodeRefsWhichReferenceContentUrl(String contentUrl);

View File

@@ -27,8 +27,11 @@
package org.alfresco.module.org_alfresco_module_rm.query;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -56,33 +59,43 @@ import org.mybatis.spring.SqlSessionTemplate;
*/
public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO, RecordsManagementModel
{
/** logger */
@SuppressWarnings ("unused")
/**
* logger
*/
@SuppressWarnings("unused")
private static final Log logger = LogFactory.getLog(RecordsManagementQueryDAOImpl.class);
/** query names */
/**
* query names
*/
private static final String COUNT_IDENTIFIER = "alfresco.query.rm.select_CountRMIndentifier";
private static final String GET_CHILDREN_PROPERTY_VALUES = "select_GetStringPropertyValuesOfChildren";
private static final String SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL = "select_NodeIdsWhichReferenceContentUrl";
private static final String SCHEDULED_FOLDERS = "alfresco.query.rm.select_RecordFoldersWithSchedules";
private static final String SCHEDULED_FOLDERS_COUNT = "alfresco.query.rm.select_RecordFoldersWithSchedulesCount";
/** SQL session template */
/**
* SQL session template
*/
protected SqlSessionTemplate template;
/** QName DAO */
/**
* QName DAO
*/
protected QNameDAO qnameDAO;
protected NodeDAO nodeDAO;
protected TenantService tenantService;
/**
* @param sqlSessionTemplate SQL session template
* @param sqlSessionTemplate SQL session template
*/
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
{
this.template = sqlSessionTemplate;
}
/**
* @param qnameDAO qname DAO
* @param qnameDAO qname DAO
*/
public final void setQnameDAO(QNameDAO qnameDAO)
{
@@ -106,25 +119,25 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
public int getCountRmaIdentifier(String identifierValue)
{
int result = 0;
// lookup the id of the identifier property qname
Pair<Long, QName> pair = qnameDAO.getQName(PROP_IDENTIFIER);
if (pair != null)
{
{
// create query params
Map<String, Object> params = new HashMap<>(2);
params.put("qnameId", pair.getFirst());
params.put("idValue", identifierValue);
// return the number of rma identifiers found that match the passed value
Integer count = (Integer)template.selectOne(COUNT_IDENTIFIER, params);
Integer count = (Integer) template.selectOne(COUNT_IDENTIFIER, params);
if (count != null)
{
result = count;
}
}
return result;
}
@@ -158,8 +171,8 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
/**
* Get a set of node reference which reference the provided content URL
*
* @param String contentUrl content URL
* @return Set<NodeRef> set of nodes that reference the provided content URL
* @param String contentUrl content URL
*/
@Override
public Set<NodeRef> getNodeRefsWhichReferenceContentUrl(String contentUrl)
@@ -208,14 +221,16 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
if (logger.isDebugEnabled())
{
logMessage.append(nodeRefToAdd).append(" (from version)");
logMessage.append(nodeRefToAdd)
.append(" (from version)");
}
}
// add the node ref of the referencing node
else
{
nodeRefToAdd = nodeDAO.getNodeIdStatus(nodeId).getNodeRef();
nodeRefToAdd = nodeDAO.getNodeIdStatus(nodeId)
.getNodeRef();
if (logger.isDebugEnabled())
{
logMessage.append(nodeRefToAdd);
@@ -240,4 +255,33 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
return nodesReferencingContentUrl;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO#getRecordFoldersWithSchedules(Long, Long)
*/
@Override
public List<NodeRef> getRecordFoldersWithSchedules(Long start, Long end)
{
Map<String, Object> params = new HashMap<>(2);
params.put("processed", qnameDAO.getQName(ASPECT_DISPOSITION_PROCESSED)
.getFirst());
params.put("folderQnameId", qnameDAO.getQName(TYPE_RECORD_FOLDER)
.getFirst());
params.put("start", start);
params.put("end", end);
List<NodeRefEntity> entities = template.selectList(SCHEDULED_FOLDERS, params);
List<NodeRef> results = new ArrayList<>();
// convert the entities to NodeRefs
for (NodeRefEntity nodeRefEntity : entities)
{
results.add(
new NodeRef(nodeRefEntity.getProtocol(), nodeRefEntity.getIdentifier(), nodeRefEntity.getUuid()));
}
return results;
}
}

View File

@@ -0,0 +1,430 @@
/*
* #%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.security.authentication.AuthenticationUtil;
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 from {1} folders with updated disposition instructions.";
private static final String MESSAGE_FOLDER_TEMPLATE = "Updated records in folder {0} 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<>();
transactionService.getRetryingTransactionHelper()
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
qnameDAO.getOrCreateQName(ASPECT_DISPOSITION_PROCESSED);
return null;
}
}, false, true);
int maxRecordFolders = getMaxRecordFolders(req);
NodeRef recordFolder = getRecordFolder(req);
int processedRecords = 0;
String message;
if (recordFolder != null)
{
// Process the specified record folder
updateRecordFolder(recordFolder);
message = MessageFormat.format(MESSAGE_FOLDER_TEMPLATE, recordFolder);
}
else
{
int processedRecordFolders = 0;
int queryBatchSize = 10000;
Long maxNodeId = nodeDAO.getMaxNodeId();
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;
}
}
message = MessageFormat.format(MESSAGE_ALL_TEMPLATE, processedRecords, processedRecordFolders);
}
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(ASPECT_FILE_PLAN_COMPONENT);
try
{
if (logger.isDebugEnabled())
{
logger.info("Checking folder: " + recordFolder);
}
recordCount = AuthenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Integer>()
{
@Override
public Integer doWork() throws Exception
{
DispositionSchedule schedule = dispositionService.getDispositionSchedule(recordFolder);
int innerRecordCount = 0;
if (schedule != null && schedule.isRecordLevelDisposition())
{
List<NodeRef> records = recordService.getRecords(recordFolder);
for (NodeRef record : records)
{
if (!nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE))
{
if (recordFolder.equals(nodeService.getPrimaryParent(record).getParentRef()))
{
if (logger.isDebugEnabled())
{
logger.info("updating record: " + record);
}
// update record disposition information
dispositionService.updateNextDispositionAction(record, schedule);
innerRecordCount++;
}
}
}
}
return innerRecordCount;
}
});
nodeService.addAspect(recordFolder, ASPECT_DISPOSITION_PROCESSED, null);
}
finally
{
behaviourFilter.enableBehaviour(ASPECT_FILE_PLAN_COMPONENT);
}
return recordCount;
}
}, false, true);
}
}