/******************************************************************************* * Copyright (c) 2017-2019 DocDoku. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * DocDoku - initial API and implementation *******************************************************************************/ package org.polarsys.eplmp.server; import org.polarsys.eplmp.core.admin.WorkspaceBackOptions; import org.polarsys.eplmp.core.common.Account; import org.polarsys.eplmp.core.common.User; import org.polarsys.eplmp.core.common.Workspace; import org.polarsys.eplmp.core.document.DocumentRevision; import org.polarsys.eplmp.core.exceptions.*; import org.polarsys.eplmp.core.hooks.SNSWebhookApp; import org.polarsys.eplmp.core.hooks.SimpleWebhookApp; import org.polarsys.eplmp.core.hooks.Webhook; import org.polarsys.eplmp.core.meta.Tag; import org.polarsys.eplmp.core.product.PartRevision; import org.polarsys.eplmp.core.services.INotifierLocal; import org.polarsys.eplmp.core.services.IPlatformOptionsManagerLocal; import org.polarsys.eplmp.core.services.IWebhookManagerLocal; import org.polarsys.eplmp.core.services.IWorkspaceManagerLocal; import org.polarsys.eplmp.core.util.FileIO; import org.polarsys.eplmp.core.workflow.Task; import org.polarsys.eplmp.core.workflow.WorkspaceWorkflow; import org.polarsys.eplmp.i18n.PropertiesLoader; import org.polarsys.eplmp.server.hooks.SNSWebhookRunner; import org.polarsys.eplmp.server.hooks.SimpleWebhookRunner; import org.polarsys.eplmp.server.hooks.WebhookRunner; import javax.annotation.Resource; import javax.ejb.Asynchronous; import javax.ejb.Local; import javax.ejb.Stateless; import javax.inject.Inject; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.io.UnsupportedEncodingException; import java.text.MessageFormat; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * Session class NotifierBean * * @author Florent.Garin */ @Local(INotifierLocal.class) @Stateless(name = "MailerBean") public class NotifierBean implements INotifierLocal { private static final String TEMPLATE_BASE_NAME = "/org/polarsys/eplmp/server/templates/NotificationText"; @Inject private ConfigManager configManager; @Inject private IPlatformOptionsManagerLocal platformOptionsManager; @Inject private IWorkspaceManagerLocal workspaceManager; @Inject private IWebhookManagerLocal webhookManager; @Resource(name = "mail/docdokuSMTP") private Session mailSession; private static final Logger LOGGER = Logger.getLogger(NotifierBean.class.getName()); @Asynchronous @Override public void sendStateNotification(String workspaceId, Collection<User> pSubscribers, DocumentRevision pDocumentRevision) { LOGGER.info("Sending state notification emails \n\tfor the document " + pDocumentRevision.getLastIteration()); try { for (User pSubscriber : pSubscribers) { sendStateNotification(pSubscriber, pDocumentRevision); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendIterationNotification(String workspaceId, Collection<User> pSubscribers, DocumentRevision pDocumentRevision) { LOGGER.info("Sending iteration notification emails \n\tfor the document " + pDocumentRevision.getLastIteration()); try { for (User pSubscriber : pSubscribers) { sendIterationNotification(pSubscriber, pDocumentRevision); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendTaggedNotification(String workspaceId, Collection<User> pSubscribers, DocumentRevision pDocR, Tag pTag) { LOGGER.info("Sending tagged notification emails \n\tfor the document " + pDocR.getLastIteration()); try { for (User pSubscriber : pSubscribers) { sendTaggedNotification(pSubscriber, pDocR, pTag); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendTaggedNotification(String workspaceId, Collection<User> pSubscribers, PartRevision pPartR, Tag pTag) { LOGGER.info("Sending tagged notification emails \n\tfor the part " + pPartR.getLastIteration()); try { for (User pSubscriber : pSubscribers) { sendTaggedNotification(pSubscriber, pPartR, pTag); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendUntaggedNotification(String workspaceId, Collection<User> pSubscribers, DocumentRevision pDocR, Tag pTag) { LOGGER.info("Sending untagged notification emails \n\tfor the document " + pDocR.getLastIteration()); try { for (User pSubscriber : pSubscribers) { sendUntaggedNotification(pSubscriber, pDocR, pTag); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendUntaggedNotification(String workspaceId, Collection<User> pSubscribers, PartRevision pPartR, Tag pTag) { LOGGER.info("Sending untagged notification emails \n\tfor the part " + pPartR.getLastIteration()); try { for (User pSubscriber : pSubscribers) { sendUntaggedNotification(pSubscriber, pPartR, pTag); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendApproval(String workspaceId, Collection<Task> pRunningTasks, DocumentRevision pDocumentRevision) { LOGGER.info("Sending approval emails \n\tfor the document " + pDocumentRevision.getLastIteration()); try { for (Task task : pRunningTasks) { sendApproval(task, pDocumentRevision); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendApproval(String workspaceId, Collection<Task> pRunningTasks, PartRevision partRevision) { LOGGER.info("Sending approval required emails \n\tfor the part " + partRevision.getLastIteration()); try { for (Task task : pRunningTasks) { sendApproval(task, partRevision); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendApproval(String workspaceId, Collection<Task> pRunningTasks, WorkspaceWorkflow workspaceWorkflow) { LOGGER.info("Sending approval required emails \n\tfor the workspace workflow " + workspaceWorkflow.getId()); try { for (Task task : pRunningTasks) { sendApproval(task, workspaceWorkflow); } } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendPasswordRecovery(Account account, String recoveryUUID) { LOGGER.info("Sending recovery message \n\tfor the user which login is " + account.getLogin()); Object[] args = { getRecoveryUrl(recoveryUUID), account.getLogin() }; try { sendMessage(account, "Recovery_title", "Recovery_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendWorkspaceDeletionNotification(Account admin, String workspaceId) { LOGGER.info("Sending workspace deletion notification message \n\tfor the user which login is " + admin.getLogin()); Object[] args = { workspaceId }; try { //User admin does not exist anymore as the workspace has been deleted sendMessage(admin, "WorkspaceDeletion_title", "WorkspaceDeletion_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendWorkspaceDeletionErrorNotification(Account admin, String workspaceId) { LOGGER.info("Sending workspace deletion error notification message \n\tfor the user which login is " + admin.getLogin()); Object[] args = { workspaceId }; try { User adminUser = new User(new Workspace(workspaceId), admin); sendMessage(adminUser, "WorkspaceDeletion_title", "WorkspaceDeletionError_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendPartRevisionWorkflowRelaunchedNotification(String workspaceId, PartRevision partRevision) { Workspace workspace = partRevision.getPartMaster().getWorkspace(); Account admin = workspace.getAdmin(); User adminUser = new User(workspace, admin); User author = partRevision.getAuthor(); LOGGER.info("Sending workflow relaunch notification email \n\tfor the part " + partRevision.getLastIteration() + " to admin: " + admin.getLogin()); // Mail both workspace admin and partRevision author sendWorkflowRelaunchedNotification(adminUser, partRevision); if (!admin.getLogin().equals(author.getLogin())) { LOGGER.info("Sending workflow relaunch notification email \n\tfor the part " + partRevision.getLastIteration() + " to user: " + author.getLogin()); sendWorkflowRelaunchedNotification(author, partRevision); } } @Asynchronous @Override public void sendDocumentRevisionWorkflowRelaunchedNotification(String workspaceId, DocumentRevision documentRevision) { Workspace workspace = documentRevision.getDocumentMaster().getWorkspace(); Account admin = workspace.getAdmin(); User author = documentRevision.getAuthor(); User adminUser = new User(workspace, admin); // Mail both workspace admin and documentMaster author sendWorkflowRelaunchedNotification(adminUser, documentRevision); if (!admin.getLogin().equals(author.getLogin())) { sendWorkflowRelaunchedNotification(author, documentRevision); } } @Asynchronous @Override public void sendWorkspaceWorkflowRelaunchedNotification(String workspaceId, WorkspaceWorkflow workspaceWorkflow) { Workspace workspace = workspaceWorkflow.getWorkspace(); Account admin = workspace.getAdmin(); User adminUser = new User(workspace, admin); // Mail workspace admin sendWorkflowRelaunchedNotification(adminUser, workspaceWorkflow); } @Asynchronous @Override public void sendWorkspaceIndexationSuccess(Account account, String workspaceId, String extraMessage) { Object[] args = { workspaceId, extraMessage }; try { User adminUser = new User(new Workspace(workspaceId), account); sendMessage(adminUser, "Indexer_success_title", "Indexer_success_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendWorkspaceIndexationFailure(Account account, String workspaceId, String extraMessage) { Object[] args = { workspaceId, extraMessage }; try { User adminUser = new User(new Workspace(workspaceId), account); sendMessage(adminUser, "Indexer_failure_title", "Indexer_failure_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Override public void sendBulkIndexationSuccess(Account account) { Object[] args = {}; try { sendMessage(account, "Indexer_bulk_success_title", "Indexer_bulk_success_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Override public void sendBulkIndexationFailure(Account account, String failureMessage) { Object[] args = { failureMessage }; try { sendMessage(account, "Indexer_bulk_failure_title", "Indexer_bulk_failure_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } @Asynchronous @Override public void sendCredential(Account account) { String accountDisabledMessage = ""; if (!account.isEnabled()) { switch (platformOptionsManager.getWorkspaceCreationStrategy()) { case ADMIN_VALIDATION: accountDisabledMessage = getString("SignUp_AccountDisabled_text", account.getLocale()); break; } } Object[] args = { account.getLogin(), configManager.getCodebase(), accountDisabledMessage }; try { sendMessage(account, "SignUp_success_title", "SignUp_success_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } private void sendStateNotification(User pSubscriber, DocumentRevision pDocumentRevision) throws MessagingException { LOGGER.info("Sending state notification emails \n\tfor the document " + pDocumentRevision.getLastIteration() + " to user " + pSubscriber.getLogin()); String stateName = pDocumentRevision.getLifeCycleState(); stateName = (stateName != null && !stateName.isEmpty()) ? stateName : getString("FinalState_name", pSubscriber.getLocale()); Object[] args = { pDocumentRevision, pDocumentRevision.getLastIteration().getCreationDate(), getDocumentRevisionPermalinkURL(pDocumentRevision), stateName }; sendMessage(pSubscriber, "StateNotification_title", "StateNotification_text", args); } private void sendIterationNotification(User pSubscriber, DocumentRevision pDocumentRevision) throws MessagingException { LOGGER.info("Sending iteration notification emails \n\tfor the document " + pDocumentRevision.getLastIteration()); Object[] args = { pDocumentRevision, pDocumentRevision.getLastIteration().getCreationDate(), pDocumentRevision.getLastIteration().getIteration(), pDocumentRevision.getLastIteration().getAuthor(), getDocumentRevisionPermalinkURL(pDocumentRevision) }; sendMessage(pSubscriber, "IterationNotification_title", "IterationNotification_text", args); } private void sendTaggedNotification(User pSubscriber, DocumentRevision pDocumentRevision, Tag pTag) throws MessagingException { sendTaggedNotification(pSubscriber, pDocumentRevision, pTag, true); } private void sendUntaggedNotification(User pSubscriber, DocumentRevision pDocumentRevision, Tag pTag) throws MessagingException { sendTaggedNotification(pSubscriber, pDocumentRevision, pTag, false); } private void sendTaggedNotification(User pSubscriber, DocumentRevision pDocumentRevision, Tag pTag, boolean tagged) throws MessagingException { LOGGER.info("Sending tag notification emails \n\tfor the document " + pDocumentRevision.getLastIteration() + " to subscriber : " + pSubscriber.getLogin()); Object[] args = { pTag, pDocumentRevision, getDocumentRevisionPermalinkURL(pDocumentRevision) }; sendMessage(pSubscriber, "TagNotification_title", tagged ? "TagNotificationTagged_text" : "TagNotificationUntagged_text", args); } private void sendTaggedNotification(User pSubscriber, PartRevision pPartRevision, Tag pTag) throws MessagingException { sendTaggedNotification(pSubscriber, pPartRevision, pTag, true); } private void sendUntaggedNotification(User pSubscriber, PartRevision pPartRevision, Tag pTag) throws MessagingException { sendTaggedNotification(pSubscriber, pPartRevision, pTag, false); } private void sendTaggedNotification(User pSubscriber, PartRevision pPartRevision, Tag pTag, boolean tagged) throws MessagingException { LOGGER.info("Sending tag notification emails \n\tfor the part " + pPartRevision.getLastIteration() + " to subscriber : " + pSubscriber.getLogin()); Object[] args = { pTag, pPartRevision, getPartRevisionPermalinkURL(pPartRevision) }; sendMessage(pSubscriber, "TagNotification_title", tagged ? "TagNotificationTagged_text" : "TagNotificationUntagged_text", args); } private void sendApproval(Task task, DocumentRevision pDocumentRevision) throws MessagingException { LOGGER.info("Sending approval required emails \n\tfor the document " + pDocumentRevision.getLastIteration()); Set<User> workers = new HashSet<>(); workers.addAll(task.getAssignedUsers()); task.getAssignedGroups().forEach(g -> workers.addAll(g.getUsers())); for (User worker : workers) { sendApprovalToUser(worker, task, pDocumentRevision); } } private void sendApproval(Task task, PartRevision partRevision) throws MessagingException { LOGGER.info("Sending approval required emails \n\tfor the part " + partRevision.getLastIteration()); Set<User> workers = new HashSet<>(); workers.addAll(task.getAssignedUsers()); task.getAssignedGroups().forEach(g -> workers.addAll(g.getUsers())); for (User worker : workers) { sendApprovalToUser(worker, task, partRevision); } } private void sendApproval(Task task, WorkspaceWorkflow workspaceWorkflow) throws MessagingException { LOGGER.info("Sending approval required emails \n\tfor the workspace workflow " + workspaceWorkflow.getId()); Set<User> workers = new HashSet<>(); workers.addAll(task.getAssignedUsers()); task.getAssignedGroups().forEach(g -> workers.addAll(g.getUsers())); for (User worker : workers) { sendApprovalToUser(worker, task, workspaceWorkflow); } } private void sendApprovalToUser(User worker, Task task, DocumentRevision pDocumentRevision) throws MessagingException { LOGGER.info("Sending approval email \n\tfor the document " + pDocumentRevision.getLastIteration() + " to user: " + worker.getLogin()); Object[] args = { task.getTitle(), getDocumentRevisionPermalinkURL(pDocumentRevision), pDocumentRevision.getKey(), task.getInstructions() == null ? "-" : task.getInstructions(), getTaskUrl(task, pDocumentRevision.getWorkspaceId()) }; sendMessage(worker, "Approval_title", "Approval_document_text", args); } private void sendApprovalToUser(User worker, Task pTask, PartRevision partRevision) throws MessagingException { LOGGER.info("Sending approval email \n\tfor the part " + partRevision.getLastIteration() + " to user: " + worker.getLogin()); Object[] args = { pTask.getTitle(), getPartRevisionPermalinkURL(partRevision), partRevision.getKey(), pTask.getInstructions() == null ? "-" : pTask.getInstructions(), getTaskUrl(pTask, partRevision.getWorkspaceId()) }; sendMessage(worker, "Approval_title", "Approval_part_text", args); } private void sendApprovalToUser(User worker, Task pTask, WorkspaceWorkflow workspaceWorkflow) throws MessagingException { LOGGER.info("Sending approval email \n\tfor the workspace workflow " + workspaceWorkflow.getId() + " to user: " + worker.getLogin()); Object[] args = { pTask.getTitle(), pTask.getInstructions() == null ? "-" : pTask.getInstructions(), getTaskUrl(pTask, workspaceWorkflow.getWorkspaceId()) }; sendMessage(worker, "Approval_title", "Approval_workspace_workflow_text", args); } private void sendWorkflowRelaunchedNotification(User user, PartRevision partRevision) { Object[] args = { partRevision.getPartNumber() + "-" + partRevision.getVersion(), user.getWorkspace().getId(), partRevision.getWorkflow().getLifeCycleState() }; try { sendMessage(user, "Workflow_relaunched_title", "PartRevision_workflow_relaunched_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } private void sendWorkflowRelaunchedNotification(User user, DocumentRevision documentRevision) { Object[] args = { documentRevision.getId() + "-" + documentRevision.getVersion(), user.getWorkspace().getId(), documentRevision.getWorkflow().getLifeCycleState() }; try { sendMessage(user, "Workflow_relaunched_title", "DocumentRevision_workflow_relaunched_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } private void sendWorkflowRelaunchedNotification(User user, WorkspaceWorkflow workspaceWorkflow) { Object[] args = { user.getWorkspace().getId(), workspaceWorkflow.getWorkflow().getLifeCycleState() }; try { sendMessage(user, "Workflow_relaunched_title", "WorkspaceWorkflow_workflow_relaunched_text", args); } catch (MessagingException pMEx) { logMessagingException(pMEx); } } // URIs private String getDocumentRevisionPermalinkURL(DocumentRevision pDocR) { return configManager.getCodebase() + "/documents/index.html#" + pDocR.getWorkspaceId() + "/" + FileIO.encode(pDocR.getId()) + "/" + pDocR.getVersion(); } private String getPartRevisionPermalinkURL(PartRevision pPartR) { return configManager.getCodebase() + "/parts/index.html#" + pPartR.getWorkspaceId() + "/" + FileIO.encode(pPartR.getPartNumber()) + "/" + pPartR.getVersion(); } private String getTaskUrl(Task pTask, String workspaceId) { return configManager.getCodebase() + "/change-management/index.html#" + workspaceId + "/tasks/" + pTask.getWorkflowId() + "-" + pTask.getActivityStep() + "-" + pTask.getNum(); } private String getRecoveryUrl(String uuid) { return configManager.getCodebase() + "/index.html#recover/" + uuid; } // Log shortcuts private void logMessagingException(MessagingException pMEx) { String logMessage = "Message format error. \n\tMail can't be sent. \n\t" + pMEx.getMessage(); LOGGER.log(Level.SEVERE, logMessage, pMEx); } // Template utils methods private Properties getProperties(Locale pLocale) { return PropertiesLoader.loadLocalizedProperties(pLocale, TEMPLATE_BASE_NAME, getClass()); } private String getString(String string, Locale pLocale) { return getProperties(pLocale).getProperty(string).replaceAll("'", "’"); } private String format(String string, Object[] args, Locale pLocale) { return MessageFormat.format(getString(string, pLocale).replaceAll("'", "’"), args); } private String getHTMLBody(String content, Locale pLocale) { String mailBodyTemplate = getString("MailBodyTemplate", pLocale); return MessageFormat.format(mailBodyTemplate, content); } private String getSubject(String string, Locale pLocale) { String mailSubjectTemplate = getString("MailSubjectTemplate", pLocale); return mailSubjectTemplate + " " + getString(string, pLocale); } // Direct account message // Only emails should be sent private void sendMessage(Account account, String subjectKey, String contentKey, Object[] contentArgs) throws MessagingException { Locale locale = account.getLocale(); String subject = getSubject(subjectKey, locale); String content = format(contentKey, contentArgs, locale); String name = account.getName(); String email = account.getEmail(); sendEmail(email, name, subject, getHTMLBody(content, locale)); } // User in workspace message private void sendMessage(User user, String subjectKey, String contentKey, Object[] contentArgs) throws MessagingException { Locale userLocale = user.getLocale(); String subject = getSubject(subjectKey, userLocale); String content = format(contentKey, contentArgs, userLocale); String name = user.getName(); String login = user.getLogin(); String email = user.getEmail(); String workspaceId = user.getWorkspaceId(); WorkspaceBackOptions workspaceBackOptions; try { workspaceBackOptions = workspaceManager.getWorkspaceBackOptions(workspaceId); } catch (AccountNotFoundException | WorkspaceNotFoundException | UserNotFoundException | UserNotActiveException | WorkspaceNotEnabledException e) { LOGGER.log(Level.SEVERE, null, e); return; } if (workspaceBackOptions.isSendEmails()) { sendEmail(email, name, subject, getHTMLBody(content, userLocale)); } List<Webhook> activeWebHooks; try { activeWebHooks = webhookManager.getActiveWebHooks(workspaceId); } catch (UserNotFoundException | WorkspaceNotFoundException | UserNotActiveException | WorkspaceNotEnabledException e) { LOGGER.log(Level.SEVERE, null, e); return; } for (Webhook webhook : activeWebHooks) { runHook(webhook, login, email, name, subject, content); } } private void sendEmail(String email, String name, String subject, String content) throws MessagingException { if (email == null || email.isEmpty()) { LOGGER.log(Level.WARNING, "Cannot send mail, email is empty"); return; } try { InternetAddress emailAddress = new InternetAddress(email, name); Message message = new MimeMessage(mailSession); message.addRecipient(Message.RecipientType.TO, emailAddress); message.setSubject(subject); message.setSentDate(new Date()); message.setContent(content, "text/html; charset=utf-8"); message.setFrom(); Transport.send(message); } catch (UnsupportedEncodingException e) { String logMessage = "Unsupported encoding: " + e.getMessage(); LOGGER.log(Level.SEVERE, logMessage, e); } } private void runHook(Webhook webhook, String login, String email, String name, String subject, String content) { LOGGER.log(Level.INFO, " Running hook " + webhook.getName()); String appName = webhook.getAppName(); WebhookRunner runner; switch (appName) { case SNSWebhookApp.APP_NAME: runner = new SNSWebhookRunner(); break; case SimpleWebhookApp.APP_NAME: runner = new SimpleWebhookRunner(); break; default: LOGGER.log(Level.SEVERE, "Unsupported webhook " + webhook); return; } runner.run(webhook, login, email, name, subject, content); } }