/*
* #%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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.module.org_alfresco_module_rm.notification;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService;
import org.alfresco.module.org_alfresco_module_rm.role.Role;
import org.alfresco.repo.notification.EMailNotificationProvider;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.service.cmr.notification.NotificationContext;
import org.alfresco.service.cmr.notification.NotificationService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Helper bean containing methods useful when sending records
* management notifications via the {@link NotificationService}
*
* @author Roy Wetherall
*/
public class RecordsManagementNotificationHelper implements RecordsManagementModel
{
private static Log logger = LogFactory.getLog(RecordsManagementNotificationHelper.class);
/** I18n */
private static final String MSG_SUBJECT_RECORDS_DUE_FOR_REVIEW = "notification.dueforreview.subject";
private static final String MSG_SUBJECT_RECORD_SUPERCEDED = "notification.superseded.subject";
private static final String MSG_SUBJECT_RECORD_REJECTED = "notification.rejected.subject";
/** Defaults */
private static final String DEFAULT_SITE = "rm";
/** Services */
private NotificationService notificationService;
private FilePlanRoleService filePlanRoleService;
private SearchService searchService;
private NamespaceService namespaceService;
private SiteService siteService;
private AuthorityService authorityService;
private TenantAdminService tenantAdminService;
private NodeService nodeService;
private FilePlanService filePlanService;
/** Notification role */
private String notificationRole;
/** EMail notification templates */
private NodeRef supersededTemplate = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "record_superseded_template");
private NodeRef dueForReviewTemplate;
private NodeRef rejectedTemplate = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "record_rejected_template");
/**
* @param notificationService notification service
*/
public void setNotificationService(NotificationService notificationService)
{
this.notificationService = notificationService;
}
/**
* @param filePlanService file plan service
*/
public void setFilePlanService(FilePlanService filePlanService)
{
this.filePlanService = filePlanService;
}
/**
* @param filePlanRoleService file plan role service
*/
public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService)
{
this.filePlanRoleService = filePlanRoleService;
}
/**
* @param notificationRole rm notification role
*/
public void setNotificationRole(String notificationRole)
{
this.notificationRole = notificationRole;
}
/**
* @param searchService search service
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* @param namespaceService namespace service
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* @param siteService site service
*/
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
/**
* @param authorityService authority service
*/
public void setAuthorityService(AuthorityService authorityService)
{
this.authorityService = authorityService;
}
/**
* @param nodeService node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param tenantAdminService tenant admin service
*/
public void setTenantAdminService(TenantAdminService tenantAdminService)
{
this.tenantAdminService = tenantAdminService;
}
/**
* @return superseded email template
*/
public NodeRef getSupersededTemplate()
{
return supersededTemplate;
}
/**
* @return rejected email template
*/
public NodeRef getRejectedTemplate()
{
return rejectedTemplate;
}
/**
* @return due for review email template
*/
public NodeRef getDueForReviewTemplate()
{
if (dueForReviewTemplate == null)
{
List<NodeRef> nodeRefs =
searchService.selectNodes(
getRootNode(),
"app:company_home/app:dictionary/cm:records_management/cm:records_management_email_templates/cm:notify-records-due-for-review-email.ftl", null,
namespaceService,
false);
if (nodeRefs.size() == 1)
{
dueForReviewTemplate = nodeRefs.get(0);
}
}
return dueForReviewTemplate;
}
/**
* Helper method to get root node in a tenant safe way.
*
* @return NodeRef root node of spaces store
*/
private NodeRef getRootNode()
{
String tenantDomain = tenantAdminService.getCurrentUserDomain();
return TenantUtil.runAsSystemTenant(new TenantRunAsWork<NodeRef>()
{
public NodeRef doWork()
{
return nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
}
}, tenantDomain);
}
/**
* Sends records due for review email notification.
*
* @param records records due for review
*/
public void recordsDueForReviewEmailNotification(final List<NodeRef> records)
{
ParameterCheck.mandatory("records", records);
if (!records.isEmpty())
{
if (nodeService.hasAspect(records.get(0), RecordsManagementModel.ASPECT_RECORD))
{
NodeRef root = getRMRoot(records.get(0));
String groupName = getGroupName(root);
if (doesGroupContainUsers(groupName))
{
NotificationContext notificationContext = new NotificationContext();
notificationContext.setSubject(I18NUtil.getMessage(MSG_SUBJECT_RECORDS_DUE_FOR_REVIEW));
notificationContext.setAsyncNotification(false);
notificationContext.setIgnoreNotificationFailure(true);
notificationContext.setBodyTemplate(getDueForReviewTemplate().toString());
Map<String, Serializable> args = new HashMap<String, Serializable>(1, 1.0f);
args.put("records", (Serializable) records);
args.put("site", getSiteName(root));
notificationContext.setTemplateArgs(args);
notificationContext.addTo(groupName);
notificationService.sendNotification(EMailNotificationProvider.NAME, notificationContext);
}
else
{
if (logger.isWarnEnabled())
{
logger.warn("Unable to send record due for review email notification, because notification group was empty.");
}
throw new AlfrescoRuntimeException(
"Unable to send record due for review email notification, because notification group was empty.");
}
}
}
}
/**
* Sends record superseded email notification.
*
* @param record superseded record
*/
public void recordSupersededEmailNotification(final NodeRef record)
{
ParameterCheck.mandatory("record", record);
NodeRef root = getRMRoot(record);
String groupName = getGroupName(root);
if (doesGroupContainUsers(groupName))
{
NotificationContext notificationContext = new NotificationContext();
notificationContext.setSubject(I18NUtil.getMessage(MSG_SUBJECT_RECORD_SUPERCEDED));
notificationContext.setAsyncNotification(false);
notificationContext.setIgnoreNotificationFailure(true);
notificationContext.setBodyTemplate(supersededTemplate.toString());
Map<String, Serializable> args = new HashMap<String, Serializable>(1, 1.0f);
args.put("record", record);
args.put("site", getSiteName(root));
notificationContext.setTemplateArgs(args);
notificationContext.addTo(groupName);
notificationService.sendNotification(EMailNotificationProvider.NAME, notificationContext);
}
else
{
if (logger.isWarnEnabled())
{
logger.warn("Unable to send record superseded email notification, because notification group was empty.");
}
}
}
/**
* Sends record rejected email notification.
*
* @param record rejected record
*/
public void recordRejectedEmailNotification(NodeRef record, String recordId, String recordCreator)
{
ParameterCheck.mandatory("record", record);
if (canSendRejectEmail(record, recordCreator))
{
String site = siteService.getSite(record).getShortName();
String rejectReason = (String) nodeService.getProperty(record, PROP_RECORD_REJECTION_REASON);
String rejectedPerson = (String) nodeService.getProperty(record, PROP_RECORD_REJECTION_USER_ID);
Date rejectDate = (Date) nodeService.getProperty(record, PROP_RECORD_REJECTION_DATE);
String recordName = (String) nodeService.getProperty(record, ContentModel.PROP_NAME);
Map<String, Serializable> args = new HashMap<String, Serializable>(8);
args.put("record", record);
args.put("site", site);
args.put("recordCreator", recordCreator);
args.put("rejectReason", rejectReason);
args.put("rejectedPerson", rejectedPerson);
args.put("rejectDate", rejectDate);
args.put("recordId", recordId);
args.put("recordName", recordName);
NotificationContext notificationContext = new NotificationContext();
notificationContext.setAsyncNotification(true);
notificationContext.setIgnoreNotificationFailure(true);
notificationContext.addTo(recordCreator);
notificationContext.setSubject(I18NUtil.getMessage(MSG_SUBJECT_RECORD_REJECTED));
notificationContext.setBodyTemplate(getRejectedTemplate().toString());
notificationContext.setTemplateArgs(args);
notificationService.sendNotification(EMailNotificationProvider.NAME, notificationContext);
}
}
/**
* Helper method to check if the mandatory properties are set
*
* @param record rejected record
*/
private boolean canSendRejectEmail(NodeRef record, String recordCreator)
{
boolean result = true;
String msg1 = "Unable to send record rejected email notification, because ";
String msg2 = " could not be found!";
if (siteService.getSite(record) == null)
{
result = false;
logger.warn(msg1 + "the site which should contain the node '" + record.toString() + "'" + msg2);
}
if (StringUtils.isBlank(recordCreator))
{
result = false;
logger.warn(msg1 + "the user, who created the record" + msg2);
}
if (StringUtils.isBlank((String) nodeService.getProperty(record, PROP_RECORD_REJECTION_REASON)))
{
result = false;
logger.warn(msg1 + "the reason for rejection" + msg2);
}
if (StringUtils.isBlank((String) nodeService.getProperty(record, PROP_RECORD_REJECTION_USER_ID)))
{
result = false;
logger.warn(msg1 + "the user, who rejected the record" + msg2);
}
if (((Date) nodeService.getProperty(record, PROP_RECORD_REJECTION_DATE)) == null)
{
result = false;
logger.warn(msg1 + "the date, when the record was rejected" + msg2);
}
return result;
}
/**
* Gets the rm root given a context node.
*
* @param context context node reference
* @return {@link NodeRef} rm root node reference
*/
private NodeRef getRMRoot(final NodeRef context)
{
return AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
{
@Override
public NodeRef doWork()
{
return filePlanService.getFilePlan(context);
}
}, AuthenticationUtil.getSystemUserName());
}
/**
* Gets the group name for the notification role.
*
* @param root rm root node
* @return String notification role's group name
*/
private String getGroupName(final NodeRef root)
{
return AuthenticationUtil.runAs(new RunAsWork<String>()
{
@Override
public String doWork()
{
// Find the authority for the given role
Role role = filePlanRoleService.getRole(root, notificationRole);
return role.getRoleGroupName();
}
}, AuthenticationUtil.getSystemUserName());
}
private boolean doesGroupContainUsers(final String groupName)
{
return AuthenticationUtil.runAs(new RunAsWork<Boolean>()
{
@Override
public Boolean doWork() throws Exception
{
Set<String> users = authorityService.getContainedAuthorities(AuthorityType.USER, groupName, true);
return !users.isEmpty();
}
}, AuthenticationUtil.getSystemUserName());
}
/**
* Get the site name, default if none/undetermined.
*
* @param root rm root
* @return String site name
*/
private String getSiteName(final NodeRef root)
{
return AuthenticationUtil.runAs(new RunAsWork<String>()
{
@Override
public String doWork()
{
String result = DEFAULT_SITE;
SiteInfo siteInfo = siteService.getSite(root);
if (siteInfo != null)
{
result = siteInfo.getShortName();
}
return result;
}
}, AuthenticationUtil.getSystemUserName());
}
}