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
This commit is contained in:
Roy Wetherall
2011-08-12 04:26:24 +00:00
parent 2f926fc967
commit 7430652942
30 changed files with 1577 additions and 20 deletions

View File

@@ -580,7 +580,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
if (fromPerson != null)
{
model.put("person", new TemplateNode(fromPerson, serviceRegistry, null));
}
}
if (ref != null)
{

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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<String, Serializable> buildTemplateModel(Map<String, Serializable> templateArgs)
{
// Set the core model parts
// Note - the user part is skipped, as that's implied via the run-as
Map<String, Serializable> model = new HashMap<String, Serializable>();
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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, NotificationProvider> providers = new HashMap<String, NotificationProvider>(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<String> getNotificationProviders()
{
return new ArrayList<String>(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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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 =
"<html>" +
" <body bgcolour='#dddddd'>" +
" <p>This is a test notification from org.alfresco.repo.notification.NotificationServiceImplSystemTest. Please do not respond!</p>" +
" <br>" +
" Template context:<br><br>" +
" userhome: ${userhome}<br>" +
" companyhome: ${companyhome}<br>" +
" productname: ${productName}<br>" +
"";
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<Void>()
{
@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<Void>()
{
@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<QName, Serializable> properties = new HashMap<QName, Serializable>(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<Void>()
{
@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<Void>()
{
@Override
public Void run()
{
NotificationContext context = new NotificationContext();
context.setFrom(FROM_EMAIL);
context.addTo(TO_USER1);
context.setSubject(SUBJECT);
context.setBodyTemplate(template);
Map<String, Serializable> templateArgs = new HashMap<String, Serializable>(1);
templateArgs.put("template", template);
context.setTemplateArgs(templateArgs);
notificationService.sendNotification(EMailNotificationProvider.NAME, context);
return null;
}
});
}
}

View File

@@ -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()
{

View File

@@ -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()

View File

@@ -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");
}

View File

@@ -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<QName, Serializable> 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
Map<String, Serializable>templateArgs = new HashMap<String, Serializable>(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<ChildAssociationRef> 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);
}
}

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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<String, Serializable> 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<String>(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<String> 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<String, Serializable> templateArgs)
{
this.templateArgs = templateArgs;
}
/**
* @return {@link Map}<{@link String}, {@link Serializable}> template arguments
*/
public Map<String, Serializable> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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);
}

View File

@@ -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<Object>()
{
@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<Object>()
{
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();
}
}

View File

@@ -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<QName, Serializable> properties = new HashMap<QName, Serializable>();
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());
}