diff --git a/config/alfresco/messages/quickshare-service.properties b/config/alfresco/messages/quickshare-service.properties new file mode 100644 index 0000000000..b25a94ff8f --- /dev/null +++ b/config/alfresco/messages/quickshare-service.properties @@ -0,0 +1,2 @@ +# QuickShare Service +quickshare.notifier.email.subject={0} {1} shared {2} with you \ No newline at end of file diff --git a/config/alfresco/quickshare-services-context.xml b/config/alfresco/quickshare-services-context.xml index 13627fc395..f654aea831 100644 --- a/config/alfresco/quickshare-services-context.xml +++ b/config/alfresco/quickshare-services-context.xml @@ -51,8 +51,8 @@ - - + + @@ -64,6 +64,27 @@ - + + + + + + + + + + + + + + + + + + + alfresco.messages.quickshare-service + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 4a10a1f4e0..b0ae0f6da9 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -958,9 +958,6 @@ download.cleaner.startDelayMins=60 download.cleaner.repeatIntervalMins=60 download.cleaner.maxAgeMins=60 -# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden -system.quickshare.enabled=true - # # Download Service Limits, in bytes # @@ -977,6 +974,7 @@ authority.useBridgeTable=true # enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden system.quickshare.enabled=true +system.quickshare.email.from.default=noreply@alfresco.com # Oubound Mail mail.service.corePoolSize=8 diff --git a/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl b/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl new file mode 100644 index 0000000000..55ac0e2a02 --- /dev/null +++ b/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl @@ -0,0 +1,452 @@ + + + + + + Alfresco + + + + + + + + + + +
+ + + + +
+ + + + + + +
+ + + + + + +
+ + + + +
+
+ - +
+
+
+
+ + + + + + + +
+ + + + +
Alfresco
+
+ + + + +
Simple+Smart 
+
+ + + + + + +
+
 
+
+ + + + + + + +
+ + + + +
+ +
+
+ + + + + +
+ + + + + + +
+ +

${shared_node_name} shared with you

+
+ +

${sender_first_name} ${sender_last_name} has shared ${shared_node_name} with you.

+

${sender_message}

+
+ +
+ + + + + + +
+
 
+
+ + + + + + +
+ +
+ + + + + + +
+
 
+
+ +
+
+ + + + + + +
+
 
+
+ + + + + + +
+ + + + + + +
      alfresco.com
+
+ Contact Us © 2015 Alfresco Software, Inc. All Rights Reserved.
+ Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom
+ 1825 S Grant St, Suite 900 San Mateo, CA 94402 USA
+
+ + + + + + +
+
 
+
+ +
+
+ + + + + + + +
{{my.Litmus_Code}}
+ + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java index b8a2c99f56..912e9b3fd7 100644 --- a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java +++ b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java @@ -20,16 +20,23 @@ package org.alfresco.repo.quickshare; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Set; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.events.types.ActivityEvent; import org.alfresco.events.types.Event; import org.alfresco.model.ContentModel; import org.alfresco.model.QuickShareModel; import org.alfresco.repo.Client; import org.alfresco.repo.Client.ClientType; +import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; @@ -47,8 +54,11 @@ import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.thumbnail.ThumbnailDefinition; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.preference.PreferenceService; import org.alfresco.service.cmr.quickshare.InvalidSharedIdException; import org.alfresco.service.cmr.quickshare.QuickShareDTO; import org.alfresco.service.cmr.quickshare.QuickShareDisabledException; @@ -66,11 +76,16 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.safehaus.uuid.UUID; import org.safehaus.uuid.UUIDGenerator; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.util.StringUtils; + /** * QuickShare Service implementation. @@ -78,16 +93,20 @@ import org.safehaus.uuid.UUIDGenerator; * In addition to the quick share service, this class also provides a BeforeDeleteNodePolicy and * OnCopyNodePolicy for content with the QuickShare aspect. * - * @author Alex Miller, janv + * @author Alex Miller, janv, Jamal Kaabi-Mofrad */ public class QuickShareServiceImpl implements QuickShareService, NodeServicePolicies.BeforeDeleteNodePolicy, CopyServicePolicies.OnCopyNodePolicy { private static final Log logger = LogFactory.getLog(QuickShareServiceImpl.class); static final String ATTR_KEY_SHAREDIDS_ROOT = ".sharedIds"; - - - private boolean enabled; + + private static final String FTL_SHARED_NODE_URL = "shared_node_url"; + private static final String FTL_SHARED_NODE_NAME = "shared_node_name"; + private static final String FTL_SENDER_MESSAGE = "sender_message"; + private static final String FTL_SENDER_FIRST_NAME = "sender_first_name"; + private static final String FTL_SENDER_LAST_NAME = "sender_last_name"; + private static final String DEFAULT_EMAIL_SUBJECT = "quickshare.notifier.email.subject"; private AttributeService attributeService; private DictionaryService dictionaryService; @@ -98,28 +117,15 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli private TenantService tenantService; private ThumbnailService thumbnailService; private EventPublisher eventPublisher; - + private ActionService actionService; + private PreferenceService preferenceService; /** Component to determine which behaviours are active and which not */ private BehaviourFilter behaviourFilter; - - /** - * Spring configuration - * - * @param behaviourFilter the behaviourFilter to set - */ - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - /** - * Enable or disable this service. - */ - public void setEnabled(boolean enabled) - { - this.enabled = enabled; - } - + + private boolean enabled; + private String defaultEmailSender; + private Map templateRegistry; + /** * Set the attribute service */ @@ -191,12 +197,82 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli { this.eventPublisher = eventPublisher; } - + + /** + * Set the actionService + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Set the preferenceService + */ + public void setPreferenceService(PreferenceService preferenceService) + { + this.preferenceService = preferenceService; + } + + /** + * Spring configuration + * + * @param behaviourFilter the behaviourFilter to set + */ + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * Enable or disable this service. + */ + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + /** + * Set the default email sender + */ + public void setDefaultEmailSender(String defaultEmailSender) + { + this.defaultEmailSender = defaultEmailSender; + } + + /** + * Set the templates + */ + public void setTemplateRegistry(Map templateRegistry) + { + this.templateRegistry = templateRegistry; + } + + private void checkMandatoryProperties() + { + PropertyCheck.mandatory(this, "attributeService", attributeService); + PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "permissionService", permissionService); + PropertyCheck.mandatory(this, "personService", personService); + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + PropertyCheck.mandatory(this, "tenantService", tenantService); + PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); + PropertyCheck.mandatory(this, "eventPublisher", eventPublisher); + PropertyCheck.mandatory(this, "actionService", actionService); + PropertyCheck.mandatory(this, "preferenceService", preferenceService); + PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); + PropertyCheck.mandatory(this, "defaultEmailSender", defaultEmailSender); + PropertyCheck.mandatory(this, "templateRegistry", templateRegistry); + } + /** * The initialise method. Register our policies. */ public void init() { + checkMandatoryProperties(); + // Register interest in the beforeDeleteNode policy - note: currently for content only !! policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), @@ -314,7 +390,7 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli @Override public Map getMetaData(NodeRef nodeRef) { - // TODO This functionality MUST be available when quickshare is also disabled, therefor refactor it out from the quickshare package to a more common package. + // TODO This functionality MUST be available when quickshare is also disabled, therefor refactor it out from the quickshare package to a more common package. Map nodeProps = nodeService.getProperties(nodeRef); ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); @@ -397,9 +473,9 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli } else { - QName type = nodeService.getType(nodeRef); - boolean sharable = isSharable(type); - metadata.put("sharable", sharable); + QName type = nodeService.getType(nodeRef); + boolean sharable = isSharable(type); + metadata.put("sharable", sharable); } Map model = new HashMap(2); @@ -432,8 +508,8 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli @Override public Map getMetaData(String sharedId) { - checkEnabled(); - + checkEnabled(); + Pair pair = getTenantNodeRefFromSharedId(sharedId); final String tenantDomain = pair.getFirst(); final NodeRef nodeRef = pair.getSecond(); @@ -476,24 +552,24 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli String sharedId = (String)nodeService.getProperty(beforeDeleteNodeRef, QuickShareModel.PROP_QSHARE_SHAREDID); if (sharedId != null) { - try - { - Pair pair = getTenantNodeRefFromSharedId(sharedId); - - @SuppressWarnings("unused") - final String tenantDomain = pair.getFirst(); - final NodeRef nodeRef = pair.getSecond(); - - // note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy - if (nodeRef.equals(beforeDeleteNodeRef)) - { - removeSharedId(sharedId); - } - } - catch (InvalidSharedIdException ex) - { - logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef); - } + try + { + Pair pair = getTenantNodeRefFromSharedId(sharedId); + + @SuppressWarnings("unused") + final String tenantDomain = pair.getFirst(); + final NodeRef nodeRef = pair.getSecond(); + + // note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy + if (nodeRef.equals(beforeDeleteNodeRef)) + { + removeSharedId(sharedId); + } + } + catch (InvalidSharedIdException ex) + { + logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef); + } } return null; } @@ -562,7 +638,7 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli private boolean isSharable(QName type) { - return type.equals(ContentModel.TYPE_CONTENT) || dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT); + return type.equals(ContentModel.TYPE_CONTENT) || dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT); } // Prevent copying of Quick share properties on node copy. @Override @@ -596,4 +672,263 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli } + @Override + public void sendEmailNotification(final QuickShareEmailRequest emailRequest) + { + ParameterCheck.mandatory("emailRequest", emailRequest); + emailRequest.validate(); + + if (!templateRegistry.containsKey(emailRequest.getTemplateId())) + { + throw new AlfrescoRuntimeException("Invalid template. Couldn't find a template with id:" + emailRequest.getTemplateId()); + } + + // Set the details of the person sending the email + final String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); + final NodeRef senderNodeRef = personService.getPerson(authenticatedUser, false); + final Map senderProps = nodeService.getProperties(senderNodeRef); + final String senderFirstName = (String) senderProps.get(ContentModel.PROP_FIRSTNAME); + final String senderLastName = (String) senderProps.get(ContentModel.PROP_LASTNAME); + final String senderEmail = (String) senderProps.get(ContentModel.PROP_EMAIL); + final String senderFullName = ((senderFirstName != null ? senderFirstName + " " : "") + (senderLastName != null ? senderLastName : "")).trim(); + + // Set the default model information + Map templateModel = new HashMap<>(5); + templateModel.put(FTL_SENDER_FIRST_NAME, senderFirstName); + templateModel.put(FTL_SENDER_LAST_NAME, senderLastName); + templateModel.put(FTL_SHARED_NODE_URL, emailRequest.getSharedNodeURL()); + templateModel.put(FTL_SHARED_NODE_NAME, emailRequest.getSharedNodeName()); + templateModel.put(FTL_SENDER_MESSAGE, emailRequest.getSenderMessage()); + + // Email sender + // By default the current-user's email address will not be used to send this mail. + // However, current-user's first and lastname will be used as the personal name. + final String from = (!emailRequest.isSendFromDefaultEmail() && senderEmail != null) ? senderEmail : this.defaultEmailSender; + + // Set the email details + Map actionParams = new HashMap<>(); + actionParams.put(MailActionExecuter.PARAM_FROM, from); + actionParams.put(MailActionExecuter.PARAM_FROM_PERSONAL_NAME, senderFullName); + actionParams.put(MailActionExecuter.PARAM_SUBJECT, DEFAULT_EMAIL_SUBJECT); + actionParams.put(MailActionExecuter.PARAM_SUBJECT_PARAMS, new Object[] { senderFirstName, senderLastName, emailRequest.getSharedNodeName() }); + actionParams.put(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, emailRequest.isIgnoreSendFailure()); + // Pick the template + actionParams.put(MailActionExecuter.PARAM_TEMPLATE, templateRegistry.get(emailRequest.getTemplateId())); + actionParams.put(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable) templateModel); + actionParams.put(MailActionExecuter.PARAM_LOCALE, getDefaultIfNull(getEmailCreatorLocale(authenticatedUser), emailRequest.getLocale())); + + for (String to : emailRequest.getToEmails()) + { + Map params = new HashMap<>(actionParams); + params.put(MailActionExecuter.PARAM_TO, to); + Action mailAction = actionService.createAction(MailActionExecuter.NAME, params); + actionService.executeAction(mailAction, null, false, true); + } + } + + private T getDefaultIfNull(T defaultValue, T newValue) + { + return (newValue == null) ? defaultValue : newValue; + } + + private Locale getEmailCreatorLocale(String userId) + { + String localeString = (String) preferenceService.getPreference(userId, "locale"); + return I18NUtil.parseLocale(localeString); + } + + /** + * Represents an email request to send a quick share link. + */ + public static class QuickShareEmailRequest + { + /** + * Whether to send an email from the default email address or use the current-user's email (if not null). + * The default is true. + */ + private boolean sendFromDefaultEmail = true; + + /** + * Optional Locale for subject and body text + */ + private Locale locale; + + /** + * The email addresses (1 or many) of the recipients. + */ + private Set toEmails; + + /** + * The template id. If not provided, a default template will be used. + */ + private String templateId = "default"; + + /** + * The shared content URL. + */ + private String sharedNodeURL; + + /** + * The shared content name. + */ + private String sharedNodeName; + /** + * Optional message from the sender. + */ + private String senderMessage; + + /** + * Whether to ignore throwing exception or not. The default is false. + */ + private boolean ignoreSendFailure = false; + + public void validate() + { + ParameterCheck.mandatoryCollection("toEmails", toEmails); + ParameterCheck.mandatoryString("templateId", templateId); + ParameterCheck.mandatoryString("sharedNodeURL", sharedNodeURL); + ParameterCheck.mandatoryString("sharedNodeName", sharedNodeName); + ParameterCheck.mandatoryString("senderMessage", senderMessage); + } + + /** + * {@link QuickShareEmailRequest#sendFromDefaultEmail} + */ + public boolean isSendFromDefaultEmail() + { + return sendFromDefaultEmail; + } + + /** + * {@link QuickShareEmailRequest#sendFromDefaultEmail} + */ + public void setSendFromDefaultEmail(Boolean sendFromDefaultEmail) + { + if (sendFromDefaultEmail != null) + { + this.sendFromDefaultEmail = sendFromDefaultEmail; + } + } + + /** + * {@link QuickShareEmailRequest#locale} + */ + public Locale getLocale() + { + return this.locale; + } + + /** + * {@link QuickShareEmailRequest#locale} + */ + public void setLocale(Locale locale) + { + this.locale = locale; + } + + /** + * {@link QuickShareEmailRequest#toEmails} + */ + public Set getToEmails() + { + return this.toEmails; + } + + /** + * {@link QuickShareEmailRequest#toEmails} + */ + public void setToEmails(Collection toEmails) + { + if (toEmails != null) + { + this.toEmails = Collections.unmodifiableSet(new HashSet<>(toEmails)); + } + } + + /** + * {@link QuickShareEmailRequest#templateId} + */ + public String getTemplateId() + { + return templateId; + } + + /** + * {@link QuickShareEmailRequest#templateId} + */ + public void setTemplateId(String templateId) + { + if (templateId != null) + { + this.templateId = templateId; + } + } + + /** + * {@link QuickShareEmailRequest#sharedNodeURL} + */ + public String getSharedNodeURL() + { + return sharedNodeURL; + } + + /** + * {@link QuickShareEmailRequest#sharedNodeURL} + */ + public void setSharedNodeURL(String sharedNodeURL) + { + this.sharedNodeURL = sharedNodeURL; + } + + /** + * {@link QuickShareEmailRequest#sharedNodeName} + */ + public String getSharedNodeName() + { + return sharedNodeName; + } + + /** + * {@link QuickShareEmailRequest#sharedNodeName} + */ + public void setSharedNodeName(String sharedNodeName) + { + this.sharedNodeName = sharedNodeName; + } + + /** + * {@link QuickShareEmailRequest#senderMessage} + */ + public String getSenderMessage() + { + return senderMessage; + } + + /** + * {@link QuickShareEmailRequest#senderMessage} + */ + public void setSenderMessage(String senderMessage) + { + this.senderMessage = senderMessage; + } + + /** + * {@link QuickShareEmailRequest#ignoreSendFailure} + */ + public boolean isIgnoreSendFailure() + { + return ignoreSendFailure; + } + + /** + * {@link QuickShareEmailRequest#ignoreSendFailure} + */ + public void setIgnoreSendFailure(Boolean ignoreSendFailure) + { + if (ignoreSendFailure != null) + { + this.ignoreSendFailure = ignoreSendFailure; + } + } + } } diff --git a/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java b/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java index 808d554ddc..e93d703456 100644 --- a/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java +++ b/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2016 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,6 +20,7 @@ package org.alfresco.service.cmr.quickshare; import java.util.Map; +import org.alfresco.repo.quickshare.QuickShareServiceImpl.QuickShareEmailRequest; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.Pair; @@ -77,4 +78,11 @@ public interface QuickShareService * Determine if the current user has permission to read the shared content. */ public boolean canRead(String sharedId); + + /** + * Notifies users by email that a content has been shared with them, and the details of it. + * + * @param emailRequest The email details including its template details + */ + public void sendEmailNotification(QuickShareEmailRequest emailRequest); }