diff --git a/config/alfresco/alfresco-shared.properties b/config/alfresco/alfresco-shared.properties index 42502b77a7..40d88ffc55 100644 --- a/config/alfresco/alfresco-shared.properties +++ b/config/alfresco/alfresco-shared.properties @@ -2,19 +2,12 @@ # Properties shared between the Alfresco server # and its remote clients (e.g.: the virtualization server). # -# Ports used by Alfresco AVM +# Ports used by Alfresco # # Note: These ports are also used by the virtualization server -# (hence, they're in a seperate file that's can be copied easily). - - -# Name of the host running AVM -avm.remote.host=localhost - -# AVMRemote API -avm.remote.port=50500 +# (hence, they're in a seperate file that can be copied easily). # Remote RMI services -rmi.services.remote.port=${avm.remote.port} -rmi.services.remote.host=${avm.remote.host} +alfresco.rmi.services.port=50500 +alfresco.rmi.services.host=localhost diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 9f5483131c..7b17e237bf 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -15,7 +15,7 @@ - + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index cbd185cf06..06ab618468 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -100,6 +100,10 @@ /${alfresco_user_store.system_container.childname} alfresco/bootstrap/alfrescoAuthorityStorePermission.xml + + /${alfresco_user_store.system_container.childname}/sys:authorities + alfresco/bootstrap/emailServer.xml + @@ -432,12 +436,25 @@ - diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 605a21dcb6..e1e96ac459 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -55,7 +55,7 @@ - + @@ -65,7 +65,7 @@ - + diff --git a/config/alfresco/email-server.properties b/config/alfresco/email-server.properties deleted file mode 100644 index 40c6ac6a8b..0000000000 --- a/config/alfresco/email-server.properties +++ /dev/null @@ -1,6 +0,0 @@ -mail.inbound.enabled=false -email.server.enabled=false -email.server.port=25 -email.server.domain=alfresco.com -email.server.allowed.senders= -email.server.blocked.senders= diff --git a/config/alfresco/emailserver/email-server.properties b/config/alfresco/emailserver/email-server.properties new file mode 100644 index 0000000000..e5c0528eb3 --- /dev/null +++ b/config/alfresco/emailserver/email-server.properties @@ -0,0 +1,13 @@ +# +# Alfresco Email Service and Email Server +# - See the sample config for descriptions of the properties +# + +email.inbound.unknownUser=anonymous +email.inbound.enabled=false + +email.server.enabled=false +email.server.port=25 +email.server.domain=alfresco.com +email.server.allowed.senders= +email.server.blocked.senders= \ No newline at end of file diff --git a/config/alfresco/email-service-context.xml b/config/alfresco/emailserver/email-service-context.xml similarity index 65% rename from config/alfresco/email-service-context.xml rename to config/alfresco/emailserver/email-service-context.xml index 5964368508..48722a1afa 100644 --- a/config/alfresco/email-service-context.xml +++ b/config/alfresco/emailserver/email-service-context.xml @@ -13,45 +13,17 @@ - true - classpath:alfresco/email-server.properties + classpath:alfresco/emailserver/email-server.properties - - - - ${email.server.domain} - - - - ${email.server.port} - - - - ${email.server.enabled} - - - - ${email.server.blocked.senders} - - - - ${email.server.allowed.senders} - - - - - - - @@ -60,55 +32,74 @@ org.alfresco.service.cmr.email.EmailService - emailService + EmailService - ${avm.remote.port} + ${alfresco.rmi.services.port} + + - - - ${mail.inbound.enabled} + + ${email.inbound.enabled} + + + ${email.inbound.unknownUser} - - - - + - - - - - - - - - - - - - + + + + + + + + + + + + + - + + + - + - + - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - @@ -162,5 +132,4 @@ - diff --git a/config/alfresco/extension/bm-remote-loader-context.xml.sample b/config/alfresco/extension/bm-remote-loader-context.xml.sample index 83df9e444c..69aa98ef56 100644 --- a/config/alfresco/extension/bm-remote-loader-context.xml.sample +++ b/config/alfresco/extension/bm-remote-loader-context.xml.sample @@ -32,7 +32,7 @@ org.alfresco.FileFolderRemote - ${rmi.services.remote.port} + ${alfresco.rmi.services.port} @@ -70,7 +70,7 @@ org.alfresco.LoaderRemote - ${rmi.services.remote.port} + ${alfresco.rmi.services.port} diff --git a/config/alfresco/extension/custom-email-server.sample.zip b/config/alfresco/extension/custom-email-server.sample.zip new file mode 100644 index 0000000000..806fee01af Binary files /dev/null and b/config/alfresco/extension/custom-email-server.sample.zip differ diff --git a/config/alfresco/messages/email-service.properties b/config/alfresco/messages/email-service.properties index ab7f00eaa4..a2b66e2f8e 100644 --- a/config/alfresco/messages/email-service.properties +++ b/config/alfresco/messages/email-service.properties @@ -1,17 +1,24 @@ -email.server.denied-address="Address {0} is in black list" -email.server.not-white-address="Address {0} is not in white list" -email.server.incorrect-node-ref="Incorrect node ref" -email.server.incorrect-node-address="Incorrect node address" -email.server.incorrect-node-type="Incorrect node type" -email.server.handler-not-found="Hanlder wasn't found" -email.server.unknown-user="Unknown user is not in email contribute group" -email.server.not-contribute-user="User {0} is not in contribute group" -email.server.contribute-group-not-exist="Email contribute group is not created" -email.server.content-error="Content error" -email.server.incorrect-message-part="Incorrect message part" -email.server.error-getting-message-content="Couldn't get message part content" -email.server.error-getting-content-stream="Couldn't get stream of the message part content" -email.server.error-creating-message="Couldn't create MIME message from input stream" -email.server.error-parse-message="Couldn't parse the message" -email.server.mail-inbound-disabled="Email behaviour to be disabled completely on the server" -email.server.usupported-encoding="Encoding {0} is not support" \ No newline at end of file +email.server.msg.received_by_smtp=Received by SMTP from ''{0}''. +email.server.msg.default_subject=Email-{0} + +email.server.err.sender_blocked=''{0}'' has been denied access. +email.server.err.inbound_mail_disabled=The Alfresco server is not configured to accept inbound emails. +email.server.err.access_denied=''{0}'' has been denied access to ''{1}''. +email.server.err.invalid_subject=The subject line must be a valid file name. +email.server.err.unknown_source_address=The 'from' email address was not recognised: {0}. +email.server.err.user_not_email_contributor=The user ''{0}'' in not in the email contributor group. +email.server.err.no_email_contributor_group=The Email Contributor Group doesn't exist. +email.server.err.invalid_node_address=The email address ''{0}'' does not reference a valid accessible node. +email.server.err.handler_not_found=Email message handler was not found for node type ''{0}''. +email.server.err.mail_read_error=An error occured while reading the mail message: {0} +email.server.err.failed_to_create_mime_message=Failed to create MIME message from input stream: {0} +email.server.err.extracting_from_address=Failed to extract the 'from' address: {0} +email.server.err.no_from_address=The message has no 'from' address. +email.server.err.extracting_to_address=Failed to extract the 'to' address: {0} +email.server.err.no_to_address=The message has no 'to' address. +email.server.err.extracting_subject=Failed to extract the message subject: {0} +email.server.err.extracting_sent_date=Failed to extract the 'sent on' date: {0} +email.server.err.parse_message=Failed to parse the email message: {0} +email.server.err.usupported_encoding=Encoding ''{0}'' is not supported +email.server.err.failed_to_read_content_stream=Failed to read the message part content: {0} +email.server.err.incorrect_message_part=Incorrect message part: {0} diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index cc0251fb47..2886f3e156 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -177,3 +177,5 @@ patch.customMessages.description=Adds Messages space to Data Dictionary. patch.customWebClientExtension.description=Adds Web Client Extension space to Data Dictionary. patch.customWorkflowDefs.description=Adds Workflow Definitions space to Data Dictionary. + +patch.emailContributorGroup.description=Adds the 'GROUP_EMAIL_CONTRIBUTORS' group. diff --git a/config/alfresco/model/emailServerModel.xml b/config/alfresco/model/emailServerModel.xml index 721f67ba4e..54ed5516d0 100644 --- a/config/alfresco/model/emailServerModel.xml +++ b/config/alfresco/model/emailServerModel.xml @@ -25,6 +25,13 @@ prefix="emailserver" /> + + + + true + + + @@ -51,12 +58,15 @@ - Aliasable + Email Alias Alias d:text true + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index dca8aef4fe..0e6315ab66 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1037,5 +1037,26 @@ + + + patch.emailContributorGroup + patch.emailContributorGroup.description + 0 + 108 + 109 + + + + + + /${alfresco_user_store.system_container.childname}/sys:authorities/usr:GROUP_EMAIL_CONTRIBUTORS + + + + /${alfresco_user_store.system_container.childname}/sys:authorities + alfresco/bootstrap/emailServer.xml + + + diff --git a/config/alfresco/remote-services-context.xml b/config/alfresco/remote-services-context.xml index cabf719b74..b459942f63 100644 --- a/config/alfresco/remote-services-context.xml +++ b/config/alfresco/remote-services-context.xml @@ -29,7 +29,7 @@ avm - ${avm.remote.port} + ${alfresco.rmi.services.port} @@ -53,7 +53,7 @@ avmsync - ${avm.remote.port} + ${alfresco.rmi.services.port} @@ -78,7 +78,7 @@ attributes - ${avm.remote.port} + ${alfresco.rmi.services.port} @@ -94,7 +94,7 @@ authentication - ${avm.remote.port} + ${alfresco.rmi.services.port} @@ -161,7 +161,7 @@ repo - ${avm.remote.port} + ${alfresco.rmi.services.port} @@ -187,7 +187,7 @@ action - ${avm.remote.port} + ${alfresco.rmi.services.port} \ No newline at end of file diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 31aec46bb6..f4856caa1e 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=108 +version.schema=109 diff --git a/source/java/org/alfresco/email/server/AliasableAspect.java b/source/java/org/alfresco/email/server/AliasableAspect.java index a2feb26b34..1c33eaaf88 100644 --- a/source/java/org/alfresco/email/server/AliasableAspect.java +++ b/source/java/org/alfresco/email/server/AliasableAspect.java @@ -54,8 +54,9 @@ public class AliasableAspect implements NodeServicePolicies.OnAddAspectPolicy, N private SearchService searchService; - public static final String SEARCH_TEMPLATE = "ASPECT:\"" + EmailServerModel.ASPECT_ALIASABLE + "\" +@" + NamespaceService.EMAILSERVER_MODEL_PREFIX + "\\:" - + EmailServerModel.PROP_ALIAS.getLocalName() + ":\"%s\""; + public static final String SEARCH_TEMPLATE = + "ASPECT:\"" + EmailServerModel.ASPECT_ALIASABLE + + "\" +@" + NamespaceService.EMAILSERVER_MODEL_PREFIX + "\\:" + EmailServerModel.PROP_ALIAS.getLocalName() + ":\"%s\""; /** * @param searchService Alfresco Search Service diff --git a/source/java/org/alfresco/email/server/EmailServer.java b/source/java/org/alfresco/email/server/EmailServer.java index 60ec4767f0..40331c155d 100644 --- a/source/java/org/alfresco/email/server/EmailServer.java +++ b/source/java/org/alfresco/email/server/EmailServer.java @@ -24,9 +24,16 @@ */ package org.alfresco.email.server; -import org.alfresco.i18n.I18NUtil; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.cmr.email.EmailMessageException; +import org.alfresco.service.cmr.email.EmailService; import org.alfresco.util.AbstractLifecycleBean; +import org.alfresco.util.PropertyCheck; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationEvent; import org.springframework.context.support.AbstractApplicationContext; @@ -38,61 +45,162 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; */ public abstract class EmailServer extends AbstractLifecycleBean { - protected EmailServerConfiguration configuration; + private static final String ERR_SENDER_BLOCKED = "email.server.err.sender_blocked"; + + private boolean enabled; + private String domain; + private int port; + private Set blockedSenders; + private Set allowedSenders; + + private EmailService emailService; + /** * @param serverConfiguration Server configuration */ - protected EmailServer(EmailServerConfiguration serverConfiguration) + protected EmailServer() { - this.configuration = serverConfiguration; + this.enabled = false; + this.port = 25; + this.domain = null; + this.blockedSenders = new HashSet(23); + this.allowedSenders = new HashSet(23); + } + + /** + * @param enabled Enable/disable server + */ + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + protected String getDomain() + { + return domain; + } + + public void setDomain(String domain) + { + this.domain = domain; + } + + protected int getPort() + { + return port; + } + + /** + * @param port SMTP port (25 is default) + */ + public void setPort(int port) + { + this.port = port; + } + + /** + * Set the blocked senders as a comma separated list. The entries will be trimmed of + * all whitespace. + * + * @param blockedSenders a comman separated list of blocked senders + */ + public void setBlockedSenders(String blockedSenders) + { + StringTokenizer tokenizer = new StringTokenizer(blockedSenders, ",", false); + while (tokenizer.hasMoreTokens()) + { + String sender = tokenizer.nextToken().trim(); + this.blockedSenders.add(sender); + } + } + + /** + * @param blockedSenders a list of senders that are not allowed to email in + */ + public void setBlockedSenders(List blockedSenders) + { + this.blockedSenders.addAll(blockedSenders); + } + + /** + * Set the allowed senders as a comma separated list. The entries will be trimmed of + * all whitespace. + * + * @param allowedSenders a comman separated list of blocked senders + */ + public void setAllowedSenders(String allowedSenders) + { + StringTokenizer tokenizer = new StringTokenizer(allowedSenders, ",", false); + while (tokenizer.hasMoreTokens()) + { + String sender = tokenizer.nextToken().trim(); + if (sender.length() == 0) + { + // Nothing + continue; + } + this.allowedSenders.add(sender); + } + } + + /** + * @param allowedSenders a list of senders that are allowed to email in + */ + public void setAllowedSenders(List allowedSenders) + { + this.allowedSenders.addAll(allowedSenders); + } + + /** + * @return the service interface to interact with + */ + protected EmailService getEmailService() + { + return emailService; + } + + /** + * @param emailService the service interface to interact with + */ + public void setEmailService(EmailService emailService) + { + this.emailService = emailService; } /** * Filter incoming message by its sender e-mail address. * - * @param sender An e-mail address of sender - * @throws EmailMessageException Exception will be thrown if the e-mail is rejected accordingly with blocked and allowed lists. + * @param sender An e-mail address of sender + * @throws EmailMessageException if the e-mail is rejected accordingly with blocked and allowed lists */ - public void blackAndWhiteListFiltering(String sender) + protected void filterSender(String sender) { - // At first try to find sender in the black list - String[] blackList = configuration.getArrayBlockedSenders(); - String[] whiteList = configuration.getArrayAllowedSenders(); - - // At first try to find sender in the black list - // If sender is found, mail will be rejected at once - if (blackList != null) + // Check if the sender is in the blocked list + for (String blockedSender : blockedSenders) { - for (String deniedAddress : blackList) + if (sender.matches(blockedSender)) { - if (sender.matches(deniedAddress)) - { - throw new EmailMessageException(I18NUtil.getMessage("email.server.denied-address", sender)); - } + throw new EmailMessageException(ERR_SENDER_BLOCKED, sender); } } - - // Sender wasn't found in black list or black list is empty - // Try to find sender in the white list - // If sender is found in white list, - // the message will be accepted at once. - if (whiteList != null) + + // If there are any restrictions in the allowed list, then a positive match + // is absolutely required + if (!allowedSenders.isEmpty()) { - boolean accept = false; - for (String acceptedAddress : whiteList) + boolean matched = false; + for (String allowedSender : allowedSenders) { - if (sender.matches(acceptedAddress)) + if (sender.matches(allowedSender)) { - if (log.isInfoEnabled()) - log.info("Sender with address \"" + sender + "\"matches to expression \"" + acceptedAddress + "\" in the white list. The message was accepted."); - accept = true; + matched = true; break; } } - if (!accept) + if (!matched) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.not-white-address", sender)); + throw new EmailMessageException(ERR_SENDER_BLOCKED, sender); } } } @@ -113,10 +221,19 @@ public abstract class EmailServer extends AbstractLifecycleBean @Override protected void onBootstrap(ApplicationEvent event) { - if (configuration.isEnabled()) + if (!enabled) { - startup(); + return; } + // Check properties + PropertyCheck.mandatory(this, "domain", domain); + if (port <= 0 || port > 65535) + { + throw new AlfrescoRuntimeException("Property 'port' is incorrect"); + } + PropertyCheck.mandatory(this, "emailService", emailService); + // Startup + startup(); } /** @@ -125,7 +242,7 @@ public abstract class EmailServer extends AbstractLifecycleBean @Override protected void onShutdown(ApplicationEvent event) { - if (configuration.isEnabled()) + if (enabled) { shutdown(); } diff --git a/source/java/org/alfresco/email/server/EmailServerConfiguration.java b/source/java/org/alfresco/email/server/EmailServerConfiguration.java deleted file mode 100644 index d6afa2800d..0000000000 --- a/source/java/org/alfresco/email/server/EmailServerConfiguration.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.email.server; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.email.EmailService; -import org.alfresco.util.AbstractLifecycleBean; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; - -/** - * Encapsulation of setting controlling the email server. - * @since 2.2 - */ -public class EmailServerConfiguration extends AbstractLifecycleBean -{ - private final static Log log = LogFactory.getLog(EmailServerConfiguration.class); - - private boolean enabled = false; - - private String domain; - private int port = 25; - - private String[] blockedSenders; - private String[] allowedSenders; - - private EmailService emailService; - - /** - * @return True if server is enabled. - */ - public boolean isEnabled() - { - return enabled; - } - - /** - * @param enabled Enable/disable server - */ - public void setEnabled(boolean enabled) - { - this.enabled = enabled; - } - - /** - * @return Domain - */ - public String getDomain() - { - return domain; - } - - /** - * @param domain Domain - */ - public void setDomain(String domain) - { - this.domain = domain; - } - - /** - * @return SMTP port (25 is default) - */ - public int getPort() - { - return port; - } - - /** - * @param port SMTP port (25 is default) - */ - public void setPort(int port) - { - this.port = port; - } - - /** - * @return Array of e-mail addresses. If an incoming e-mail has a sender from this list, message will be rejected. - */ - public String[] getArrayBlockedSenders() - { - return blockedSenders; - } - - /** - * @param Comma separated blackSenders of e-mail addresses. If an incoming e-mail has a sender from this list, message will be rejected. - */ - public void setBlockedSenders(String blockedSenders) - { - if (blockedSenders != null && blockedSenders.trim().length() > 0) - { - this.blockedSenders = blockedSenders.split(";"); - } - else - { - this.blockedSenders = null; - } - } - - /** - * @return Array of e-mail addresses. If an incoming e-mail has a sender from this list, message will be accepted. - */ - public String[] getArrayAllowedSenders() - { - return allowedSenders; - } - - /** - * @param Comma separated whiteSenders of e-mail addresses. If an incoming e-mail has a sender from this list, message will be accepted. - */ - public void setAllowedSenders(String allowedSenders) - { - if (allowedSenders != null && allowedSenders.trim().length() > 0) - { - this.allowedSenders = allowedSenders.split(";"); - } - else - { - this.allowedSenders = null; - } - } - - /** - * @return Email Service - */ - public EmailService getEmailService() - { - return emailService; - } - - /** - * @param emailService Email Service - */ - public void setEmailService(EmailService emailService) - { - this.emailService = emailService; - } - - /** - * Method checks that all mandatory fiedls are set. - * - * @throws AlfrescoRuntimeException Exception is thrown if at least one mandatory field isn't set. - */ - private void check() - { - if (domain == null) - { - throw new AlfrescoRuntimeException("Property 'domain' not set"); - } - if (port <= 0 || port > 65535) - { - throw new AlfrescoRuntimeException("Property 'port' is incorrect"); - } - if (emailService == null) - { - throw new AlfrescoRuntimeException("Property 'emailService' not set"); - } - if (blockedSenders == null) - { - if (log.isDebugEnabled()) - { - log.debug("Property 'blockedSenders' not set"); - } - } - if (allowedSenders == null) - { - if (log.isDebugEnabled()) - { - log.debug("Property 'allowedSenders' not set"); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void onBootstrap(ApplicationEvent event) - { - check(); - } - - /** - * {@inheritDoc} - *

- * NO-OP - */ - @Override - protected void onShutdown(ApplicationEvent event) - { - } -} diff --git a/source/java/org/alfresco/email/server/EmailServiceImpl.java b/source/java/org/alfresco/email/server/EmailServiceImpl.java index eef0ddad93..bb89e9b324 100644 --- a/source/java/org/alfresco/email/server/EmailServiceImpl.java +++ b/source/java/org/alfresco/email/server/EmailServiceImpl.java @@ -28,10 +28,12 @@ import java.util.Collection; import java.util.Map; import org.alfresco.email.server.handler.EmailMessageHandler; -import org.alfresco.i18n.I18NUtil; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.integrity.IntegrityException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.email.EmailMessage; @@ -43,28 +45,47 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; /** * Concrete email service implementation. This is responsible for routing the * emails into the server. + * * @since 2.2 */ public class EmailServiceImpl implements EmailService { - private static final Log log = LogFactory.getLog(EmailServiceImpl.class); - + private static final String ERR_INBOUND_EMAIL_DISABLED = "email.server.err.inbound_mail_disabled"; + private static final String ERR_INVALID_SUBJECT = "email.server.err.invalid_subject"; + private static final String ERR_ACCESS_DENIED = "email.server.err.access_denied"; + private static final String ERR_UNKNOWN_SOURCE_ADDRESS = "email.server.err.unknown_source_address"; + private static final String ERR_USER_NOT_EMAIL_CONTRIBUTOR = "email.server.err.user_not_email_contributor"; + private static final String ERR_NO_EMAIL_CONTRIBUTOR_GROUP = "email.server.err.no_email_contributor_group"; + private static final String ERR_INVALID_NODE_ADDRESS = "email.server.err.invalid_node_address"; + private static final String ERR_HANDLER_NOT_FOUND = "email.server.err.handler_not_found"; + + private NamespaceService namespaceService; private NodeService nodeService; private SearchService searchService; private RetryingTransactionHelper retryingTransactionHelper; - private boolean mailInboundEnabled; + private boolean emailInboundEnabled; /** Login of user that is set as unknown. */ private String unknownUser; /** List of message handlers */ private Map emailMessageHandlerMap; + /** + * + * @param namespaceService the service to resolve namespace prefixes + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + /** * @param nodeService Alfresco Node Service */ @@ -113,9 +134,9 @@ public class EmailServiceImpl implements EmailService this.unknownUser = unknownUser; } - public void setMailInboundEnabled(boolean mailInboundEnabled) + public void setEmailInboundEnabled(boolean mailInboundEnabled) { - this.mailInboundEnabled = mailInboundEnabled; + this.emailInboundEnabled = mailInboundEnabled; } /** @@ -143,87 +164,114 @@ public class EmailServiceImpl implements EmailService */ private void processMessage(final NodeRef nodeRef, final EmailMessage message) { - if (!mailInboundEnabled) + if (!emailInboundEnabled) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.mail-inbound-disabled")); + throw new EmailMessageException(ERR_INBOUND_EMAIL_DISABLED); } try { - RetryingTransactionCallback callback = new RetryingTransactionCallback() + // Get the username for the process using the system account + final RetryingTransactionCallback getUsernameCallback = new RetryingTransactionCallback() { - public Object execute() + public String execute() throws Throwable { - final String userName = authenticate(message.getFrom()); - - AuthenticationUtil.runAs(new RunAsWork() + String from = message.getFrom(); + return getUsername(from); + } + }; + RunAsWork getUsernameRunAsWork = new RunAsWork() + { + public String doWork() throws Exception + { + return retryingTransactionHelper.doInTransaction(getUsernameCallback, false); + } + }; + String username = AuthenticationUtil.runAs(getUsernameRunAsWork, AuthenticationUtil.SYSTEM_USER_NAME); + + // Process the message using the username's account + final RetryingTransactionCallback processMessageCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + String recipient = message.getTo(); + NodeRef targetNodeRef = null; + if (nodeRef == null) { - public Object doWork() throws Exception - { - String recepient = message.getTo(); - NodeRef targetNodeRef = null; - if (nodeRef == null) - { - targetNodeRef = getTargetNode(recepient); - } - else - { - targetNodeRef = nodeRef; - } - EmailMessageHandler messageHandler = getMessageHandler(targetNodeRef); - messageHandler.processMessage(targetNodeRef, message); - return null; - } - - }, userName); + targetNodeRef = getTargetNode(recipient); + } + else + { + targetNodeRef = nodeRef; + } + EmailMessageHandler messageHandler = getMessageHandler(targetNodeRef); + messageHandler.processMessage(targetNodeRef, message); return null; } }; - retryingTransactionHelper.doInTransaction(callback, false); + RunAsWork processMessageRunAsWork = new RunAsWork() + { + public Object doWork() throws Exception + { + return retryingTransactionHelper.doInTransaction(processMessageCallback, false); + } + }; + AuthenticationUtil.runAs(processMessageRunAsWork, username); + } + catch (EmailMessageException e) + { + // These are email-specific errors + throw e; + } + catch (AccessDeniedException e) + { + throw new EmailMessageException(ERR_ACCESS_DENIED, message.getFrom(), message.getTo()); + } + catch (IntegrityException e) + { + throw new EmailMessageException(ERR_INVALID_SUBJECT); } catch (Throwable e) { - log.error("Error process email message", e); - throw new EmailMessageException(e.getMessage()); + throw new AlfrescoRuntimeException("Email message processing failed", e); } } /** - * @param nodeRef Target node - * @return Handler that can process message addressed to specific node (target node). - * @throws EmailMessageException Exception is thrown if nodeRef is null or suitable message handler isn't found. + * @param nodeRef Target node + * @return Handler that can process message addressed to specific node (target node). + * @throws EmailMessageException is thrown if a suitable message handler isn't found. */ private EmailMessageHandler getMessageHandler(NodeRef nodeRef) { - if (nodeRef == null) - { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-ref")); - } - String nodeTypeLocalName = nodeService.getType(nodeRef).getLocalName(); - EmailMessageHandler handler = emailMessageHandlerMap.get(nodeTypeLocalName); + ParameterCheck.mandatory("nodeRef", nodeRef); + + QName nodeTypeQName = nodeService.getType(nodeRef); + String prefixedNodeTypeStr = nodeTypeQName.toPrefixString(namespaceService); + EmailMessageHandler handler = emailMessageHandlerMap.get(prefixedNodeTypeStr); if (handler == null) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.handler-not-found")); + throw new EmailMessageException(ERR_HANDLER_NOT_FOUND, prefixedNodeTypeStr); } return handler; } /** - * Method determines target node by recepient e-mail address. + * Method determines target node by recipient e-mail address. * - * @param recepient An e-mail address of a receipient - * @return Referance to the target node. - * @exception EmailMessageException Exception is thrown if the target node couldn't be determined by some reasons. + * @param recipient An e-mail address of a recipient + * @return Reference to the target node + * @throws EmailMessageException is thrown if the target node couldn't be determined by some reasons. */ - private NodeRef getTargetNode(String recepient) + private NodeRef getTargetNode(String recipient) { - if (recepient == null || recepient.length() == 0) + if (recipient == null || recipient.length() == 0) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-address")); + throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient); } - String[] parts = recepient.split("@"); + String[] parts = recipient.split("@"); if (parts.length != 2) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-address")); + throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient); } // Ok, address looks well, let's try to find related alias @@ -243,14 +291,14 @@ public class EmailServiceImpl implements EmailService } } - // Ok, alias wasn't found, let's try to interpret recepient address as 'node-bdid' value + // Ok, alias wasn't found, let's try to interpret recipient address as 'node-bdid' value query = "@sys\\:node-dbid:" + parts[0]; resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, query); if (resultSet.length() > 0) { return resultSet.getNodeRef(0); } - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-address")); + throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient); } /** @@ -260,7 +308,7 @@ public class EmailServiceImpl implements EmailService * @return User name * @throws EmailMessageException Exception will be thrown if authentication is failed. */ - private String authenticate(String from) + private String getUsername(String from) { String userName = null; StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); @@ -270,10 +318,13 @@ public class EmailServiceImpl implements EmailService if (resultSet.length() == 0) { - userName = unknownUser; - if (userName == null || !isEmailContributeUser(userName)) + if (unknownUser == null || unknownUser.length() == 0) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.unknown-user")); + throw new EmailMessageException(ERR_UNKNOWN_SOURCE_ADDRESS, from); + } + else + { + userName = unknownUser; } } else @@ -281,17 +332,21 @@ public class EmailServiceImpl implements EmailService NodeRef userNode = resultSet.getNodeRef(0); if (nodeService.exists(userNode)) { - userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(userNode, ContentModel.PROP_USERNAME)); - if (!isEmailContributeUser(userName)) - { - throw new EmailMessageException(I18NUtil.getMessage("email.server.not-contribute-user", userName)); - } + userName = DefaultTypeConverter.INSTANCE.convert( + String.class, + nodeService.getProperty(userNode, ContentModel.PROP_USERNAME)); } else { - throw new EmailMessageException(I18NUtil.getMessage("email.server.contribute-group-not-exist")); + // The Lucene index returned a dead result + throw new EmailMessageException(ERR_UNKNOWN_SOURCE_ADDRESS, from); } } + // Ensure that the user is part of the Email Contributors group + if (userName == null || !isEmailContributeUser(userName)) + { + throw new EmailMessageException(ERR_USER_NOT_EMAIL_CONTRIBUTOR, userName); + } return userName; } @@ -310,16 +365,22 @@ public class EmailServiceImpl implements EmailService if (resultSet.length() == 0) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.contribute-group-not-exist")); + throw new EmailMessageException(ERR_NO_EMAIL_CONTRIBUTOR_GROUP); } NodeRef groupNode = resultSet.getNodeRef(0); - Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService.getProperty(groupNode, ContentModel.PROP_MEMBERS)); + Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection( + String.class, + nodeService.getProperty(groupNode, ContentModel.PROP_MEMBERS)); if (memberCollection.contains(userName)) + { return true; - - return false; + } + else + { + return false; + } } } diff --git a/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java index 2634f7a004..d68d35c033 100644 --- a/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java @@ -24,26 +24,28 @@ */ package org.alfresco.email.server.handler; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Serializable; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import org.alfresco.email.server.EmailServerModel; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.email.EmailMessage; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -60,17 +62,16 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler { private static final Log log = LogFactory.getLog(EmailMessageHandler.class); - private AuthenticationService authenticationService; - private AuthenticationComponent authenticationComponent; + private DictionaryService dictionaryService; private NodeService nodeService; - private PersonService personService; private SearchService searchService; private ContentService contentService; + private MimetypeService mimetypeService; /** * @return Alfresco Content Service. */ - public ContentService getContentService() + protected ContentService getContentService() { return contentService; } @@ -84,41 +85,25 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler } /** - * @return Alfresco Authentication Component. + * @return the Alfresco dictionary service */ - public AuthenticationComponent getAuthenticationComponent() + protected DictionaryService getDictionaryService() { - return authenticationComponent; + return dictionaryService; } /** - * @param authenticationComponent Alfresco Authentication Component. + * @param dictionaryService Alfresco dictionary service */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + public void setDictionaryService(DictionaryService dictionaryService) { - this.authenticationComponent = authenticationComponent; - } - - /** - * @return Alfresco Authentication Service. - */ - public AuthenticationService getAuthenticationService() - { - return authenticationService; - } - - /** - * @param authenticationService Alfresco Authentication Service. - */ - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; + this.dictionaryService = dictionaryService; } /** * @return Alfresco Node Service. */ - public NodeService getNodeService() + protected NodeService getNodeService() { return nodeService; } @@ -131,39 +116,30 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler this.nodeService = nodeService; } - /** - * @return Alfesco Person Service. - */ - public PersonService getPersonService() - { - return personService; - } - - /** - * @param personService Alfresco Person Service. - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * @return Alfresco Search Service. - */ - public SearchService getSearchService() - { - return searchService; - } - /** * @param searchService Alfresco Search Service. */ - public void setSearchService(SearchService searchService) { this.searchService = searchService; } + /** + * @return the service used to determine mimeypte and encoding + */ + protected MimetypeService getMimetypeService() + { + return mimetypeService; + } + + /** + * @param mimetypeService the the service to determine mimetype and encoding + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + /** * @param to Email address which user part specifies node-dbid * @return Referance to requested node. @@ -206,12 +182,12 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler * * @param nodeRef Target node. * @param content Text for writting. - * @param contentType MIME content type. For exaple you can set this parameter to "text/html" or "text/xml", etc. + * @param mimetype MIME content type. For exaple you can set this parameter to "text/html" or "text/xml", etc. */ - protected void writeContent(NodeRef nodeRef, String content, String contentType) + protected void writeContent(NodeRef nodeRef, String content, String mimetype) { - InputStream inputStream = new ByteArrayInputStream(content.getBytes()); - writeContent(nodeRef, inputStream, contentType, "UTF-8"); + InputStream inputStream = new ByteArrayInputStream(content.getBytes(Charset.forName("UTF-8"))); + writeContent(nodeRef, inputStream, mimetype, "UTF-8"); } /** @@ -219,20 +195,35 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler * * @param nodeRef Target node. * @param content Content stream. - * @param contentType MIME content type. + * @param mimetype MIME content type. * @param encoding Encoding. Can be null for non text based content. */ - protected void writeContent(NodeRef nodeRef, InputStream content, String contentType, String encoding) + protected void writeContent(NodeRef nodeRef, InputStream content, String mimetype, String encoding) { + InputStream bis = new BufferedInputStream(content, 4092); + + // Guess the encoding if it is text + if (mimetypeService.isText(mimetype)) + { + ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder(); + encoding = charsetFinder.getCharset(bis, mimetype).name(); + } + else if (encoding == null) + { + encoding = "UTF-8"; + } + if (log.isDebugEnabled()) { - log.debug("Write content (MimeType=\"" + contentType + "\", Encoding=\"" + encoding + "\""); + log.debug("Write content (MimeType=\"" + mimetype + "\", Encoding=\"" + encoding + "\""); } + + ContentService contentService = getContentService(); ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - writer.setMimetype(contentType); + writer.setMimetype(mimetype); writer.setEncoding(encoding); - writer.putContent(content); + writer.putContent(bis); } /** diff --git a/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java index a9a37b3900..61dbc65dae 100644 --- a/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java @@ -41,6 +41,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyMap; /** * Abstact class implements common logic for forum processing email mesages. @@ -62,12 +63,20 @@ public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMess Date now = new Date(); String nodeName = "posted-" + new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss").format(now) + ".html"; - Map properties = new HashMap(1); + PropertyMap properties = new PropertyMap(3); properties.put(ContentModel.PROP_NAME, nodeName); - ChildAssociationRef childAssoc = nodeService.createNode(nodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nodeName), - ForumModel.TYPE_POST, properties); - NodeRef postNode = childAssoc.getChildRef(); + NodeRef postNode = nodeService.getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, nodeName); + if (postNode == null) + { + ChildAssociationRef childAssoc = nodeService.createNode( + nodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nodeName), + ForumModel.TYPE_POST, + properties); + postNode = childAssoc.getChildRef(); + } // Add necessary aspects properties.clear(); @@ -116,26 +125,34 @@ public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMess /** * Adds topic node into Alfresco repository * - * @param parentNode Parent node - * @param name Topic name - * @return Reference to created node + * @param parentNode Parent node + * @param name Topic name + * @return Reference to created node */ protected NodeRef addTopicNode(NodeRef parentNode, String name) { - + NodeService nodeService = getNodeService(); Map properties = new HashMap(1); properties.put(ContentModel.PROP_NAME, name); - ChildAssociationRef association = getNodeService().createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), - ForumModel.TYPE_TOPIC, properties); - NodeRef topic = association.getChildRef(); + NodeRef topicNode = nodeService.getChildByName(parentNode, ContentModel.ASSOC_CONTAINS, name); + if (topicNode == null) + { + ChildAssociationRef association = nodeService.createNode( + parentNode, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + ForumModel.TYPE_TOPIC, + properties); + topicNode = association.getChildRef(); + } // Add necessary aspects properties.clear(); properties.put(ApplicationModel.PROP_ICON, "topic"); - getNodeService().addAspect(topic, ApplicationModel.ASPECT_UIFACETS, properties); + getNodeService().addAspect(topicNode, ApplicationModel.ASPECT_UIFACETS, properties); - return topic; + return topicNode; } } diff --git a/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java index 984f0139f6..1d6a6d4269 100644 --- a/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java @@ -30,12 +30,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.alfresco.i18n.I18NUtil; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.email.EmailMessage; -import org.alfresco.service.cmr.email.EmailMessageException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -64,9 +64,10 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis(); } - QName contentType = getNodeService().getType(nodeRef); + QName nodeTypeQName = getNodeService().getType(nodeRef); - if (contentType.equals(ContentModel.TYPE_CONTENT)) + DictionaryService dictionaryService = getDictionaryService(); + if (dictionaryService.isSubClass(nodeTypeQName, ContentModel.TYPE_CONTENT)) { NodeRef forumNode = getForumNode(nodeRef); @@ -87,7 +88,9 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle } else { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-type")); + throw new AlfrescoRuntimeException("\n" + + "Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" + + "Check the message handler mappings."); } } @@ -99,7 +102,7 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle */ private NodeRef addForumNode(NodeRef nodeRef) { - NodeService nodeService=getNodeService(); + NodeService nodeService = getNodeService(); //Add discussable aspect to content node if (!nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE)) { diff --git a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java index 0c4950b63a..0153d4dd94 100644 --- a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java @@ -25,19 +25,25 @@ package org.alfresco.email.server.handler; import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.mail.MessagingException; import org.alfresco.email.server.EmailServerModel; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.email.EmailMessage; import org.alfresco.service.cmr.email.EmailMessageException; import org.alfresco.service.cmr.email.EmailMessagePart; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.NamespaceService; @@ -53,6 +59,10 @@ import org.apache.commons.logging.LogFactory; */ public class FolderEmailMessageHandler extends AbstractEmailMessageHandler { + private static final String MSG_RECEIVED_BY_SMTP = "email.server.msg.received_by_smtp"; + private static final String MSG_DEFAULT_SUBJECT = "email.server.msg.default_subject"; + private static final String ERR_MAIL_READ_ERROR = "email.server.err.mail_read_error"; + private static final Log log = LogFactory.getLog(FolderEmailMessageHandler.class); /** @@ -67,90 +77,82 @@ public class FolderEmailMessageHandler extends AbstractEmailMessageHandler try { // Check type of the node. It must be a SPACE - QName nodeTypeName = getNodeService().getType(nodeRef); + QName nodeTypeQName = getNodeService().getType(nodeRef); - if (nodeTypeName.equals(ContentModel.TYPE_FOLDER)) + if (getDictionaryService().isSubClass(nodeTypeQName, ContentModel.TYPE_FOLDER)) { // Add the content into the system - addAlfrescoContent(nodeRef, message, null); + addAlfrescoContent(nodeRef, message); } else { - if (log.isDebugEnabled()) - { - log.debug("Addressed node type isn't a folder. Message has been passed without any actions."); - } + throw new AlfrescoRuntimeException("\n" + + "Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" + + "Check the message handler mappings."); } } catch (IOException ex) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.content-error"), ex); + throw new EmailMessageException(ERR_MAIL_READ_ERROR, ex.getMessage()); } } /** * Add content to Alfresco repository * - * @param spaceNodeRef Addressed node - * @param mailParser Mail message - * @param nameConflictResolver String that can be used as part of name for resolving name conflict. - * @throws IOException Exception can be thrown while saving a content into Alfresco repository. - * @throws MessagingException Exception can be thrown while parsing e-mail message. + * @param spaceNodeRef Addressed node + * @param mailParser Mail message + * @throws IOException Exception can be thrown while saving a content into Alfresco repository. + * @throws MessagingException Exception can be thrown while parsing e-mail message. */ - public void addAlfrescoContent(NodeRef spaceNodeRef, EmailMessage message, String nameConflictResolver) throws IOException + public void addAlfrescoContent(NodeRef spaceNodeRef, EmailMessage message) throws IOException { // Set default values for email fields - if (nameConflictResolver == null) - nameConflictResolver = ""; - String messageSubject = "EMPTY_SUBJECT_" + nameConflictResolver; - if (message.getSubject().length() != 0) + String messageSubject = message.getSubject(); + if (messageSubject == null || messageSubject.length() == 0) { - messageSubject = message.getSubject() + nameConflictResolver; + Date now = new Date(); + messageSubject = I18NUtil.getMessage(MSG_DEFAULT_SUBJECT, new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss").format(now)); } - // Create node - if (log.isDebugEnabled()) - { - log.debug("Adding main content node ..."); - } + // Create main content node NodeRef contentNodeRef; contentNodeRef = addContentNode(getNodeService(), spaceNodeRef, messageSubject); - // Add titled aspect - addTitledAspect(contentNodeRef, messageSubject); - + addTitledAspect(contentNodeRef, messageSubject, message.getFrom()); // Add emailed aspect addEmailedAspect(contentNodeRef, message); - // Write the message content - if (message.getBody() != null) - writeContent(contentNodeRef, message.getBody().getContent(), message.getBody().getContentType(), message.getBody().getEncoding()); - else - writeContent(contentNodeRef, ""); - if (log.isDebugEnabled()) { - log.debug("Main content node has been added."); + InputStream contentIs = message.getBody().getContent(); + // The message body is plain text, unless an extension has been provided + MimetypeService mimetypeService = getMimetypeService(); + String mimetype = mimetypeService.guessMimetype(messageSubject); + if (mimetype.equals(MimetypeMap.MIMETYPE_BINARY)) + { + mimetype= MimetypeMap.MIMETYPE_TEXT_PLAIN; + } + // Use the default encoding. It will get overridden if the body is text. + String encoding = message.getBody().getEncoding(); + + writeContent(contentNodeRef, contentIs, mimetype, encoding); } // Add attachments EmailMessagePart[] attachments = message.getAttachments(); for (EmailMessagePart attachment : attachments) { - NodeRef attachmentNode; String fileName = attachment.getFileName(); - // Add name conflict resolver if necessary - if (nameConflictResolver.length() != 0) - { - if (fileName.lastIndexOf('.') != -1) - fileName = fileName.substring(0, fileName.lastIndexOf('.')) + " (" + nameConflictResolver + ")" + fileName.substring(fileName.lastIndexOf('.')); - else - fileName += " (" + nameConflictResolver + ")"; - } + InputStream contentIs = attachment.getContent(); + + MimetypeService mimetypeService = getMimetypeService(); + String mimetype = mimetypeService.guessMimetype(fileName); + String encoding = attachment.getEncoding(); - attachmentNode = addAttachment(getNodeService(), spaceNodeRef, contentNodeRef, fileName); - writeContent(attachmentNode, attachment.getContent(), attachment.getContentType(), attachment.getEncoding()); + NodeRef attachmentNode = addAttachment(getNodeService(), spaceNodeRef, contentNodeRef, fileName); + writeContent(attachmentNode, contentIs, mimetype, encoding); } } @@ -178,11 +180,25 @@ public class FolderEmailMessageHandler extends AbstractEmailMessageHandler */ private NodeRef addContentNode(NodeService nodeService, NodeRef parent, String name, QName assocType) { - Map contentProps = new HashMap(); - contentProps.put(ContentModel.PROP_NAME, name); - ChildAssociationRef associationRef = nodeService.createNode(parent, assocType, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, name), ContentModel.TYPE_CONTENT, - contentProps); - return associationRef.getChildRef(); + NodeRef childNodeRef = nodeService.getChildByName(parent, assocType, name); + if (childNodeRef != null) + { + // The node is present already. Make sure the name csae is correct + nodeService.setProperty(childNodeRef, ContentModel.PROP_NAME, name); + } + else + { + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, name); + ChildAssociationRef associationRef = nodeService.createNode( + parent, + assocType, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + ContentModel.TYPE_CONTENT, + contentProps); + childNodeRef = associationRef.getChildRef(); + } + return childNodeRef; } /** @@ -203,9 +219,15 @@ public class FolderEmailMessageHandler extends AbstractEmailMessageHandler NodeRef attachmentNode = addContentNode(nodeService, folder, fileName); + // Remove 'attached' aspect so that we work with the document in its clean form + if (nodeService.hasAspect(attachmentNode, EmailServerModel.ASPECT_ATTACHED)) + { + nodeService.removeAspect(attachmentNode, EmailServerModel.ASPECT_ATTACHED); + } + // Add attached aspect - Map attachedProps = new HashMap(); - nodeService.addAspect(attachmentNode, EmailServerModel.ASPECT_ATTACHED, attachedProps); + nodeService.addAspect(attachmentNode, EmailServerModel.ASPECT_ATTACHED, null); + // Recreate the association nodeService.createAssociation(attachmentNode, mainContentNode, EmailServerModel.ASSOC_ATTACHMENT); if (log.isDebugEnabled()) @@ -221,11 +243,11 @@ public class FolderEmailMessageHandler extends AbstractEmailMessageHandler * @param nodeRef Target node. * @param title Title */ - private void addTitledAspect(NodeRef nodeRef, String title) + private void addTitledAspect(NodeRef nodeRef, String title, String from) { Map titledProps = new HashMap(); titledProps.put(ContentModel.PROP_TITLE, title); - titledProps.put(ContentModel.PROP_DESCRIPTION, "Received by SMTP"); + titledProps.put(ContentModel.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_RECEIVED_BY_SMTP, from)); getNodeService().addAspect(nodeRef, ContentModel.ASPECT_TITLED, titledProps); if (log.isDebugEnabled()) diff --git a/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java index 1f75c1075a..199067e82a 100644 --- a/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java @@ -24,10 +24,9 @@ */ package org.alfresco.email.server.handler; -import org.alfresco.i18n.I18NUtil; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ForumModel; import org.alfresco.service.cmr.email.EmailMessage; -import org.alfresco.service.cmr.email.EmailMessageException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -55,9 +54,9 @@ public class ForumEmailMessageHandler extends AbstractForumEmailMessageHandler messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis(); } - QName nodeType = getNodeService().getType(nodeRef); + QName nodeTypeQName = getNodeService().getType(nodeRef); - if (nodeType.equals(ForumModel.TYPE_FORUM)) + if (getDictionaryService().isSubClass(nodeTypeQName, ForumModel.TYPE_FORUM)) { NodeRef topicNode = getTopicNode(nodeRef, messageSubject); @@ -69,7 +68,9 @@ public class ForumEmailMessageHandler extends AbstractForumEmailMessageHandler } else { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-type")); + throw new AlfrescoRuntimeException("\n" + + "Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" + + "Check the message handler mappings."); } } } diff --git a/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java index 357d476875..f3d1398be2 100644 --- a/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java @@ -24,10 +24,9 @@ */ package org.alfresco.email.server.handler; -import org.alfresco.i18n.I18NUtil; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ForumModel; import org.alfresco.service.cmr.email.EmailMessage; -import org.alfresco.service.cmr.email.EmailMessageException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -44,20 +43,26 @@ public class TopicEmailMessageHandler extends AbstractForumEmailMessageHandler */ public void processMessage(NodeRef nodeRef, EmailMessage message) { - QName nodeType = getNodeService().getType(nodeRef); + QName nodeTypeQName = getNodeService().getType(nodeRef); NodeRef topicNode = null; - if (nodeType.equals(ForumModel.TYPE_TOPIC)) + if (getDictionaryService().isSubClass(nodeTypeQName, ForumModel.TYPE_TOPIC)) { topicNode = nodeRef; } - else if (nodeType.equals(ForumModel.TYPE_POST)) + else if (getDictionaryService().isSubClass(nodeTypeQName, ForumModel.TYPE_POST)) { - topicNode = getNodeService().getPrimaryParent(nodeRef).getChildRef(); + topicNode = getNodeService().getPrimaryParent(nodeRef).getParentRef(); + if (topicNode == null) + { + throw new AlfrescoRuntimeException("A POST node has no primary parent: " + nodeRef); + } } - if (topicNode == null) + else { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-ref")); + throw new AlfrescoRuntimeException("\n" + + "Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" + + "Check the message handler mappings."); } addPostNode(topicNode, message); } diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java index 4c2e1db75f..3c1862441b 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java @@ -39,7 +39,6 @@ import javax.mail.Session; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeUtility; -import org.alfresco.i18n.I18NUtil; import org.alfresco.service.cmr.email.EmailMessage; import org.alfresco.service.cmr.email.EmailMessageException; import org.alfresco.service.cmr.email.EmailMessagePart; @@ -53,6 +52,15 @@ import org.apache.commons.logging.LogFactory; */ public class SubethaEmailMessage implements EmailMessage { + private static final String ERR_FAILED_TO_CREATE_MIME_MESSAGE = "email.server.err.failed_to_create_mime_message"; + private static final String ERR_EXTRACTING_FROM_ADDRESS = "email.server.err.extracting_from_address"; + private static final String ERR_NO_FROM_ADDRESS = "email.server.err.no_from_address"; + private static final String ERR_EXTRACTING_TO_ADDRESS = "email.server.err.extracting_to_address"; + private static final String ERR_NO_TO_ADDRESS = "email.server.err.no_to_address"; + private static final String ERR_EXTRACTING_SUBJECT = "email.server.err.extracting_subject"; + private static final String ERR_EXTRACTING_SENT_DATE = "email.server.err.extracting_sent_date"; + private static final String ERR_PARSE_MESSAGE = "email.server.err.parse_message"; + private static final long serialVersionUID = -3735187524926395261L; private static final Log log = LogFactory.getLog(SubethaEmailMessage.class); @@ -98,7 +106,7 @@ public class SubethaEmailMessage implements EmailMessage } catch (MessagingException e) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.error-creating-message"), e); + throw new EmailMessageException(ERR_FAILED_TO_CREATE_MIME_MESSAGE, e.getMessage()); } processMimeMessage(mimeMessage); @@ -115,11 +123,11 @@ public class SubethaEmailMessage implements EmailMessage } catch (MessagingException e) { - throw new EmailMessageException("Error extract from.", e); + throw new EmailMessageException(ERR_EXTRACTING_FROM_ADDRESS, e.getMessage()); } if (addresses == null || addresses.length == 0) { - throw new EmailMessageException("There is no one from."); + throw new EmailMessageException(ERR_NO_FROM_ADDRESS); } from = addresses[0].toString(); } @@ -133,11 +141,11 @@ public class SubethaEmailMessage implements EmailMessage } catch (MessagingException e) { - throw new EmailMessageException("Error extract recepient.", e); + throw new EmailMessageException(ERR_EXTRACTING_TO_ADDRESS, e.getMessage()); } if (addresses == null || addresses.length == 0) { - throw new EmailMessageException("There is no one recepient."); + throw new EmailMessageException(ERR_NO_TO_ADDRESS); } to = addresses[0].toString(); } @@ -148,7 +156,7 @@ public class SubethaEmailMessage implements EmailMessage } catch (MessagingException e) { - throw new EmailMessageException("Error extract subject.", e); + throw new EmailMessageException(ERR_EXTRACTING_SUBJECT, e.getMessage()); } if (subject == null) { @@ -161,7 +169,7 @@ public class SubethaEmailMessage implements EmailMessage } catch (MessagingException e) { - throw new EmailMessageException("Error extract sentDate.", e); + throw new EmailMessageException(ERR_EXTRACTING_SENT_DATE, e.getMessage()); } if (sentDate == null) { @@ -261,11 +269,11 @@ public class SubethaEmailMessage implements EmailMessage } catch (IOException e) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.error-parse-message"), e); + throw new EmailMessageException(ERR_PARSE_MESSAGE, e.getMessage()); } catch (MessagingException e) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.error-parse-message"), e); + throw new EmailMessageException(ERR_PARSE_MESSAGE, e.getMessage()); } } diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java index a4d63d2a0c..4572ea2698 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java @@ -35,9 +35,9 @@ import java.util.regex.Pattern; import javax.mail.MessagingException; import javax.mail.Part; -import org.alfresco.i18n.I18NUtil; import org.alfresco.service.cmr.email.EmailMessageException; import org.alfresco.service.cmr.email.EmailMessagePart; +import org.alfresco.util.ParameterCheck; import org.alfresco.util.remote.RemotableInputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -47,6 +47,10 @@ import org.apache.commons.logging.LogFactory; */ public class SubethaEmailMessagePart implements EmailMessagePart { + private static final String ERR_UNSUPPORTED_ENCODING = "email.server.err.usupported_encoding"; + private static final String ERR_FAILED_TO_READ_CONTENT_STREAM = "email.server.err.failed_to_read_content_stream"; + private static final String ERR_INCORRECT_MESSAGE_PART = "email.server.err.incorrect_message_part"; + private static final long serialVersionUID = -8530238872199733096L; static final Log log = LogFactory.getLog(SubethaEmailMessagePart.class); @@ -74,8 +78,7 @@ public class SubethaEmailMessagePart implements EmailMessagePart */ public SubethaEmailMessagePart(Part messagePart) { - if (messagePart == null) - throw new IllegalArgumentException("messagePart"); + ParameterCheck.mandatory("messagePart", messagePart); try { @@ -89,7 +92,7 @@ public class SubethaEmailMessagePart implements EmailMessagePart encoding = matcher.group(1); if (!Charset.isSupported(encoding)) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.usupported-encoding", encoding)); + throw new EmailMessageException(ERR_UNSUPPORTED_ENCODING, encoding); } } @@ -99,12 +102,12 @@ public class SubethaEmailMessagePart implements EmailMessagePart } catch (Exception ex) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.error-getting-content-stream"), ex); + throw new EmailMessageException(ERR_FAILED_TO_READ_CONTENT_STREAM, ex.getMessage()); } } catch (MessagingException e) { - throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-message-part"), e); + throw new EmailMessageException(ERR_INCORRECT_MESSAGE_PART, e.getMessage()); } } diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java index 660d502a9b..0201a5f617 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java @@ -31,7 +31,6 @@ import java.util.LinkedList; import java.util.List; import org.alfresco.email.server.EmailServer; -import org.alfresco.email.server.EmailServerConfiguration; import org.alfresco.service.cmr.email.EmailMessage; import org.alfresco.service.cmr.email.EmailMessageException; import org.apache.commons.logging.Log; @@ -53,17 +52,17 @@ public class SubethaEmailServer extends EmailServer private SMTPServer serverImpl; - protected SubethaEmailServer(EmailServerConfiguration serverConfiguration) + protected SubethaEmailServer() { - super(serverConfiguration); - serverImpl = new SMTPServer(new HandlerFactory()); - serverImpl.setPort(serverConfiguration.getPort()); - serverImpl.setHostName(serverConfiguration.getDomain()); + super(); } @Override public void startup() { + serverImpl = new SMTPServer(new HandlerFactory()); + serverImpl.setPort(getPort()); + serverImpl.setHostName(getDomain()); serverImpl.start(); log.info("Email Server has started successfully"); } @@ -112,7 +111,7 @@ public class SubethaEmailServer extends EmailServer this.from = from; try { - blackAndWhiteListFiltering(from); + filterSender(from); } catch (EmailMessageException e) { @@ -168,12 +167,17 @@ public class SubethaEmailServer extends EmailServer try { emailMessage = new SubethaEmailMessage(from, delivery.getRecipient(), data); - configuration.getEmailService().importMessage(emailMessage); + getEmailService().importMessage(emailMessage); } catch (EmailMessageException e) { throw new RejectException(554, e.getMessage()); } + catch (Throwable e) + { + log.error(e.getMessage(), e); + throw new RejectException(554, "An internal error prevented mail delivery."); + } } public List getAuthenticationMechanisms() diff --git a/source/java/org/alfresco/service/cmr/email/EmailMessageException.java b/source/java/org/alfresco/service/cmr/email/EmailMessageException.java index 6b51515b28..36dd0de231 100644 --- a/source/java/org/alfresco/service/cmr/email/EmailMessageException.java +++ b/source/java/org/alfresco/service/cmr/email/EmailMessageException.java @@ -24,7 +24,12 @@ */ package org.alfresco.service.cmr.email; +import org.alfresco.i18n.I18NUtil; + /** + * A checked and handled exception indicating a specific and well-known + * email error condition. + * * @since 2.2 */ public class EmailMessageException extends RuntimeException @@ -32,36 +37,13 @@ public class EmailMessageException extends RuntimeException private static final long serialVersionUID = 5039365329619219256L; /** - * Empty contructor + * @param message exception message. + * @param params message arguments for I18N + * + * @see I18NUtil#getMessage(String, Object[]) */ - public EmailMessageException() + public EmailMessageException(String message, Object ... params) { - - super(); - } - - /** - * @param message exception message. - * @param cause throwable object. - */ - public EmailMessageException(String message, Throwable cause) - { - super(message, cause); - } - - /** - * @param message exception message. - */ - public EmailMessageException(String message) - { - super(message); - } - - /** - * @param cause throwable object - */ - public EmailMessageException(Throwable cause) - { - super(cause); + super(I18NUtil.getMessage(message, params)); } }