From 743065294251ffe9c31514579ce8fd60a371555f Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Fri, 12 Aug 2011 04:26:24 +0000 Subject: [PATCH] Merged BRANCHES/DEV/WF-NOTIFICATION to HEAD: 29508: Workflow Notification - First Cut * Notification service to consolidate sending of user notifications (kinds of notifications are provided by Sprung in notification providers) * EMail notification provider implementation (uses standard Email action to send email) * Frist cut workflow email template (still needs lots of details added) * AMP, etc for email template * Hook point within Activit and JBMP implementations * Property added to model (startTask) indicating whether email notifications should be sent * Hook points sensitive to property * Wf forms updated to show property 29703: Workflow Notification: * Remove AMP and replace with exploded XMl and template (easier to maintain) * Bootstrap updated * Patch added * Refactored hooks to use generic workflowTask object (tidies up helper methods) * I18n'ed messages * Task and work package information placed in template model * Email template built with reference to Lintons wire's (still needs some polish!) * Added Notification Servcice to Service Registry git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29705 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/activiti-context.xml | 1 + config/alfresco/application-context-core.xml | 1 + .../bootstrap/notification/wf-email.html.ftl | 144 ++++++++ .../workflow-email-notification.xml | 39 +++ config/alfresco/import-export-context.xml | 5 + .../messages/bootstrap-spaces.properties | 5 + .../alfresco/messages/bpm-messages.properties | 2 + .../messages/notification-service.properties | 7 + .../messages/patch-service.properties | 2 + config/alfresco/model/bpmModel.xml | 7 +- .../notification-services-context.xml | 63 ++++ .../alfresco/patch/patch-services-context.xml | 28 ++ config/alfresco/repository.properties | 1 + config/alfresco/version.properties | 2 +- .../action/executer/MailActionExecuter.java | 2 +- .../EMailNotificationProvider.java | 308 ++++++++++++++++++ .../notification/NotificationServiceImpl.java | 106 ++++++ .../NotificationServiceImplSystemTest.java | 224 +++++++++++++ .../rendition/MockedTestServiceRegistry.java | 7 + .../service/ServiceDescriptorRegistry.java | 10 + .../alfresco/repo/workflow/WorkflowModel.java | 2 + .../workflow/WorkflowNotificationUtils.java | 146 +++++++++ .../tasklistener/TaskCreateListener.java | 27 ++ .../workflow/jbpm/AlfrescoAssignment.java | 27 +- .../org/alfresco/service/ServiceRegistry.java | 8 + .../cmr/notification/NotificationContext.java | 228 +++++++++++++ .../notification/NotificationProvider.java | 46 +++ .../cmr/notification/NotificationService.java | 66 ++++ .../alfresco/util/BaseAlfrescoTestCase.java | 65 +++- .../org/alfresco/util/TestWithUserUtils.java | 18 +- 30 files changed, 1577 insertions(+), 20 deletions(-) create mode 100644 config/alfresco/bootstrap/notification/wf-email.html.ftl create mode 100644 config/alfresco/bootstrap/notification/workflow-email-notification.xml create mode 100644 config/alfresco/messages/notification-service.properties create mode 100644 config/alfresco/notification-services-context.xml create mode 100644 source/java/org/alfresco/repo/notification/EMailNotificationProvider.java create mode 100644 source/java/org/alfresco/repo/notification/NotificationServiceImpl.java create mode 100644 source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java create mode 100644 source/java/org/alfresco/service/cmr/notification/NotificationContext.java create mode 100644 source/java/org/alfresco/service/cmr/notification/NotificationProvider.java create mode 100644 source/java/org/alfresco/service/cmr/notification/NotificationService.java diff --git a/config/alfresco/activiti-context.xml b/config/alfresco/activiti-context.xml index 1b309ff155..73658a125f 100644 --- a/config/alfresco/activiti-context.xml +++ b/config/alfresco/activiti-context.xml @@ -166,6 +166,7 @@ class="org.alfresco.repo.workflow.activiti.tasklistener.TaskCreateListener" depends-on="activitiWorkflowManager"> + + diff --git a/config/alfresco/bootstrap/notification/wf-email.html.ftl b/config/alfresco/bootstrap/notification/wf-email.html.ftl new file mode 100644 index 0000000000..f5ede4a675 --- /dev/null +++ b/config/alfresco/bootstrap/notification/wf-email.html.ftl @@ -0,0 +1,144 @@ + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + +
+ + +
+ <#if args.workflowPooled == true> + New Pooled Task + <#else> + You been assigned a task + +
+
+ ${date?datetime?string.full} +
+
+
+

Hi,

+ +

+ <#if args.workflowPooled == true> + The following pooled task is available to be claimed: + <#else> + You have been assigned the following task: + +

+ +

"${args.workflowDescription}"

+ +

+ <#if (args.workflowDueDate)??>Due:  ${args.workflowDueDate?date?string.full}
+ <#if (args.workflowPriority)??> + Priority:   + + <#if args.workflowPriority == 3> + Low + <#elseif args.workflowPriority == 2> + Medium + <#else> + High + + + +

+ + <#if (args.workflowDocuments)??> + + <#list args.workflowDocuments as doc> + + +
+ + +
+ + + +
${doc.name}
Click on this link to download the document:
+ + ${shareUrl}/proxy/alfresco/api/node/content/workspace/SpacesStore/${doc.id}/${doc.name}?a=true + +
+ + + <#if args.workflowPooled == true> +

Click this link to view the task:

+

${shareUrl}/page/task-view?taskId=${args.workflowId} + <#else> +

Click this link to edit the task:

+

${shareUrl}/page/task-edit?taskId=${args.workflowId} + + +

Sincerely,
+ Alfresco ${productName!""}

+
+
+
+
 
+
+ To find out more about Alfresco ${productName!""} visit http://www.alfresco.com +
+
 
+
+ +
+
+
+ + \ No newline at end of file diff --git a/config/alfresco/bootstrap/notification/workflow-email-notification.xml b/config/alfresco/bootstrap/notification/workflow-email-notification.xml new file mode 100644 index 0000000000..daf3c03e56 --- /dev/null +++ b/config/alfresco/bootstrap/notification/workflow-email-notification.xml @@ -0,0 +1,39 @@ + + + + + + + + ${spaces.templates.email.workflowNotification.description} + space-icon-default + ${spaces.templates.email.workflowNotification.name} + ${spaces.templates.email.workflowNotification.name} + + + + + + + + + + + + true + workspace + SpacesStore + wf-email-html-ftl + wf-email.html.ftl + wf-email.html.ftl + ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} + contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html.ftl|mimetype=text/plain|size=1455|encoding=UTF-8 + + + + + + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 40f5799696..fc598ee430 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -531,6 +531,11 @@ alfresco/templates/following-email-templates.acp alfresco/messages/bootstrap-spaces + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + alfresco/bootstrap/notification/workflow-email-notification.xml + alfresco/messages/bootstrap-spaces + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} alfresco/templates/rss_templates.acp diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index dfc950091e..a259f50f4c 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -93,6 +93,8 @@ spaces.templates.email.notify.description=Notify Email Templates spaces.templates.email.generate_the_invite_email.description=Email template used to generate the invite email for Alfresco Share +spaces.templates.email.generate_the_wf_notification_email.description=Email template for notifying users of new a workflow task + email.template.email_template_for_notifying_users=Email template for notifying users from a rule or action email.template.email_template_for_notifying_users.sample=Sample Email template for notifying users from a rule or action @@ -152,3 +154,6 @@ spaces.transfer_temp.description=Folder to store temporary nodes during transfer spaces.inbound_transfer_records.name=Inbound Transfer Records spaces.inbound_transfer_records.title=Inbound Transfer Records spaces.inbound_transfer_records.description=Folder containing records of inbound transfers + +spaces.templates.email.workflowNotification.name=Workflow Notification +spaces.templates.email.workflowNotification.description=Workflow notification email templates \ No newline at end of file diff --git a/config/alfresco/messages/bpm-messages.properties b/config/alfresco/messages/bpm-messages.properties index 41d8d18d1d..cea1f41baa 100644 --- a/config/alfresco/messages/bpm-messages.properties +++ b/config/alfresco/messages/bpm-messages.properties @@ -62,6 +62,8 @@ bpm_businessprocessmodel.property.bpm_workflowDueDate.title=Workflow Due Date bpm_businessprocessmodel.property.bpm_workflowDueDate.description=Workflow Due Date bpm_businessprocessmodel.property.bpm_workflowPriority.title=Workflow Priority bpm_businessprocessmodel.property.bpm_workflowPriority.description=Workflow Priority +bpm_businessprocessmodel.property.bpm_sendEMailNotifications.title=Send EMail Notifications +bpm_businessprocessmodel.property.bpm_sendEMailNotifications.description=Send EMail Notifications bpm_businessprocessmodel.association.bpm_assignee.title=Workflow Assignee bpm_businessprocessmodel.association.bpm_assignee.description=Workflow Assignee bpm_businessprocessmodel.association.bpm_assignees.title=Workflow Assignees diff --git a/config/alfresco/messages/notification-service.properties b/config/alfresco/messages/notification-service.properties new file mode 100644 index 0000000000..39fb7c971d --- /dev/null +++ b/config/alfresco/messages/notification-service.properties @@ -0,0 +1,7 @@ +# Notification Service externalised display strings +np-does-not-exist=Unable to send notification, because notification provider {0} does not exist. +default-sender-used=Sender of email notification has no email set, default sender will be used. (user={0}) +no-recipients=Unable to send email notification, because no recipients where provided. (document={0}) +no-body-or-template=Unable to send notification, because no body or body template where specified. (node={0}) +assigned-task=You have been assigned a task +new-pooled-task=New Pooled Task diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 792c4323d4..c7f8ebf533 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -422,3 +422,5 @@ patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. patch.copiedFromAspect.description=Adds peer associations for cm:copiedfrom and cm:workingcopy (new model) and removes cm:source property patch.copiedFromAspect.result=Fixed cm:copiedfrom model for {0} nodes. See file {1} for details. + +patch.workflowNotification.description=Patch to add workflow email notification email folder and template. \ No newline at end of file diff --git a/config/alfresco/model/bpmModel.xml b/config/alfresco/model/bpmModel.xml index 0c8b149447..0dad23ad7f 100644 --- a/config/alfresco/model/bpmModel.xml +++ b/config/alfresco/model/bpmModel.xml @@ -307,12 +307,17 @@ - d:int + d:intRW 2 + + + d:boolean + false + diff --git a/config/alfresco/notification-services-context.xml b/config/alfresco/notification-services-context.xml new file mode 100644 index 0000000000..e7af54d45f --- /dev/null +++ b/config/alfresco/notification-services-context.xml @@ -0,0 +1,63 @@ + + + + + + + + + org.alfresco.service.cmr.notification.NotificationService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + + + + alfresco.messages.notification-service + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index b218ff1e2f..38f48d082a 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -2944,4 +2944,32 @@
+ + + patch.workflowNotification + patch.workflowNotification.description + 0 + 5014 + 5015 + + + + + + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.workflowemailnotification.childname} + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + alfresco/bootstrap/notification/workflow-email-notification.xml + alfresco/messages/bootstrap-spaces + + + + \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 6083e73247..3822c5dcdd 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -422,6 +422,7 @@ spaces.extension_webscripts.childname=cm:extensionwebscripts spaces.models.childname=app:models spaces.workflow.definitions.childname=app:workflow_defs spaces.publishing.root.childname=app:publishing_root +spaces.templates.email.workflowemailnotification.childname=cm:workflownotification # ADM VersionStore Configuration version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 2b49cf88a5..29e0cc47e2 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=5014 +version.schema=5015 diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index b5e1925b2b..2cc261b4ac 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -580,7 +580,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if (fromPerson != null) { model.put("person", new TemplateNode(fromPerson, serviceRegistry, null)); - } + } if (ref != null) { diff --git a/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java b/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java new file mode 100644 index 0000000000..a1fb0282eb --- /dev/null +++ b/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2005-2010 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 . + */ +package org.alfresco.repo.notification; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.model.Repository; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.notification.NotificationContext; +import org.alfresco.service.cmr.notification.NotificationProvider; +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.TemplateService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.ModelUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * EMail notification provider implementation + * + * @author Roy Wetherall + */ +public class EMailNotificationProvider implements NotificationProvider +{ + /** I18N */ + private static final String MSG_DEFAULT_SENDER_USED = "default-sender-used"; + private static final String MSG_NO_RECIPIENTS = "no-recipients"; + private static final String MSG_NO_BODY_OR_TEMPLATE = "no-body-or-template"; + + /** Log */ + private static Log logger = LogFactory.getLog(EMailNotificationProvider.class); + + /** Name of provider */ + public final static String NAME = "email"; + + /** Notification service */ + private NotificationService notificationService; + + /** Node service */ + private NodeService nodeService; + + /** Action service */ + private ActionService actionService; + + /** Person service */ + private PersonService personService; + + /** Repository object */ + private Repository repository; + + /** File folder service */ + private FileFolderService fileFolderService; + + /** Repository administration service */ + private RepoAdminService repoAdminService; + + /** + * @param notificationService notification service + */ + public void setNotificationService(NotificationService notificationService) + { + this.notificationService = notificationService; + } + + /** + * @param nodeService node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param actionService action service + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * @param personService person service + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param repository repository object + */ + public void setRepository(Repository repository) + { + this.repository = repository; + } + + /** + * @param repoAdminService repository administration serviceS + */ + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + /** + * @param fileFolderService file folder service + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * Init method registers provider with notification service. + */ + public void init() + { + notificationService.register(this); + } + + /** + * @see org.alfresco.service.cmr.notification.NotificationProvider#getName() + */ + @Override + public String getName() + { + return NAME; + } + + /** + * @see org.alfresco.service.cmr.notification.NotificationProvider#sendNotification(org.alfresco.service.cmr.notification.NotificationContext) + */ + @Override + public void sendNotification(NotificationContext notificationContext) + { + Action mail = actionService.createAction(MailActionExecuter.NAME); + + // Set from parameter + String from = notificationContext.getFrom(); + if (from != null && from.length() != 0) + { + String fromEMail = getEMailFromUser(from); + if (fromEMail != null) + { + mail.setParameterValue(MailActionExecuter.PARAM_FROM, fromEMail); + } + else + { + if (logger.isWarnEnabled() == true) + { + logger.warn(I18NUtil.getMessage(MSG_DEFAULT_SENDER_USED, from)); + } + } + } + + // Set to parameter + List to = notificationContext.getTo(); + if (to == null || to.size() == 0) + { + errorEncountered(notificationContext, + I18NUtil.getMessage(MSG_NO_RECIPIENTS, notificationContext.getDocument())); + return; + } + else + { + mail.setParameterValue(MailActionExecuter.PARAM_TO_MANY, (Serializable)to); + } + + // Set subject + String subject = notificationContext.getSubject(); + if (subject != null) + { + mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subject); + } + + // Set body + String body = notificationContext.getBody(); + if (body != null && body.length() != 0) + { + mail.setParameterValue(MailActionExecuter.PARAM_TEXT, body); + } + else + { + // Check for template + NodeRef template = notificationContext.getBodyTemplate(); + if (template == null) + { + errorEncountered(notificationContext, + I18NUtil.getMessage(MSG_NO_BODY_OR_TEMPLATE, notificationContext.getDocument())); + return; + } + else + { + template = fileFolderService.getLocalizedSibling(template); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, template); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, + (Serializable)buildTemplateModel(notificationContext.getTemplateArgs())); + + + } + } + + // Set ignore failure + mail.setParameterValue(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, Boolean.valueOf(notificationContext.isIgnoreNotificationFailure())); + + // Execute mail action upon document + actionService.executeAction(mail, notificationContext.getDocument(), false, notificationContext.isAsyncNotification()); + } + + /** + * Gets the email for the given user name. Returns null if none set. + * + * @param user user name + * @return {@link String} user email + */ + private String getEMailFromUser(String user) + { + String email = null; + + NodeRef person = personService.getPerson(user); + if (person != null) + { + email = (String)nodeService.getProperty(person, ContentModel.PROP_EMAIL); + } + + return email; + } + + /** + * Build the model for the body template. + * + * @param templateArgs template args provided by the notification context + * @return {@link Map}<{@link String},{@link Serializable}> template model values + */ + private Map buildTemplateModel(Map templateArgs) + { + // Set the core model parts + // Note - the user part is skipped, as that's implied via the run-as + Map model = new HashMap(); + model.put(TemplateService.KEY_COMPANY_HOME, repository.getCompanyHome()); + NodeRef person = repository.getPerson(); + if (person != null) + { + model.put(TemplateService.KEY_PERSON, person); + model.put(TemplateService.KEY_USER_HOME, repository.getUserHome(person)); + } + model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); + + // Put the notification context information in the model? + // TODO + + if (templateArgs != null && templateArgs.size() != 0) + { + // Put the provided args in the model + model.put("args", (Serializable)templateArgs); + } + + // All done + return model; + } + + /** + * Deals with an error when it is encountered + * + * @param notificationContext notification context + * @param message error message + */ + private void errorEncountered(NotificationContext notificationContext, String message) + { + if (logger.isWarnEnabled() == true) + { + logger.warn(message); + } + + if (notificationContext.isIgnoreNotificationFailure() == false) + { + throw new AlfrescoRuntimeException(message); + } + } +} diff --git a/source/java/org/alfresco/repo/notification/NotificationServiceImpl.java b/source/java/org/alfresco/repo/notification/NotificationServiceImpl.java new file mode 100644 index 0000000000..bffe39f0a0 --- /dev/null +++ b/source/java/org/alfresco/repo/notification/NotificationServiceImpl.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005-2010 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 . + */ +package org.alfresco.repo.notification; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.notification.NotificationContext; +import org.alfresco.service.cmr.notification.NotificationProvider; +import org.alfresco.service.cmr.notification.NotificationService; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Notification service implementation. + * + * @author Roy Wetherall + */ +public class NotificationServiceImpl implements NotificationService +{ + /** I18N */ + private static final String MSG_NP_DOES_NOT_EXIST = "np-does-not-exist"; + + /** Log */ + @SuppressWarnings("unused") + private static Log logger = LogFactory.getLog(EMailNotificationProvider.class); + + /** Map of registered notification providers */ + private Map providers = new HashMap(3); + + /** + * @see org.alfresco.service.cmr.notification.NotificationService#register(org.alfresco.service.cmr.notification.NotificationProvider) + */ + @Override + public void register(NotificationProvider notificationProvider) + { + // Check mandatory params + ParameterCheck.mandatory("notificationProvider", notificationProvider); + + // Add the notification provider to the map + providers.put(notificationProvider.getName(), notificationProvider); + } + + /** + * @see org.alfresco.service.cmr.notification.NotificationService#exists(java.lang.String) + */ + @Override + public boolean exists(String notificationProvider) + { + // Check the mandatory params + ParameterCheck.mandatory("notificationProvider", notificationProvider); + + return providers.containsKey(notificationProvider); + } + + /** + * @see org.alfresco.service.cmr.notification.NotificationService#getNotificationProviders() + */ + @Override + public List getNotificationProviders() + { + return new ArrayList(providers.keySet()); + } + + /** + * @see org.alfresco.service.cmr.notification.NotificationService#sendNotification(java.lang.String, org.alfresco.service.cmr.notification.NotificationContext) + */ + @Override + public void sendNotification(String notificationProvider, NotificationContext notificationContext) + { + // Check the mandatory params + ParameterCheck.mandatory("notificationProvider", notificationProvider); + ParameterCheck.mandatory("notificationContext", notificationContext); + + // Check that the notificaiton provider exists + if (exists(notificationProvider) == false) + { + throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_NP_DOES_NOT_EXIST, notificationProvider)); + } + + // Get the notification provider and send notification + NotificationProvider provider = providers.get(notificationProvider); + provider.sendNotification(notificationContext); + } +} diff --git a/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java b/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java new file mode 100644 index 0000000000..5f5a37f56a --- /dev/null +++ b/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2005-2010 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 . + */ +package org.alfresco.repo.notification; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.notification.NotificationContext; +import org.alfresco.service.cmr.notification.NotificationService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseAlfrescoTestCase; +import org.alfresco.util.GUID; + +/** + * Notification service implementation test. + * + * @author Roy Wetherall + */ +public class NotificationServiceImplSystemTest extends BaseAlfrescoTestCase +{ + private static final String FROM_USER = "fromUser" + GUID.generate();; + private static final String FROM_EMAIL = "test@alfresco.com"; + private static final String FROM_FIRST_NAME = "Grace"; + private static final String FROM_LAST_NAME = "Wetherall"; + + private static final String TO_USER1 = "userOne" + GUID.generate();; + private static final String TO_USER2 = "userTwo" + GUID.generate();; + private static final String TO_USER3 = "userThree" + GUID.generate();; + + private static final String EMAIL = "rwetherall@alfresco.com"; + private static final String PASSWORD = "password"; + private static final String FIRST_NAME = "Peter"; + private static final String LAST_NAME = "Wetherall"; + + private static final String SUBJECT = "Notification Test"; + private static final String BODY = "This is a test notification from org.alfresco.repo.notification.NotificationServiceImplSystemTest. Please do not respond!"; + + private static final String TEMPLATE = + "" + + " " + + "

This is a test notification from org.alfresco.repo.notification.NotificationServiceImplSystemTest. Please do not respond!

" + + "
" + + " Template context:

" + + " userhome: ${userhome}
" + + " companyhome: ${companyhome}
" + + " productname: ${productName}
" + + ""; + + private NotificationService notificationService; + private MutableAuthenticationService authenticationService; + private PersonService personService; + private Repository repository; + private FileFolderService fileFolderService; + + private NodeRef fromPerson; + private NodeRef toPerson1; + private NodeRef toPerson2; + private NodeRef toPerson3; + + private NodeRef template; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + // Get the notification service + notificationService = (NotificationService)ctx.getBean("NotificationService"); + authenticationService = (MutableAuthenticationService)ctx.getBean("AuthenticationService"); + personService = (PersonService)ctx.getBean("PersonService"); + repository = (Repository)ctx.getBean("repositoryHelper"); + fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); + + retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // As system user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + + // Create people and users + fromPerson = createPerson(FROM_USER, PASSWORD, FROM_FIRST_NAME, FROM_LAST_NAME, FROM_EMAIL); + toPerson1 = createPerson(TO_USER1, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL); + toPerson2 = createPerson(TO_USER2, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL); + toPerson3 = createPerson(TO_USER3, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL); + + // Create a test template + NodeRef companyHome = repository.getCompanyHome(); + template = fileFolderService.create(companyHome, "testTemplate" + GUID.generate() + ".ftl", ContentModel.TYPE_CONTENT).getNodeRef(); + + ContentWriter writer = contentService.getWriter(template, ContentModel.PROP_CONTENT, true); + writer.setEncoding("UTF-8"); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.putContent(TEMPLATE); + + return null; + } + }); + } + + @Override + protected void tearDown() throws Exception + { + retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // As system user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + + // Delete the template + nodeService.deleteNode(template); + + return null; + } + }); + + super.tearDown(); + } + + private NodeRef createPerson(String userName, String password, String firstName, String lastName, String email) + { + NodeRef person = null; + + if (authenticationService.authenticationExists(userName) == false) + { + authenticationService.createAuthentication(userName, password.toCharArray()); + + Map properties = new HashMap(7); + properties.put(ContentModel.PROP_USERNAME, userName); + properties.put(ContentModel.PROP_FIRSTNAME, firstName); + properties.put(ContentModel.PROP_LASTNAME, lastName); + properties.put(ContentModel.PROP_EMAIL, email); + + person = personService.createPerson(properties); + } + else + { + person = personService.getPerson(userName); + } + return person; + } + + @Override + protected boolean useSpacesStore() + { + return true; + } + + public void testSimpleEmailNotification() + { + doTestInTransaction(new Test() + { + @Override + public Void run() + { + NotificationContext context = new NotificationContext(); + + context.setFrom(FROM_EMAIL); + context.addTo(TO_USER1); + context.setSubject(SUBJECT); + context.setBody(BODY); + + notificationService.sendNotification(EMailNotificationProvider.NAME, context); + + return null; + } + }); + } + + public void testTemplateEmailNotification() + { + doTestInTransaction(new Test() + { + @Override + public Void run() + { + NotificationContext context = new NotificationContext(); + + context.setFrom(FROM_EMAIL); + context.addTo(TO_USER1); + context.setSubject(SUBJECT); + context.setBodyTemplate(template); + + Map templateArgs = new HashMap(1); + templateArgs.put("template", template); + context.setTemplateArgs(templateArgs); + + notificationService.sendNotification(EMailNotificationProvider.NAME, context); + + return null; + } + }); + } +} diff --git a/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java index 8c239c86d3..59f6a47a4c 100644 --- a/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java +++ b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java @@ -51,6 +51,7 @@ import org.alfresco.service.cmr.ml.ContentFilterLanguagesService; import org.alfresco.service.cmr.ml.EditionService; import org.alfresco.service.cmr.ml.MultilingualContentService; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.notification.NotificationService; import org.alfresco.service.cmr.rating.RatingService; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; @@ -108,6 +109,12 @@ public class MockedTestServiceRegistry implements ServiceRegistry return null; } + public NotificationService getNotificationService() + { + // TODO Auto-generated method stub + return null; + } + public WebProjectService getWebProjectService() { diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index 2f4218c12e..d30dd28440 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -49,6 +49,7 @@ import org.alfresco.service.cmr.ml.ContentFilterLanguagesService; import org.alfresco.service.cmr.ml.EditionService; import org.alfresco.service.cmr.ml.MultilingualContentService; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.notification.NotificationService; import org.alfresco.service.cmr.rating.RatingService; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; @@ -339,6 +340,15 @@ public class ServiceDescriptorRegistry { return (WorkflowService)getService(WORKFLOW_SERVICE); } + + /* + * (non-Javadoc) + * @see org.alfresco.service.ServiceRegistry#getNotificationService() + */ + public NotificationService getNotificationService() + { + return (NotificationService)getService(NOTIFICATION_SERVICE); + } /* (non-Javadoc) * @see org.alfresco.service.ServiceRegistry#getWorkflowService() diff --git a/source/java/org/alfresco/repo/workflow/WorkflowModel.java b/source/java/org/alfresco/repo/workflow/WorkflowModel.java index 6ba67299fd..4e8e99a439 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowModel.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowModel.java @@ -86,5 +86,7 @@ public interface WorkflowModel static final QName PROP_WORKFLOW_DEF_ENGINE_ID = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "engineId"); static final QName PROP_WORKFLOW_DEF_NAME = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "definitionName"); static final QName PROP_WORKFLOW_DEF_DEPLOYED = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "definitionDeployed"); + + static final QName PROP_SEND_EMAIL_NOTIFICATIONS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "sendEMailNotifications"); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java b/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java new file mode 100644 index 0000000000..6802355125 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java @@ -0,0 +1,146 @@ +/** + * + */ +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.notification.EMailNotificationProvider; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.notification.NotificationContext; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.QName; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Utility class containing methods to help when sending workflow notifications. + * + * TODO? Move to workflow serivce?? + * + * @author Roy Wetherall + */ +public abstract class WorkflowNotificationUtils +{ + /** Send EMail notifications property */ + public static final String PROP_SEND_EMAIL_NOTIFICATIONS = "bpm_sendEMailNotifications"; + + /** I18N */ + public static final String MSG_ASSIGNED_TASK = "assigned-task"; + public static final String MSG_NEW_POOLED_TASK = "new-pooled-task"; + + /** Standard workflow assigned template */ + private static final NodeRef WF_ASSIGNED_TEMPLATE = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "wf-email-html-ftl"); + + /** + * Send workflow assigned email notification. + * + * @param services service registry + * @param taskId workflow global task id + * @param assignedAuthority assigned authority + * @param pooled true if pooled task, false otherwise + */ + public static void sendWorkflowAssignedNotificationEMail(ServiceRegistry services, + String taskId, + String assignedAuthority, + boolean pooled) + { + sendWorkflowAssignedNotificationEMail(services, taskId, new String[]{assignedAuthority}, pooled); + } + + /** + * Send workflow assigned email notification. + * + * @param services service registry + * @param taskId workflow global task id + * @param assignedAuthorites assigned authorities + * @param pooled true if pooled task, false otherwise + */ + public static void sendWorkflowAssignedNotificationEMail(ServiceRegistry services, + String taskId, + String[] assignedAuthorites, + boolean pooled) + { + WorkflowTask workflowTask = services.getWorkflowService().getTaskById(taskId); + Map props = workflowTask.getProperties(); + NotificationContext notificationContext = new NotificationContext(); + + // Determine the subject of the notification + String subject = null; + if (pooled == false) + { + subject = I18NUtil.getMessage(MSG_ASSIGNED_TASK); + } + else + { + subject = I18NUtil.getMessage(MSG_NEW_POOLED_TASK); + } + notificationContext.setSubject(subject); + + // Set the email template + notificationContext.setBodyTemplate(WF_ASSIGNED_TEMPLATE); + + // Build the template args + MaptemplateArgs = new HashMap(7); + templateArgs.put("workflowId", workflowTask.getId()); + templateArgs.put("workflowTitle", workflowTask.getTitle()); + + // Get the description + String description = (String)props.get(WorkflowModel.PROP_DESCRIPTION); + if (description == null) + { + description = workflowTask.getDescription(); + } + templateArgs.put("workflowDescription", description); + + // Get the due date + Date dueDate = (Date)props.get(WorkflowModel.PROP_DUE_DATE); + if (dueDate != null) + { + templateArgs.put("workflowDueDate", dueDate); + } + + // Get the workflow priority + Integer priority = (Integer)props.get(WorkflowModel.PROP_PRIORITY); + if (priority != null) + { + templateArgs.put("workflowPriority", priority); + } + + // Indicate whether this is a pooled workflow item or not + templateArgs.put("workflowPooled", pooled); + + // Add details of associated content + NodeRef workflowPackage = workflowTask.getPath().getInstance().getWorkflowPackage(); + List assocs = services.getNodeService().getChildAssocs(workflowPackage); + NodeRef[] docs = new NodeRef[assocs.size()]; + if (assocs.size() != 0) + { + int index = 0; + for (ChildAssociationRef assoc : assocs) + { + docs[index] = assoc.getChildRef(); + index++; + } + templateArgs.put("workflowDocuments", docs); + } + + // Set the template args + notificationContext.setTemplateArgs(templateArgs); + + // Set the notification recipients + for (String assignedAuthority : assignedAuthorites) + { + notificationContext.addTo(assignedAuthority); + } + + // Send email notification + services.getNotificationService().sendNotification(EMailNotificationProvider.NAME, notificationContext); + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java index cdab33c3fb..a698e0d99b 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java +++ b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java @@ -23,9 +23,12 @@ import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener; import org.activiti.engine.form.FormData; import org.activiti.engine.impl.form.TaskFormHandler; +import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.TaskEntity; +import org.alfresco.repo.workflow.WorkflowNotificationUtils; import org.alfresco.repo.workflow.activiti.ActivitiConstants; import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; +import org.alfresco.service.ServiceRegistry; /** * Tasklistener that is notified when a task is created. This will set all @@ -38,6 +41,17 @@ public class TaskCreateListener implements TaskListener { private ActivitiPropertyConverter propertyConverter; + /** Service Registry */ + private ServiceRegistry services; + + /** + * @param services the service registry + */ + public void setServices(ServiceRegistry services) + { + this.services = services; + } + @Override public void notify(DelegateTask task) { @@ -51,6 +65,19 @@ public class TaskCreateListener implements TaskListener { task.setVariableLocal(ActivitiConstants.PROP_TASK_FORM_KEY, taskFormKey); } + + // Determine whether we need to send the workflow notification or not + ExecutionEntity executionEntity = ((ExecutionEntity)task.getExecution()).getProcessInstance(); + Boolean value = (Boolean)executionEntity.getVariable(WorkflowNotificationUtils.PROP_SEND_EMAIL_NOTIFICATIONS); + if (Boolean.TRUE.equals(value) == true) + { + // Send email notification + WorkflowNotificationUtils.sendWorkflowAssignedNotificationEMail( + services, + "activiti$" + task.getId(), + task.getAssignee(), + false); + } } private String getFormKey(DelegateTask task) diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoAssignment.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoAssignment.java index bee49a070e..e06775170e 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoAssignment.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoAssignment.java @@ -25,6 +25,7 @@ import java.util.List; import org.alfresco.model.ContentModel; import org.alfresco.repo.jscript.ScriptNode; import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.workflow.WorkflowNotificationUtils; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.workflow.WorkflowException; @@ -195,17 +196,41 @@ public class AlfrescoAssignment extends JBPMSpringAssignmentHandler } } + //Determine whether we need to send email notifications of not + Boolean sendEMailNotification = (Boolean)executionContext.getVariable(WorkflowNotificationUtils.PROP_SEND_EMAIL_NOTIFICATIONS); + // // make the assignment // if (assignedActor != null) { assignable.setActorId(assignedActor); + + if (Boolean.TRUE.equals(sendEMailNotification) == true) + { + // Send the notification + WorkflowNotificationUtils.sendWorkflowAssignedNotificationEMail( + services, + JBPMEngine.ENGINE_ID + "$" + executionContext.getTaskInstance().getId(), + assignedActor, + false); + } + } if (assignedPooledActors != null) { assignable.setPooledActors(assignedPooledActors); - } + + if (Boolean.TRUE.equals(sendEMailNotification) == true) + { + // Send the notification + WorkflowNotificationUtils.sendWorkflowAssignedNotificationEMail( + services, + JBPMEngine.ENGINE_ID + "$" + executionContext.getTaskInstance().getId(), + assignedPooledActors, + true); + } + } } diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index 49f6657253..22fb9c95be 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -48,6 +48,7 @@ import org.alfresco.service.cmr.ml.ContentFilterLanguagesService; import org.alfresco.service.cmr.ml.EditionService; import org.alfresco.service.cmr.ml.MultilingualContentService; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.notification.NotificationService; import org.alfresco.service.cmr.rating.RatingService; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; @@ -142,6 +143,7 @@ public interface ServiceRegistry static final QName NODE_LOCATOR_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "nodeLocatorService"); static final QName BLOG_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "BlogService"); static final QName CALENDAR_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CalendarService"); + static final QName NOTIFICATION_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "NotificationService"); // WCM / AVM static final QName AVM_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "AVMService"); @@ -353,6 +355,12 @@ public interface ServiceRegistry */ @NotAuditable WorkflowService getWorkflowService(); + + /** + * @return the notification service (or null if on is not provided) + */ + @NotAuditable + NotificationService getNotificationService(); /** * @return the audit service (or null if one is not provided) diff --git a/source/java/org/alfresco/service/cmr/notification/NotificationContext.java b/source/java/org/alfresco/service/cmr/notification/NotificationContext.java new file mode 100644 index 0000000000..5803b236be --- /dev/null +++ b/source/java/org/alfresco/service/cmr/notification/NotificationContext.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2005-2010 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 . + */ +package org.alfresco.service.cmr.notification; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Notification context. Provides the contextual information about a notification. + * + * @author Roy Wetherall + */ +public class NotificationContext +{ + /** Authority name notification is being sent from */ + private String from; + + /** Authorities notification is being sent to */ + private List to; + + /** Subject of the notification */ + private String subject; + + /** Body of the notification */ + private String body; + + /** Template node used to generate the body of the notification */ + private NodeRef bodyTemplate; + + /** Template arguments (appear as map under 'arg' property in template model) */ + private Map templateArgs; + + /** Document giving notification context */ + private NodeRef document; + + /** Indicates whether notification failure should be ignored or not */ + private boolean ignoreNotificationFailure = true; + + /** Indicates whether the notification should be sent asynchronously or not */ + private boolean asyncNotification = false; + + /** + * Default constructor + */ + public NotificationContext() + { + to = new ArrayList(1); + } + + /** + * @param from from authority + */ + public void setFrom(String from) + { + this.from = from; + } + + /** + * @return {@link String} from authority + */ + public String getFrom() + { + return from; + } + + /** + * @param to to authorities + */ + public void addTo(String to) + { + this.to.add(to); + } + + /** + * @return {@link List}<{@link String}> to authorities + */ + public List getTo() + { + return to; + } + + /** + * @param subject subject of notification + */ + public void setSubject(String subject) + { + this.subject = subject; + } + + /** + * @return subject of notification + */ + public String getSubject() + { + return subject; + } + + /** + * Note: this takes presendence over the body template if both are set + * + * @param body body of notification. + */ + public void setBody(String body) + { + this.body = body; + } + + /** + * @return {@link String} body of notification + */ + public String getBody() + { + return body; + } + + /** + * The body template is a node reference to a template that can be executed with the given + * template arguments to produce the body of the notification. + * + * @param bodyTemplate body template + */ + public void setBodyTemplate(NodeRef bodyTemplate) + { + this.bodyTemplate = bodyTemplate; + } + + /** + * @return {@link NodeRef} body template + */ + public NodeRef getBodyTemplate() + { + return bodyTemplate; + } + + /** + * The template arguments are used as context for the body template when it is executed. Any values placed in this map will + * be available in the template from the root object 'args'. For example '${args.workflowDescription}'. + * + * @param templateArgs template arguments + */ + public void setTemplateArgs(Map templateArgs) + { + this.templateArgs = templateArgs; + } + + /** + * @return {@link Map}<{@link String}, {@link Serializable}> template arguments + */ + public Map getTemplateArgs() + { + return templateArgs; + } + + /** + * Document that the notification relates to. This does not have to be set. Will be used to populate the 'document' root object accessable within the body template + * if set. + * + * @param document related document + */ + public void setDocument(NodeRef document) + { + this.document = document; + } + + /** + * @return {@link NodeRef} related document + */ + public NodeRef getDocument() + { + return document; + } + + /** + * Indicates whether to ignore a notification failure or not. + * + * @param ignoreNotificationFailure true if ignore notification failure, false otherwise + */ + public void setIgnoreNotificationFailure(boolean ignoreNotificationFailure) + { + this.ignoreNotificationFailure = ignoreNotificationFailure; + } + + /** + * @return boolean true if ignore notification failure, false otherwise + */ + public boolean isIgnoreNotificationFailure() + { + return ignoreNotificationFailure; + } + + /** + * Indicates whether the notification will be sent asynchronously or not. + * + * @param asyncNotification true if notification sent asynchronously, false otherwise + */ + public void setAsyncNotification(boolean asyncNotification) + { + this.asyncNotification = asyncNotification; + } + + /** + * @return boolean true if notification send asynchronously, false otherwise + */ + public boolean isAsyncNotification() + { + return asyncNotification; + } +} diff --git a/source/java/org/alfresco/service/cmr/notification/NotificationProvider.java b/source/java/org/alfresco/service/cmr/notification/NotificationProvider.java new file mode 100644 index 0000000000..fa7f56bf7c --- /dev/null +++ b/source/java/org/alfresco/service/cmr/notification/NotificationProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2010 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 . + */ +package org.alfresco.service.cmr.notification; + +import org.alfresco.service.NotAuditable; + +/** + * Notification Provider interface. + * + * @author Roy Wetherall + */ +public interface NotificationProvider +{ + /** + * Gets the name of the notification provider. + * + * @return notification provider name + */ + @NotAuditable + String getName(); + + /** + * Sends a notification using the notification provider. + * + * @param notificationContext notification context + */ + @NotAuditable + void sendNotification(NotificationContext notificationContext); + +} diff --git a/source/java/org/alfresco/service/cmr/notification/NotificationService.java b/source/java/org/alfresco/service/cmr/notification/NotificationService.java new file mode 100644 index 0000000000..41a610397a --- /dev/null +++ b/source/java/org/alfresco/service/cmr/notification/NotificationService.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005-2010 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 . + */ + +package org.alfresco.service.cmr.notification; + +import java.util.List; + +import org.alfresco.service.NotAuditable; + +/** + * Notification Service Interface. + * + * @author Roy Wetherall + */ +public interface NotificationService +{ + /** + * Registers a notification provider with the notification service. + * + * @param notificationProvider notification provider + */ + @NotAuditable + void register(NotificationProvider notificationProvider); + + /** + * Gets a list of all the currently available notification providers. + * + * @return {@link List}<{@link String}> notification provider names + */ + @NotAuditable + List getNotificationProviders(); + + /** + * Indicates whether a notification provider exists or not. + * + * @param notificationProvider notification provider + * @return boolean true if exists, false otherwise + */ + @NotAuditable + boolean exists(String notificationProvider); + + /** + * Send notification using the names notification provider and notification context. + * + * @param notificationProvider notification provider + * @param notificationContext notification context + */ + @NotAuditable + void sendNotification(String notificationProvider, NotificationContext notificationContext); +} diff --git a/source/java/org/alfresco/util/BaseAlfrescoTestCase.java b/source/java/org/alfresco/util/BaseAlfrescoTestCase.java index ab12016078..444397a019 100644 --- a/source/java/org/alfresco/util/BaseAlfrescoTestCase.java +++ b/source/java/org/alfresco/util/BaseAlfrescoTestCase.java @@ -20,7 +20,9 @@ package org.alfresco.util; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.ContentService; @@ -82,6 +84,14 @@ public abstract class BaseAlfrescoTestCase extends RetryingTransactionHelperTest ctx = ApplicationContextHelper.getApplicationContext(); } + /** + * @return true if using spaces store, otherwise creates own store + */ + protected boolean useSpacesStore() + { + return false; + } + /** * @see junit.framework.TestCase#setUp() */ @@ -102,13 +112,31 @@ public abstract class BaseAlfrescoTestCase extends RetryingTransactionHelperTest this.transactionService = serviceRegistry.getTransactionService(); this.retryingTransactionHelper = (RetryingTransactionHelper)ctx.getBean("retryingTransactionHelper"); - // Authenticate as the system user - this must be done before we create the store - authenticationComponent.setSystemUserAsCurrentUser(); + retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Object execute() throws Throwable + { + // As system user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - // Create the store and get the root node - this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - this.rootNodeRef = this.nodeService.getRootNode(this.storeRef); - + if (useSpacesStore() == false) + { + // Create the store and get the root node + storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + } + else + { + // Use the spaces store + storeRef = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; + } + + // Get the root node reference + rootNodeRef = nodeService.getRootNode(storeRef); + + return null; + } + }); } /** @@ -126,15 +154,24 @@ public abstract class BaseAlfrescoTestCase extends RetryingTransactionHelperTest @Override protected void tearDown() throws Exception { - try + retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() { - authenticationComponent.clearCurrentSecurityContext(); - } - catch (Throwable e) - { - e.printStackTrace(); - // Don't let this mask any previous exceptions - } + @Override + public Object execute() throws Throwable + { + // As system user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + + if (useSpacesStore() == false) + { + // Delete the created store + nodeService.deleteStore(storeRef); + } + + return null; + } + }); + super.tearDown(); } } \ No newline at end of file diff --git a/source/java/org/alfresco/util/TestWithUserUtils.java b/source/java/org/alfresco/util/TestWithUserUtils.java index 70d18fa5bf..b9a79e144e 100644 --- a/source/java/org/alfresco/util/TestWithUserUtils.java +++ b/source/java/org/alfresco/util/TestWithUserUtils.java @@ -53,6 +53,17 @@ public abstract class TestWithUserUtils NodeService nodeService, MutableAuthenticationService authenticationService) { + createUser(userName, password, null, rootNodeRef, nodeService, authenticationService); + } + + public static void createUser( + String userName, + String password, + String email, + NodeRef rootNodeRef, + NodeService nodeService, + MutableAuthenticationService authenticationService) + { // ignore if the user's authentication already exists if (authenticationService.authenticationExists(userName)) { @@ -69,10 +80,13 @@ public abstract class TestWithUserUtils HashMap properties = new HashMap(); properties.put(ContentModel.PROP_USERNAME, userName); + if (email != null && email.length() != 0) + { + properties.put(ContentModel.PROP_EMAIL, email); + } nodeService.createNode(typesNodeRef, children, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, userName) , container, properties); - // Create the users - + // Create the users authenticationService.createAuthentication(userName, password.toCharArray()); }