diff --git a/config/alfresco/quickshare-services-context.xml b/config/alfresco/quickshare-services-context.xml index f654aea831..8e34dc4d0a 100644 --- a/config/alfresco/quickshare-services-context.xml +++ b/config/alfresco/quickshare-services-context.xml @@ -51,6 +51,19 @@ + + + + classpath*:alfresco/quickshare/quickshare-clients.properties + + + + + + + + + @@ -67,16 +80,7 @@ - - - - - - - - - - + diff --git a/config/alfresco/quickshare/quickshare-clients.properties b/config/alfresco/quickshare/quickshare-clients.properties new file mode 100644 index 0000000000..9187b8072f --- /dev/null +++ b/config/alfresco/quickshare/quickshare-clients.properties @@ -0,0 +1,4 @@ +# registry of clients that are able to email shared links + +quickshare.client.sfs.sharedLinkBaseUrl=http://localhost:8082/sfs/s +quickshare.client.sfs.templateAssetsUrl=http://localhost:8082/sfs 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 index 55ac0e2a02..1bf90e535d 100644 --- a/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl +++ b/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl @@ -261,7 +261,7 @@ "http://go.alfresco.com/a0100K050L0000KZjI2d00U" target="_blank" >Alfresco @@ -298,7 +298,7 @@ @@ -394,23 +394,23 @@ middle;">

${shared_node_name} shared with you

        + src="${template_assets_url}/Alfresco_Icon_Social_Google.png" width="23" height="23" border="0"> alfresco.com
+ src="${template_assets_url}/AlfrrescoGlobal_Logo_BW.png" border="0" alt="alfresco.com" width="92" height="29">
diff --git a/source/java/org/alfresco/repo/quickshare/ClientAppConfig.java b/source/java/org/alfresco/repo/quickshare/ClientAppConfig.java new file mode 100644 index 0000000000..de7346d765 --- /dev/null +++ b/source/java/org/alfresco/repo/quickshare/ClientAppConfig.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2005-2016 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.quickshare; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * This class picks up all the loaded properties passed to it and uses a naming + * convention to isolate the client's name and related values. + * So, if a new client (e.g. MyClientName) is required to send shared-link email, then the following + * needs to be put into a properties file. + * + * The default property file is alfresco/quickshare/quickshare-clients.properties which + * could be overridden by alfresco-global properties file. + * + * @author Jamal Kaabi-Mofrad + */ +public class ClientAppConfig extends AbstractLifecycleBean +{ + private static final Log logger = LogFactory.getLog(ClientAppConfig.class); + + public static final String PREFIX = "quickshare.client."; + public static final String PROP_SHARED_LINK_BASE_URL = "sharedLinkBaseUrl"; + public static final String PROP_TEMPLATE_ASSETS_URL = "templateAssetsUrl"; + + private Properties defaultProperties; + private Properties globalProperties; + + private ConcurrentMap clients = new ConcurrentHashMap<>(); + + public ClientAppConfig() + { + } + + public void setDefaultProperties(Properties defaultProperties) + { + this.defaultProperties = defaultProperties; + } + + public void setGlobalProperties(Properties globalProperties) + { + this.globalProperties = globalProperties; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + load(); + if (logger.isDebugEnabled()) + { + logger.debug("All bootstrapped quickShare clients: " + clients); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // nothing to do + } + + public void load() + { + Map mergedProperties = getAndMergeProperties(); + Set clientsNames = processPropertyKeys(mergedProperties); + clients.putAll(processClients(clientsNames, mergedProperties)); + } + + public Map getClients() + { + return Collections.unmodifiableMap(clients); + } + + public ClientApp getClient(String name) + { + return clients.get(name); + } + + public void setClient(ClientApp client) + { + if (client != null) + { + clients.put(client.getName(), client); + } + } + + public boolean removeClient(String name) + { + ClientApp client = clients.remove(name); + return (client != null); + } + + protected Set processPropertyKeys(Map allProps) + { + Set clientsNames = new HashSet<>(); + for (String key : allProps.keySet()) + { + String propKey = key; + if (propKey.startsWith(PREFIX)) + { + propKey = propKey.substring(PREFIX.length()); + // Find the client name + int clientNameControlDot = propKey.indexOf('.'); + if (clientNameControlDot < 1) + { + logMalformedPropertyKey(key); + continue; + } + + int propNameLength = (propKey.length() - clientNameControlDot) - 1; // Length of characters between dots + if (propNameLength < 1) + { + logMalformedPropertyKey(key); + continue; + } + String clientName = propKey.substring(0, clientNameControlDot); + String propName = propKey.substring((clientNameControlDot + 1)); + if (PROP_SHARED_LINK_BASE_URL.equals(propName) || PROP_TEMPLATE_ASSETS_URL.equals(propName)) + { + clientsNames.add(clientName); + } + else + { + logMalformedPropertyKey(key); + } + } + else + { + logMalformedPropertyKey(propKey); + } + } + return clientsNames; + } + + protected Map processClients(Set clientsNames, Map allProps) + { + Map clientApps = new HashMap<>(clientsNames.size()); + for (String name : clientsNames) + { + String propKey = getPropertyKey(name, PROP_SHARED_LINK_BASE_URL); + String sharedLinkBaseUrl = allProps.get(propKey); + if (StringUtils.isEmpty(sharedLinkBaseUrl)) + { + logInvalidPropertyValue(propKey, sharedLinkBaseUrl); + continue; + } + + propKey = getPropertyKey(name, PROP_TEMPLATE_ASSETS_URL); + String templateAssetsUrl = allProps.get(propKey); + if (StringUtils.isEmpty(templateAssetsUrl)) + { + logInvalidPropertyValue(propKey, templateAssetsUrl); + continue; + } + // Create the client data + ClientApp client = new ClientApp(name, sharedLinkBaseUrl, templateAssetsUrl); + clientApps.put(name, client); + } + return clientApps; + } + + protected Map getAndMergeProperties() + { + Map allProperties = new HashMap<>(); + for (String propKey : defaultProperties.stringPropertyNames()) + { + allProperties.put(propKey, defaultProperties.getProperty(propKey)); + + } + + //override default values from other property files + for (String propKey : globalProperties.stringPropertyNames()) + { + if (propKey.startsWith(PREFIX)) + { + allProperties.put(propKey, globalProperties.getProperty(propKey)); + } + } + + return allProperties; + } + + private void logMalformedPropertyKey(String propName) + { + logger.warn("Ignoring quickShare client (malformed property key): " + propName); + } + + private void logInvalidPropertyValue(String propName, String propValue) + { + logger.warn("Ignoring quickShare client (invalid value) [" + propValue + "] for the property:" + propName); + } + + private String getPropertyKey(String clientName, String clientProp) + { + return PREFIX + clientName + '.' + clientProp; + } + + public static class ClientApp + { + private final String name; + private final String sharedLinkBaseUrl; + private final String templateAssetsUrl; + + public ClientApp(String name, String sharedLinkBaseUrl, String templateAssetsUrl) + { + this.name = name; + this.sharedLinkBaseUrl = sharedLinkBaseUrl; + this.templateAssetsUrl = templateAssetsUrl; + } + + public String getName() + { + return name; + } + + public String getSharedLinkBaseUrl() + { + return sharedLinkBaseUrl; + } + + public String getTemplateAssetsUrl() + { + return templateAssetsUrl; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(o instanceof ClientApp)) + { + return false; + } + + ClientApp clientApp = (ClientApp) o; + return getName().equals(clientApp.getName()); + } + + @Override + public int hashCode() + { + return getName().hashCode(); + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(250); + sb.append("ClientApp [name=").append(name) + .append(", sharedLinkBaseUrl=").append(sharedLinkBaseUrl) + .append(", templateAssetsUrl=").append(templateAssetsUrl) + .append(']'); + return sb.toString(); + } + } +} diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareClientNotFoundException.java b/source/java/org/alfresco/repo/quickshare/QuickShareClientNotFoundException.java new file mode 100644 index 0000000000..89168da280 --- /dev/null +++ b/source/java/org/alfresco/repo/quickshare/QuickShareClientNotFoundException.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2016 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.quickshare; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class QuickShareClientNotFoundException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -2567136374291827783L; + + public QuickShareClientNotFoundException(String msgId) + { + super(msgId); + } + + public QuickShareClientNotFoundException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } +} diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java index f9977ae4a0..b88dbb6b4a 100644 --- a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java +++ b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java @@ -29,7 +29,6 @@ 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; @@ -47,6 +46,7 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.quickshare.ClientAppConfig.ClientApp; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -105,7 +105,9 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli 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 FTL_TEMPLATE_ASSETS_URL = "template_assets_url"; private static final String DEFAULT_EMAIL_SUBJECT = "quickshare.notifier.email.subject"; + private static final String EMAIL_TEMPLATE_REF ="alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl"; private AttributeService attributeService; private DictionaryService dictionaryService; @@ -123,7 +125,7 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli private boolean enabled; private String defaultEmailSender; - private Map templateRegistry; + private ClientAppConfig clientAppConfig; /** * Set the attribute service @@ -240,11 +242,11 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli } /** - * Set the templates + * Set the quickShare clientAppConfig */ - public void setTemplateRegistry(Map templateRegistry) + public void setClientAppConfig(ClientAppConfig clientAppConfig) { - this.templateRegistry = templateRegistry; + this.clientAppConfig = clientAppConfig; } private void checkMandatoryProperties() @@ -262,7 +264,7 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli PropertyCheck.mandatory(this, "preferenceService", preferenceService); PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); PropertyCheck.mandatory(this, "defaultEmailSender", defaultEmailSender); - PropertyCheck.mandatory(this, "templateRegistry", templateRegistry); + PropertyCheck.mandatory(this, "clientAppConfig", clientAppConfig); } /** @@ -677,9 +679,10 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli ParameterCheck.mandatory("emailRequest", emailRequest); emailRequest.validate(); - if (!templateRegistry.containsKey(emailRequest.getTemplateId())) + ClientApp clientApp = clientAppConfig.getClient(emailRequest.getClient()); + if (clientApp == null) { - throw new AlfrescoRuntimeException("Invalid template. Couldn't find a template with id:" + emailRequest.getTemplateId()); + throw new QuickShareClientNotFoundException("Client was not found [" + emailRequest.getClient() + "]"); } // Set the details of the person sending the email @@ -688,31 +691,30 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli 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); + Map templateModel = new HashMap<>(6); templateModel.put(FTL_SENDER_FIRST_NAME, senderFirstName); templateModel.put(FTL_SENDER_LAST_NAME, senderLastName); - templateModel.put(FTL_SHARED_NODE_URL, emailRequest.getSharedNodeURL()); + final String sharedNodeUrl = getUrl(clientApp.getSharedLinkBaseUrl()) + '/' + emailRequest.getSharedId(); + templateModel.put(FTL_SHARED_NODE_URL, sharedNodeUrl); 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; + String templateAssetsUrl = getUrl(clientApp.getTemplateAssetsUrl()); + templateModel.put(FTL_TEMPLATE_ASSETS_URL, templateAssetsUrl); // Set the email details Map actionParams = new HashMap<>(); - actionParams.put(MailActionExecuter.PARAM_FROM, from); + // 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. + actionParams.put(MailActionExecuter.PARAM_FROM, this.defaultEmailSender); 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, EMAIL_TEMPLATE_REF); actionParams.put(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable) templateModel); actionParams.put(MailActionExecuter.PARAM_LOCALE, getDefaultIfNull(getEmailCreatorLocale(authenticatedUser), emailRequest.getLocale())); @@ -725,6 +727,15 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli } } + private String getUrl(String url) + { + if (url.endsWith("/")) + { + return url.substring(0, url.length() - 1); + } + return url; + } + private T getDefaultIfNull(T defaultValue, T newValue) { return (newValue == null) ? defaultValue : newValue; @@ -742,11 +753,9 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli 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. + * The client's name that must be registered in order to send emails. */ - private boolean sendFromDefaultEmail = true; - + private String client; /** * Optional Locale for subject and body text */ @@ -758,19 +767,15 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli private Set toEmails; /** - * The template id. If not provided, a default template will be used. + * The shared id. */ - private String templateId = "default"; - - /** - * The shared content URL. - */ - private String sharedNodeURL; + private String sharedId; /** * The shared content name. */ private String sharedNodeName; + /** * Optional message from the sender. */ @@ -784,29 +789,8 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli public void validate() { ParameterCheck.mandatoryCollection("toEmails", toEmails); - ParameterCheck.mandatoryString("templateId", templateId); - ParameterCheck.mandatoryString("sharedNodeURL", sharedNodeURL); + ParameterCheck.mandatoryString("sharedId", sharedId); 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; - } } /** @@ -845,38 +829,37 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli } /** - * {@link QuickShareEmailRequest#templateId} + * {@link QuickShareEmailRequest#client} */ - public String getTemplateId() + public String getClient() { - return templateId; + return client; } /** - * {@link QuickShareEmailRequest#templateId} + * {@link QuickShareEmailRequest#client} */ - public void setTemplateId(String templateId) + public QuickShareEmailRequest setClient(String client) { - if (templateId != null) - { - this.templateId = templateId; - } + this.client = client; + return this; } /** - * {@link QuickShareEmailRequest#sharedNodeURL} + * {@link QuickShareEmailRequest#sharedId} */ - public String getSharedNodeURL() + public String getSharedId() { - return sharedNodeURL; + return sharedId; } /** - * {@link QuickShareEmailRequest#sharedNodeURL} + * {@link QuickShareEmailRequest#sharedId} */ - public void setSharedNodeURL(String sharedNodeURL) + public QuickShareEmailRequest setSharedId(String sharedId) { - this.sharedNodeURL = sharedNodeURL; + this.sharedId = sharedId; + return this; } /**