/*
*
* Copyright 2013 Entando S.r.l. (http://www.entando.com) All rights reserved.
*
* This file is part of Entando software.
* Entando is a free software;
* You can redistribute it and/or modify it
* under the terms of the GNU General Public License (GPL) as published by the Free Software Foundation; version 2.
*
* See the file License for the specific language governing permissions
* and limitations under the License
*
*
*
* Copyright 2013 Entando S.r.l. (http://www.entando.com) All rights reserved.
*
*/
package com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import com.agiletec.aps.system.ApsSystemUtils;
import com.agiletec.aps.system.SystemConstants;
import com.agiletec.aps.system.common.AbstractService;
import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute;
import com.agiletec.aps.system.exception.ApsSystemException;
import com.agiletec.aps.system.services.authorization.IApsAuthority;
import com.agiletec.aps.system.services.authorization.IAuthorizationManager;
import com.agiletec.aps.system.services.authorization.authorizator.IApsAuthorityManager;
import com.agiletec.aps.system.services.baseconfig.ConfigInterface;
import com.agiletec.aps.system.services.role.IRoleManager;
import com.agiletec.aps.system.services.role.Permission;
import com.agiletec.aps.system.services.role.Role;
import com.agiletec.aps.system.services.user.IAuthenticationProviderManager;
import com.agiletec.aps.system.services.user.IUserManager;
import com.agiletec.aps.util.DateConverter;
import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager;
import com.agiletec.plugins.jacms.aps.system.services.content.model.Content;
import com.agiletec.plugins.jpcontentworkflow.aps.system.JpcontentworkflowSystemConstants;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier.model.ContentStatusChangedEventInfo;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier.model.NotifierConfig;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier.parse.WorkflowNotifierDOM;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier.scheduler.MailSenderTask;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier.scheduler.Scheduler;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.notifier.scheduler.Task;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.workflow.IContentWorkflowManager;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.workflow.model.Step;
import com.agiletec.plugins.jpcontentworkflow.aps.system.services.workflow.model.Workflow;
import com.agiletec.plugins.jpmail.aps.services.mail.IMailManager;
import org.entando.entando.aps.system.services.userprofile.IUserProfileManager;
import org.entando.entando.aps.system.services.userprofile.model.IUserProfile;
@Aspect
public class WorkflowNotifierManager extends AbstractService implements IWorkflowNotifierManager {
@Override
public void init() throws Exception {
this.loadConfigs();
this.openScheduler();
ApsSystemUtils.getLogger().debug(this.getName() + ": inizializzato " +
"servizio notificatore cambiamento stato contenuti");
}
@Override
public void release() {
this.closeScheduler();
}
@Override
public void destroy() {
this.release();
super.destroy();
}
protected void loadConfigs() throws ApsSystemException {
try {
ConfigInterface configManager = this.getConfigManager();
String xml = configManager.getConfigItem(JpcontentworkflowSystemConstants.WORKFLOW_NOTIFIER_CONFIG_ITEM);
if (xml == null) {
throw new ApsSystemException("Configuration item not present: " + JpcontentworkflowSystemConstants.WORKFLOW_NOTIFIER_CONFIG_ITEM);
}
WorkflowNotifierDOM configDOM = new WorkflowNotifierDOM();
this.setNotifierConfig(configDOM.extractConfig(xml));
} catch (Throwable t) {
ApsSystemUtils.logThrowable(t, this, "loadConfigs");
throw new ApsSystemException("Errore in fase di inizializzazione", t);
}
}
@Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.saveContent(..)) && args(content,..)")
public void listenContentSaving(Object content) {
try {
boolean notify = true;
Content currentContent = (Content) content;
String contentId = currentContent.getId();
if (null != contentId) {
Content previousContent = this.getContentManager().loadContent(contentId, false);
if (previousContent.getStatus().equals(currentContent.getStatus())) {
notify = false;
}
}
if (notify) {
this.saveContentStatusChanged(currentContent);
}
} catch (Throwable t) {
ApsSystemUtils.logThrowable(t, this, "traceContent", "Error notifying content status change");
}
}
@Override
public NotifierConfig getNotifierConfig() {
return _notifierConfig.clone();
}
protected NotifierConfig getWorkflowNotifierConfig() {
return _notifierConfig;
}
protected void setNotifierConfig(NotifierConfig notifierConfig) {
this._notifierConfig = notifierConfig;
}
@Override
public void saveNotifierConfig(NotifierConfig notifierConfig) throws ApsSystemException {
try {
WorkflowNotifierDOM configDOM = new WorkflowNotifierDOM();
String xml = configDOM.createConfigXml(notifierConfig);
this.getConfigManager().updateConfigItem(JpcontentworkflowSystemConstants.WORKFLOW_NOTIFIER_CONFIG_ITEM, xml);
this.setNotifierConfig(notifierConfig);
this.closeScheduler();
this.openScheduler();
} catch (Throwable t) {
ApsSystemUtils.logThrowable(t, this, "saveNotifierConfig");
throw new ApsSystemException("Errore in fase di salvataggio configurazione notificatore", t);
}
}
protected void saveContentStatusChanged(Content content) {
if (this.getWorkflowNotifierConfig().isActive()) {
try {
ContentStatusChangedEventInfo statusChangedInfo = new ContentStatusChangedEventInfo();
statusChangedInfo.setContentId(content.getId());
statusChangedInfo.setContentTypeCode(content.getTypeCode());
statusChangedInfo.setContentDescr(content.getDescr());
statusChangedInfo.setMainGroup(content.getMainGroup());
statusChangedInfo.setDate(new Date());
statusChangedInfo.setStatus(content.getStatus());
this.getNotifierDAO().saveContentEvent(statusChangedInfo);
} catch (Throwable t) {
ApsSystemUtils.logThrowable(t, this, "saveContentStatusChanged",
"Error saving content status changed event" + content.getId());
}
}
}
/**
* Apre lo scheduler istanziando il task relativo
* alla spedizione degli sms con i rilevamenti meteo.
*/
protected void openScheduler() {
this.closeScheduler();
NotifierConfig notifierConfig = this.getWorkflowNotifierConfig();
if (notifierConfig.isActive()) {
long milliSecondsDelay = notifierConfig.getHoursDelay() * 3600000l; // x minuti secondi millisecondi
Date startTime = notifierConfig.getStartScheduler();
startTime = this.calculateStartTime(startTime, milliSecondsDelay);
Task task = new MailSenderTask(this);
this._mailSenderScheduler = new Scheduler(task, startTime, milliSecondsDelay);
}
}
private Date calculateStartTime(Date startTime, long delay) {
Date current = new Date();
long waitTime = current.getTime() - startTime.getTime();
if (waitTime > 0) {
startTime = new Date((current.getTime() + delay) - (waitTime % delay));
}
return startTime;
}
protected void closeScheduler() {
if (this._mailSenderScheduler != null) this._mailSenderScheduler.cancel();
this._mailSenderScheduler = null;
}
@Override
public void sendMails() throws ApsSystemException {
try {
Map<String, List<ContentStatusChangedEventInfo>> statusChangedInfos = this.getNotifierDAO().getEventsToNotify();
ApsSystemUtils.getLogger().trace("Found " + statusChangedInfos.size() + " events to notify");
if (statusChangedInfos.size()>0) {
Map<String, List<ContentStatusChangedEventInfo>> contentsForUsers = this.prepareContentsForUsers(statusChangedInfos);
Iterator<String> iter = contentsForUsers.keySet().iterator();
while (iter.hasNext()) {
String username = iter.next();
List<ContentStatusChangedEventInfo> contentInfos = contentsForUsers.get(username);
this.sendMail(username, contentInfos);
}
List<ContentStatusChangedEventInfo> notifiedContents = new ArrayList<ContentStatusChangedEventInfo>();
for (List<ContentStatusChangedEventInfo> contentsForType : statusChangedInfos.values()) {
notifiedContents.addAll(contentsForType);
}
ApsSystemUtils.getLogger().trace("Notified " + notifiedContents.size() + " events");
this.getNotifierDAO().signNotifiedEvents(notifiedContents);
}
} catch (Throwable t) {
ApsSystemUtils.logThrowable(t, this, "sendMails");
throw new ApsSystemException("Errore in fase di invio mail di notifica", t);
}
}
protected void sendMail(String username, List<ContentStatusChangedEventInfo> contentInfos) {
try {
NotifierConfig notifierConfig = this.getWorkflowNotifierConfig();
String mailAddress = this.getMailAddress(username);
if (null == mailAddress) {
return;
}
String[] mailAddresses = {mailAddress};
Map<String, String> params = this.prepareParams(username);
String subject = this.replaceParams(notifierConfig.getSubject(), params);
String text = this.prepareMailText(params, contentInfos);
String senderCode = notifierConfig.getSenderCode();
String contentType = (notifierConfig.isHtml()) ? IMailManager.CONTENTTYPE_TEXT_HTML : IMailManager.CONTENTTYPE_TEXT_PLAIN;
this.getMailManager().sendMail(text, subject, mailAddresses, null, null, senderCode, contentType);
} catch(Throwable t) {
ApsSystemUtils.logThrowable(t, this, "sendMail", "Error sending mail to user " + username);
}
}
protected String getMailAddress(String username) throws Throwable {
String email = null;
IUserProfileManager profileManager = (IUserProfileManager) super.getBeanFactory().getBean(SystemConstants.USER_PROFILE_MANAGER);
IUserProfile profile = profileManager.getProfile(username);
if (null != profile) {
ITextAttribute mailAttribute = (ITextAttribute) profile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL);
if (null != mailAttribute && mailAttribute.getText().trim().length() > 0) {
email = mailAttribute.getText();
}
}
return email;
}
protected Map<String, String> prepareParams(String username) {
Map<String, String> params = new HashMap<String, String>();
params.put("user", username);
return params;
}
protected void addContentParams(Map<String, String> params, ContentStatusChangedEventInfo contentInfo) {
params.put("type", contentInfo.getContentTypeCode());
params.put("contentId", contentInfo.getContentId());
params.put("descr", contentInfo.getContentDescr());
params.put("group", contentInfo.getMainGroup());
params.put("status", contentInfo.getStatus());
params.put("data", DateConverter.getFormattedDate(contentInfo.getDate(), "dd MMMM yyyy"));
}
protected String prepareMailText(Map<String, String> params, List<ContentStatusChangedEventInfo> contentInfos) {
NotifierConfig notifierConfig = this.getWorkflowNotifierConfig();
String header = this.replaceParams(notifierConfig.getHeader(), params);
String footer = this.replaceParams(notifierConfig.getFooter(), params);
String body = notifierConfig.getTemplate();
StringBuilder text = new StringBuilder(header);
for (ContentStatusChangedEventInfo contentInfo : contentInfos) {
this.addContentParams(params, contentInfo);
text.append(this.replaceParams(body, params));
}
text.append(footer);
return text.toString();
}
/**
* @param defaultText Il testo di partenza, contenente le stringhe da rimpiazzare secondo la sintassi {chiaveStringa}.
* @param params La mappa dei parametri da rimpiazzare (solo il nome, esluse le { })<br />
* ATTENZIONE: le chiavi non devono contenere caratteri speciali per le regular expressions.<br />
* In tal caso vanno utilizzati i caratteri di escape.
* @return Il testo con tutte le occorrenze delle parole chiave sostituite.
*/
protected String replaceParams(String defaultText, Map<String, String> params) {
String body = defaultText;
Iterator<Entry<String, String>> it = params.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> pairs = it.next();
String field = "\\{" + pairs.getKey() + "\\}";
body = body.replaceAll(field, pairs.getValue());
}
return body;
}
protected Map<String, List<ContentStatusChangedEventInfo>> prepareContentsForUsers (
Map<String, List<ContentStatusChangedEventInfo>> statusChangedInfos) throws ApsSystemException {
Map<String, List<ContentStatusChangedEventInfo>> contentsForUsers = new HashMap<String, List<ContentStatusChangedEventInfo>>();
List<String> editors = new ArrayList<String>();
List<String> supervisors = new ArrayList<String>();
this.findContentOperators(editors, supervisors);
if (null != statusChangedInfos) {
Iterator<String> iter = statusChangedInfos.keySet().iterator();
while (iter.hasNext()) {
String typeCode = iter.next();
List<ContentStatusChangedEventInfo> infosForContentType = statusChangedInfos.get(typeCode);
this.prepareUsersForContentType(contentsForUsers, typeCode, infosForContentType, editors, supervisors);
}
}
return contentsForUsers;
}
protected void findContentOperators(List<String> editors, List<String> supervisors) throws ApsSystemException {
List<Role> rolesWithSupervisor = ((IRoleManager) this.getRoleManager()).getRolesWithPermission(Permission.SUPERVISOR);
List<String> roleNamesWithSupervisor = this.getRolesNames(rolesWithSupervisor);
List<Role> rolesWithEditors = ((IRoleManager) this.getRoleManager()).getRolesWithPermission("editContents");
List<String> roleNamesWithEditor = this.getRolesNames(rolesWithEditors);
IUserProfileManager profileManager = (IUserProfileManager) super.getBeanFactory().getBean(SystemConstants.USER_PROFILE_MANAGER);
List<String> usernames = profileManager.searchId(null);
for (int i = 0; i < usernames.size(); i++) {
String extractedUsername = usernames.get(i);
List<IApsAuthority> userRoles = this.getRoleManager().getAuthorizationsByUser(extractedUsername);
if (null == userRoles) {
continue;
}
for (int j = 0; j < userRoles.size(); j++) {
IApsAuthority role = userRoles.get(j);
if (null == role) {
continue;
}
if (roleNamesWithSupervisor.contains(role.getAuthority())) {
supervisors.add(extractedUsername);
}
if (roleNamesWithEditor.contains(role.getAuthority())) {
editors.add(extractedUsername);
}
}
}
}
private List<String> getRolesNames(List<Role> roles) {
List<String> names = new ArrayList<String>();
if (null == roles) {
return names;
}
for (int i = 0; i < roles.size(); i++) {
IApsAuthority role = roles.get(i);
if (null == role) {
continue;
}
names.add(role.getAuthority());
}
return names;
}
protected void prepareUsersForContentType(Map<String, List<ContentStatusChangedEventInfo>> contentsForUsers,
String typeCode, List<ContentStatusChangedEventInfo> infosForContentType, List<String> editors, List<String> supervisors) throws ApsSystemException {
Workflow workflow = this.getWorkflowManager().getWorkflow(typeCode);
String contentTypeRole = workflow.getRole();
if (contentTypeRole == null || contentTypeRole.length() == 0) {
editors = this.filterUsersForRole(contentTypeRole, editors);
supervisors = this.filterUsersForRole(contentTypeRole, supervisors);
}
List<String> usersForStep = null;
String previousStepRole = null;
for (ContentStatusChangedEventInfo contentInfo : infosForContentType) {
String currentStep = contentInfo.getStatus();
Step step = workflow.getStep(currentStep);
String currentStepRole = step!=null ? step.getRole() : null;
boolean needsSupervisor = Content.STATUS_READY.equals(currentStep);
if (previousStepRole==null || !previousStepRole.equals(currentStepRole)) {
previousStepRole = currentStepRole;
if (needsSupervisor) {
usersForStep = supervisors;
} else {
usersForStep = this.filterUsersForRole(currentStepRole, editors);
}
}
String mainGroup = contentInfo.getMainGroup();
List<String> allowedUsers = this.filterUsersForGroup(mainGroup, usersForStep);
this.addContentForUsers(contentInfo, allowedUsers, contentsForUsers);
}
}
protected List<String> filterUsersForRole(String roleName, List<String> users) throws ApsSystemException {
List<String> usersForContentType = null;
try {
if (users.isEmpty() || roleName == null || roleName.length()==0) {
usersForContentType = users;
} else {
usersForContentType = new ArrayList<String>();
IApsAuthorityManager roleManager = this.getRoleManager();
IApsAuthority authority = roleManager.getAuthority(roleName);
List<String> usernamesWithAuth = this.getRoleManager().getUsernamesByAuthority(authority);
for (int i = 0; i < users.size(); i++) {
String username = users.get(i);
if (null == username) {
continue;
}
for (int j = 0; j < usernamesWithAuth.size(); j++) {
String userWithAuth = usernamesWithAuth.get(j);
if (null == userWithAuth) {
continue;
}
if (username.equals(userWithAuth)) {
usersForContentType.add(username);
}
}
}
}
} catch (Throwable t) {
ApsSystemUtils.logThrowable(t, this, "filterUsersForRole");
throw new ApsSystemException("Errore nel filtro degli utenti in base al ruolo", t);
}
return usersForContentType;
}
protected List<String> filterUsersForGroup(String groupName, List<String> usernames) throws ApsSystemException {
List<String> usersForContentType = null;
if (usernames.isEmpty() || groupName == null || groupName.length()==0) {
usersForContentType = usernames;
} else {
usersForContentType = new ArrayList<String>();
IApsAuthorityManager groupManager = (IApsAuthorityManager) super.getBeanFactory().getBean(SystemConstants.GROUP_MANAGER);
IApsAuthority authority = groupManager.getAuthority(groupName);
List<String> usersWithAuth = groupManager.getUsernamesByAuthority(authority);
for (int i = 0; i < usernames.size(); i++) {
String username = usernames.get(i);
if (null == username) {
continue;
}
for (int j = 0; j < usersWithAuth.size(); j++) {
String userWithAuth = usersWithAuth.get(j);
if (null == userWithAuth) {
continue;
}
if (username.equals(userWithAuth)) {
usersForContentType.add(username);
}
}
}
}
return usersForContentType;
}
protected void addContentForUsers(ContentStatusChangedEventInfo contentInfo, List<String> allowedUsers,
Map<String, List<ContentStatusChangedEventInfo>> contentsForUsers) {
if (null == contentsForUsers || contentsForUsers.isEmpty()) {
return;
}
for (int i = 0; i < allowedUsers.size(); i++) {
String username = allowedUsers.get(i);
List<ContentStatusChangedEventInfo> infos = contentsForUsers.get(username);
if (infos == null) {
infos = new ArrayList<ContentStatusChangedEventInfo>();
contentsForUsers.put(username, infos);
}
infos.add(contentInfo);
}
}
protected ConfigInterface getConfigManager() {
return _configManager;
}
public void setConfigManager(ConfigInterface configManager) {
this._configManager = configManager;
}
protected IUserManager getUserManager() {
return _userManager;
}
public void setUserManager(IUserManager userManager) {
this._userManager = userManager;
}
protected IAuthenticationProviderManager getAuthProvider() {
return _authProvider;
}
public void setAuthProvider(IAuthenticationProviderManager authProvider) {
this._authProvider = authProvider;
}
protected IAuthorizationManager getAuthorizationManager() {
return _authorizationManager;
}
public void setAuthorizationManager(IAuthorizationManager authorizationManager) {
this._authorizationManager = authorizationManager;
}
protected IApsAuthorityManager getRoleManager() {
return _roleManager;
}
public void setRoleManager(IApsAuthorityManager roleManager) {
this._roleManager = roleManager;
}
protected IContentWorkflowManager getWorkflowManager() {
return _workflowManager;
}
public void setWorkflowManager(IContentWorkflowManager workflowManager) {
this._workflowManager = workflowManager;
}
protected IMailManager getMailManager() {
return _mailManager;
}
public void setMailManager(IMailManager mailManager) {
this._mailManager = mailManager;
}
protected IContentManager getContentManager() {
return _contentManager;
}
public void setContentManager(IContentManager contentManager) {
this._contentManager = contentManager;
}
protected IWorkflowNotifierDAO getNotifierDAO() {
return _notifierDAO;
}
public void setNotifierDAO(IWorkflowNotifierDAO notifierDAO) {
this._notifierDAO = notifierDAO;
}
private NotifierConfig _notifierConfig;
protected Scheduler _mailSenderScheduler;
private ConfigInterface _configManager;
private IUserManager _userManager;
private IAuthenticationProviderManager _authProvider;
private IAuthorizationManager _authorizationManager;
private IApsAuthorityManager _roleManager;
private IContentWorkflowManager _workflowManager;
private IMailManager _mailManager;
private IContentManager _contentManager;
private IWorkflowNotifierDAO _notifierDAO;
}