diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index efddc1ba9d..47fd76a876 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -19,6 +19,7 @@ + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 253d9b89a8..0da6b3f999 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -429,5 +429,14 @@ + + + diff --git a/config/alfresco/bootstrap/emailServer.xml b/config/alfresco/bootstrap/emailServer.xml new file mode 100644 index 0000000000..4c3a6ed71a --- /dev/null +++ b/config/alfresco/bootstrap/emailServer.xml @@ -0,0 +1,29 @@ + + + + + + + + + user + alfrescoUserStore + + + admin + + + + GROUP_EMAIL_CONTRIBUTORS + GROUP_EMAIL_CONTRIBUTORS + GROUP_EMAIL_CONTRIBUTORS + + + + + \ No newline at end of file diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 5ef697b7fc..7055916a0d 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -685,6 +685,9 @@ org/alfresco/repo/rule/ruleModel.xml org/alfresco/repo/version/version_model.xml + + alfresco/model/emailServerModel.xml + alfresco/model/deprecated/deprecated_contentModel.xml diff --git a/config/alfresco/email-server.properties b/config/alfresco/email-server.properties new file mode 100644 index 0000000000..40c6ac6a8b --- /dev/null +++ b/config/alfresco/email-server.properties @@ -0,0 +1,6 @@ +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/email-service-context.xml b/config/alfresco/email-service-context.xml new file mode 100644 index 0000000000..5964368508 --- /dev/null +++ b/config/alfresco/email-service-context.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + alfresco.messages.email-service + + + + + + + + true + + + + classpath:alfresco/email-server.properties + + + + + + + + ${email.server.domain} + + + + ${email.server.port} + + + + ${email.server.enabled} + + + + ${email.server.blocked.senders} + + + + ${email.server.allowed.senders} + + + + + + + + + + + + + org.alfresco.service.cmr.email.EmailService + + + emailService + + + ${avm.remote.port} + + + + + + + ${mail.inbound.enabled} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/extension/custom-email-service-context.xml.sample b/config/alfresco/extension/custom-email-service-context.xml.sample new file mode 100644 index 0000000000..38c293c68f --- /dev/null +++ b/config/alfresco/extension/custom-email-service-context.xml.sample @@ -0,0 +1,26 @@ + + + + + + + + + true + + + + classpath:alfresco/email-server.properties + + + classpath:alfresco/extension/custom-email-server.properties + + + + + diff --git a/config/alfresco/messages/email-service.properties b/config/alfresco/messages/email-service.properties new file mode 100644 index 0000000000..ab7f00eaa4 --- /dev/null +++ b/config/alfresco/messages/email-service.properties @@ -0,0 +1,17 @@ +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 diff --git a/config/alfresco/model/emailServerModel.xml b/config/alfresco/model/emailServerModel.xml new file mode 100644 index 0000000000..721f67ba4e --- /dev/null +++ b/config/alfresco/model/emailServerModel.xml @@ -0,0 +1,65 @@ + + + + + + Emailserver Model + UTL + 1.0 + + + + + + + + + + + + + + + + + + + Attached + + + Attachment + + true + true + + + cm:content + false + false + + + + + + + Emailed + cm:emailed + + + + Aliasable + + + Alias + d:text + true + + + + + + \ No newline at end of file diff --git a/config/alfresco/remote-email-service-test-context.xml b/config/alfresco/remote-email-service-test-context.xml new file mode 100644 index 0000000000..7a01971001 --- /dev/null +++ b/config/alfresco/remote-email-service-test-context.xml @@ -0,0 +1,28 @@ + + + + + + + + true + + + + classpath:alfresco/remote-email-service-test.properties + + + + + + + + ${email.service.rmi.registry.host} + + + ${email.service.rmi.registry.port} + + + + diff --git a/source/java/org/alfresco/email/server/AliasableAspect.java b/source/java/org/alfresco/email/server/AliasableAspect.java new file mode 100644 index 0000000000..a2feb26b34 --- /dev/null +++ b/source/java/org/alfresco/email/server/AliasableAspect.java @@ -0,0 +1,147 @@ +/* + * 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 java.io.Serializable; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.policy.Behaviour.NotificationFrequency; +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.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Class that supports functionality of aliasable aspect. + * + * @author YanO + * @since 2.2 + */ +public class AliasableAspect implements NodeServicePolicies.OnAddAspectPolicy, NodeServicePolicies.OnUpdatePropertiesPolicy +{ + private PolicyComponent policyComponent; + + private NodeService nodeService; + + private SearchService searchService; + + public static final String SEARCH_TEMPLATE = "ASPECT:\"" + EmailServerModel.ASPECT_ALIASABLE + "\" +@" + NamespaceService.EMAILSERVER_MODEL_PREFIX + "\\:" + + EmailServerModel.PROP_ALIAS.getLocalName() + ":\"%s\""; + + /** + * @param searchService Alfresco Search Service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param nodeService Alfresco Node Service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param policyComponent Alfresco Policy Component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Spring initilaise method used to register the policy behaviours + */ + public void initialise() + { + // Register the policy behaviours + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), EmailServerModel.ASPECT_ALIASABLE, new JavaBehaviour(this, + "onAddAspect", NotificationFrequency.FIRST_EVENT)); + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), EmailServerModel.ASPECT_ALIASABLE, new JavaBehaviour(this, + "onUpdateProperties")); + } + + /** + * Check that alias property isn't duplicated. If the rule is broken, AlfrescoRuntimeException will be thrown. + * + * @param nodeRef Reference to target node + * @param alias Alias that we want to set to the targen node + * @exception AlfrescoRuntimeException Throws if the alias property is duplicated. + */ + private void checkAlias(NodeRef nodeRef, String alias) + { + // Try to find duplication in the system + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + // Create search string like this: ASPECT:"emailserver:aliasable" +@emailserver\:alias:"alias_string" + String query = String.format(SEARCH_TEMPLATE, alias); + ResultSet res = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, query); + for (int i = 0; i < res.length(); i++) + { + NodeRef resRef = res.getNodeRef(i); + Object otherAlias = nodeService.getProperty(resRef, EmailServerModel.PROP_ALIAS); + if (!resRef.equals(nodeRef) && alias.equals(otherAlias)) + { + throw new AlfrescoRuntimeException("Node with alias=\"" + alias + "\" already exists. Duplicate isn't allowed."); + } + } + } + + /** + * @see org.alfresco.repo.node.NodeServicePolicies$OnAddAspectPolicy#onAddAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) + * @exception AlfrescoRuntimeException Throws if the alias property is duplicated. + */ + public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) + { + Object alias = nodeService.getProperty(nodeRef, EmailServerModel.PROP_ALIAS); + if (alias != null) + { + checkAlias(nodeRef, alias.toString()); + } + } + + /** + * @see org.alfresco.repo.node.NodeServicePolicies$OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) + * @exception AlfrescoRuntimeException Throws if the alias property is duplicated. + */ + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + { + Serializable alias = after.get(EmailServerModel.PROP_ALIAS); + if (alias != null) + { + checkAlias(nodeRef, alias.toString()); + } + + } +} diff --git a/source/java/org/alfresco/email/server/EmailServer.java b/source/java/org/alfresco/email/server/EmailServer.java new file mode 100644 index 0000000000..60ec4767f0 --- /dev/null +++ b/source/java/org/alfresco/email/server/EmailServer.java @@ -0,0 +1,198 @@ +/* + * 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.i18n.I18NUtil; +import org.alfresco.service.cmr.email.EmailMessageException; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Base implementation of an email server. + * @since 2.2 + */ +public abstract class EmailServer extends AbstractLifecycleBean +{ + protected EmailServerConfiguration configuration; + + /** + * @param serverConfiguration Server configuration + */ + protected EmailServer(EmailServerConfiguration serverConfiguration) + { + this.configuration = serverConfiguration; + } + + /** + * 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. + */ + public void blackAndWhiteListFiltering(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) + { + for (String deniedAddress : blackList) + { + if (sender.matches(deniedAddress)) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.denied-address", 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) + { + boolean accept = false; + for (String acceptedAddress : whiteList) + { + if (sender.matches(acceptedAddress)) + { + if (log.isInfoEnabled()) + log.info("Sender with address \"" + sender + "\"matches to expression \"" + acceptedAddress + "\" in the white list. The message was accepted."); + accept = true; + break; + } + } + if (!accept) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.not-white-address", sender)); + } + } + } + + /** + * Method is called when server is starting up. + */ + public abstract void startup(); + + /** + * Method is called when server is shutting down. + */ + public abstract void shutdown(); + + /** + * {@inheritDoc} + */ + @Override + protected void onBootstrap(ApplicationEvent event) + { + if (configuration.isEnabled()) + { + startup(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void onShutdown(ApplicationEvent event) + { + if (configuration.isEnabled()) + { + shutdown(); + } + } + + private static volatile Boolean stop = false; + + public static void main(String[] args) + { + if (args.length == 0) + { + usage(); + return; + } + AbstractApplicationContext context = null; + try + { + context = new ClassPathXmlApplicationContext(args); + } catch (BeansException e) + { + System.err.println("Erro create context: " + e); + usage(); + return; + } + + try + { + if (!context.containsBean("emailServer")) + { + usage(); + return; + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() + { + stop = true; + synchronized (stop) + { + stop.notifyAll(); + } + } + }); + System.out.println("Use Ctrl-C to shutdown EmailServer"); + + while (!stop) + { + synchronized (stop) + { + stop.wait(); + } + } + } + catch (InterruptedException e) + { + } + finally + { + context.close(); + } + + } + + private static void usage() + { + System.err.println("Use: EmailServer configLocation1, configLocation2, ..."); + System.err.println("\t configLocation - spring xml configs with EmailServer related beans (emailServer, emailServerConfiguration, emailService)"); + } + +} diff --git a/source/java/org/alfresco/email/server/EmailServerConfiguration.java b/source/java/org/alfresco/email/server/EmailServerConfiguration.java new file mode 100644 index 0000000000..d6afa2800d --- /dev/null +++ b/source/java/org/alfresco/email/server/EmailServerConfiguration.java @@ -0,0 +1,215 @@ +/* + * 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/EmailServerModel.java b/source/java/org/alfresco/email/server/EmailServerModel.java new file mode 100644 index 0000000000..ce2c92938f --- /dev/null +++ b/source/java/org/alfresco/email/server/EmailServerModel.java @@ -0,0 +1,51 @@ +/* + * 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.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Class defines the costants for Email Server Model + * + * @see alfresco/model/emailServerModel.xml + * @author Yan O + * @since 2.2 + */ +public interface EmailServerModel +{ + // Attachable aspect + static final QName ASPECT_ATTACHED = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "attached"); + + static final QName ASSOC_ATTACHMENT = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "attachment"); + + // Aliasable aspect + static final QName ASPECT_ALIASABLE = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "aliasable"); + + static final QName PROP_ALIAS = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "alias"); + + // Aspect emailed + static final QName ASPECT_EMAILED = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "emailed"); +} diff --git a/source/java/org/alfresco/email/server/EmailServiceImpl.java b/source/java/org/alfresco/email/server/EmailServiceImpl.java new file mode 100644 index 0000000000..eef0ddad93 --- /dev/null +++ b/source/java/org/alfresco/email/server/EmailServiceImpl.java @@ -0,0 +1,325 @@ +/* + * 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 java.util.Collection; +import java.util.Map; + +import org.alfresco.email.server.handler.EmailMessageHandler; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.email.EmailMessage; +import org.alfresco.service.cmr.email.EmailMessageException; +import org.alfresco.service.cmr.email.EmailService; +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.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; + +/** + * 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 NodeService nodeService; + private SearchService searchService; + private RetryingTransactionHelper retryingTransactionHelper; + + private boolean mailInboundEnabled; + /** Login of user that is set as unknown. */ + private String unknownUser; + /** List of message handlers */ + private Map emailMessageHandlerMap; + + /** + * @param nodeService Alfresco Node Service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService Alfresco Search Service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param retryingTransactionHelper Alfresco RetryingTransactionHelper + */ + public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + + /** + * @return Map of message handlers + */ + public Map getEmailMessageHandlerMap() + { + return emailMessageHandlerMap; + } + + /** + * @param emailMessageHandlerMap Map of message handlers + */ + public void setEmailMessageHandlerMap(Map emailMessageHandlerMap) + { + this.emailMessageHandlerMap = emailMessageHandlerMap; + } + + /** + * @param unknownUser Login of user that should be set as unknown. + */ + public void setUnknownUser(String unknownUser) + { + this.unknownUser = unknownUser; + } + + public void setMailInboundEnabled(boolean mailInboundEnabled) + { + this.mailInboundEnabled = mailInboundEnabled; + } + + /** + * {@inheritDoc} + */ + public void importMessage(EmailMessage message) + { + processMessage(null, message); + } + + /** + * {@inheritDoc} + */ + public void importMessage(NodeRef nodeRef, EmailMessage message) + { + processMessage(nodeRef, message); + } + + /** + * Process the message. Method is called after filtering by sender's address. + * + * @param nodeRef Addressed node (target node). + * @param message Email message + * @throws EmailMessageException Any exception occured inside the method will be converted and thrown as EmailMessageException + */ + private void processMessage(final NodeRef nodeRef, final EmailMessage message) + { + if (!mailInboundEnabled) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.mail-inbound-disabled")); + } + try + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() + { + final String userName = authenticate(message.getFrom()); + + AuthenticationUtil.runAs(new RunAsWork() + { + 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); + return null; + } + }; + retryingTransactionHelper.doInTransaction(callback, false); + } + catch (Throwable e) + { + log.error("Error process email message", e); + throw new EmailMessageException(e.getMessage()); + } + } + + /** + * @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. + */ + 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); + if (handler == null) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.handler-not-found")); + } + return handler; + } + + /** + * Method determines target node by recepient 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. + */ + private NodeRef getTargetNode(String recepient) + { + if (recepient == null || recepient.length() == 0) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-address")); + } + String[] parts = recepient.split("@"); + if (parts.length != 2) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-address")); + } + + // Ok, address looks well, let's try to find related alias + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + String query = String.format(AliasableAspect.SEARCH_TEMPLATE, parts[0]); + ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, query); + + // Sometimes result contains trash. For example if we look for node with alias='target' after searching, + // we will get all nodes wich contain word 'target' in them alias property. + for (int i = 0; i < resultSet.length(); i++) + { + NodeRef resRef = resultSet.getNodeRef(i); + Object alias = nodeService.getProperty(resRef, EmailServerModel.PROP_ALIAS); + if (parts[0].equals(alias)) + { + return resRef; + } + } + + // Ok, alias wasn't found, let's try to interpret recepient 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")); + } + + /** + * Authenticate in Alfresco repository by sender's e-mail address. + * + * @param from Sender's email address + * @return User name + * @throws EmailMessageException Exception will be thrown if authentication is failed. + */ + private String authenticate(String from) + { + String userName = null; + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + String query = "TYPE:cm\\:person +@cm\\:email:\"" + from + "\""; + + ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, query); + + if (resultSet.length() == 0) + { + userName = unknownUser; + if (userName == null || !isEmailContributeUser(userName)) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.unknown-user")); + } + } + else + { + 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)); + } + } + else + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.contribute-group-not-exist")); + } + } + return userName; + } + + /** + * Check that the user is the member in EMAIL_CONTRIBUTORS group + * + * @param userName User name + * @return True if the user is member of the group + * @exception EmailMessageException Exception will be thrown if the EMAIL_CONTRIBUTORS group isn't found + */ + private boolean isEmailContributeUser(String userName) + { + String searchQuery = "TYPE:\"{http://www.alfresco.org/model/user/1.0}authorityContainer\" +@usr\\:authorityName:\"GROUP_EMAIL_CONTRIBUTORS\""; + StoreRef storeRef = new StoreRef("user", "alfrescoUserStore"); + ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, searchQuery); + + if (resultSet.length() == 0) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.contribute-group-not-exist")); + } + + NodeRef groupNode = resultSet.getNodeRef(0); + + Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService.getProperty(groupNode, ContentModel.PROP_MEMBERS)); + + if (memberCollection.contains(userName)) + return true; + + return false; + } +} diff --git a/source/java/org/alfresco/email/server/EmailServiceRemotable.java b/source/java/org/alfresco/email/server/EmailServiceRemotable.java new file mode 100644 index 0000000000..1320450445 --- /dev/null +++ b/source/java/org/alfresco/email/server/EmailServiceRemotable.java @@ -0,0 +1,99 @@ +/* + * 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.email.server.impl.subetha.SubethaEmailMessage; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.email.EmailMessage; +import org.alfresco.service.cmr.email.EmailService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.remoting.rmi.RmiClientInterceptor; + +/** + * @author Michael Shavnev + * @since 2.2 + */ +public class EmailServiceRemotable extends AbstractLifecycleBean implements EmailService +{ + private String rmiRegistryHost; + + private int rmiRegistryPort; + + private EmailService emailServiceProxy; + + public void setRmiRegistryHost(String rmiRegistryHost) + { + this.rmiRegistryHost = rmiRegistryHost; + } + + public void setRmiRegistryPort(int rmiRegistryPort) + { + this.rmiRegistryPort = rmiRegistryPort; + } + + public void importMessage(EmailMessage message) + { + if (message instanceof SubethaEmailMessage) + { + ((SubethaEmailMessage) message).setRmiRegistry(rmiRegistryHost, rmiRegistryPort); + } + emailServiceProxy.importMessage(message); + } + + public void importMessage(NodeRef nodeRef, EmailMessage message) + { + if (message instanceof SubethaEmailMessage) + { + ((SubethaEmailMessage) message).setRmiRegistry(rmiRegistryHost, rmiRegistryPort); + } + emailServiceProxy.importMessage(nodeRef, message); + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + if (rmiRegistryHost == null) + { + throw new AlfrescoRuntimeException("Property 'rmiRegistryHost' not set"); + } + if (rmiRegistryPort == 0) + { + throw new AlfrescoRuntimeException("Property 'rmiRegistryPort' not set"); + } + + RmiClientInterceptor rmiClientInterceptor = new RmiClientInterceptor(); + rmiClientInterceptor.setRefreshStubOnConnectFailure(true); + rmiClientInterceptor.setServiceUrl("rmi://" + rmiRegistryHost + ":" + rmiRegistryPort + "/emailService"); + emailServiceProxy = (EmailService) ProxyFactory.getProxy(EmailService.class, rmiClientInterceptor); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } +} diff --git a/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java new file mode 100644 index 0000000000..2634f7a004 --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java @@ -0,0 +1,259 @@ +/* + * 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.handler; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Serializable; +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.service.cmr.email.EmailMessage; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +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; + +import com.sun.star.auth.InvalidArgumentException; + +/** + * Abstract class implements common logic for processing email messages. + * + * @author maxim + * @since 2.2 + */ +public abstract class AbstractEmailMessageHandler implements EmailMessageHandler +{ + private static final Log log = LogFactory.getLog(EmailMessageHandler.class); + + private AuthenticationService authenticationService; + private AuthenticationComponent authenticationComponent; + private NodeService nodeService; + private PersonService personService; + private SearchService searchService; + private ContentService contentService; + + /** + * @return Alfresco Content Service. + */ + public ContentService getContentService() + { + return contentService; + } + + /** + * @param contentService Alfresco Content Service. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @return Alfresco Authentication Component. + */ + public AuthenticationComponent getAuthenticationComponent() + { + return authenticationComponent; + } + + /** + * @param authenticationComponent Alfresco Authentication Component. + */ + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + 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; + } + + /** + * @return Alfresco Node Service. + */ + public NodeService getNodeService() + { + return nodeService; + } + + /** + * @param nodeService Alfresco Node Service. + */ + public void setNodeService(NodeService nodeService) + { + 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; + } + + /** + * @param to Email address which user part specifies node-dbid + * @return Referance to requested node. + * @throws InvalidArgumentException The exception is thrown if input string has incorrect format or empty. + */ + protected NodeRef getTargetNode(String to) throws InvalidArgumentException + { + if (to == null || to.length() == 0) + { + throw new InvalidArgumentException("Input string has to contain email address."); + } + String[] parts = to.split("@"); + if (parts.length != 2) + { + throw new InvalidArgumentException("Incorrect email address format."); + } + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + String query = "@sys\\:node-dbid:" + parts[0]; + ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, query); + if (resultSet.length() == 1) + { + return resultSet.getNodeRef(0); + } + return null; + } + + /** + * Write the content to the node + * + * @param nodeRef Target node + * @param content Content + */ + protected void writeContent(NodeRef nodeRef, String content) + { + writeContent(nodeRef, content, MimetypeMap.MIMETYPE_TEXT_PLAIN); + } + + /** + * Write the string as content to the node. + * + * @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. + */ + protected void writeContent(NodeRef nodeRef, String content, String contentType) + { + InputStream inputStream = new ByteArrayInputStream(content.getBytes()); + writeContent(nodeRef, inputStream, contentType, "UTF-8"); + } + + /** + * Write content to the node from InputStream. + * + * @param nodeRef Target node. + * @param content Content stream. + * @param contentType 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) + { + if (log.isDebugEnabled()) + { + log.debug("Write content (MimeType=\"" + contentType + "\", Encoding=\"" + encoding + "\""); + } + ContentService contentService = getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(contentType); + writer.setEncoding(encoding); + writer.putContent(content); + } + + /** + * Add emailed aspect to the specified node. + * + * @param nodeService Alfresco Node Service. + * @param nodeRef Target node. + * @param mailParser Mail message that will be used for extracting necessary information + */ + protected void addEmailedAspect(NodeRef nodeRef, EmailMessage message) + { + Map emailProps = new HashMap(); + emailProps.put(ContentModel.PROP_SENTDATE, message.getSentDate()); + emailProps.put(ContentModel.PROP_ORIGINATOR, message.getFrom()); + emailProps.put(ContentModel.PROP_ADDRESSEE, message.getTo()); + emailProps.put(ContentModel.PROP_SUBJECT, message.getSubject()); + nodeService.addAspect(nodeRef, EmailServerModel.ASPECT_EMAILED, emailProps); + + if (log.isDebugEnabled()) + { + log.debug("Emailed aspect has been added."); + } + } +} diff --git a/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java new file mode 100644 index 0000000000..a9a37b3900 --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java @@ -0,0 +1,141 @@ +/* + * 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.handler; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.service.cmr.email.EmailMessage; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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; + +/** + * Abstact class implements common logic for forum processing email mesages. + * + * @author maxim + * @since 2.2 + */ +public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMessageHandler +{ + /** + * Posts content + * + * @param nodeRef Reference to node + * @param parser Mail parser + */ + protected void addPostNode(NodeRef nodeRef, EmailMessage message) + { + NodeService nodeService = getNodeService(); + Date now = new Date(); + String nodeName = "posted-" + new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss").format(now) + ".html"; + + Map properties = new HashMap(1); + 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(); + + // Add necessary aspects + properties.clear(); + properties.put(ContentModel.PROP_TITLE, nodeName); + nodeService.addAspect(postNode, ContentModel.ASPECT_TITLED, properties); + properties.clear(); + properties.put(ApplicationModel.PROP_EDITINLINE, true); + nodeService.addAspect(postNode, ApplicationModel.ASPECT_INLINEEDITABLE, properties); + + // Write content + if (message.getBody() != null) + { + writeContent(postNode, message.getBody().getContent(), message.getBody().getContentType(), message.getBody().getEncoding()); + } + else + { + writeContent(postNode, ""); + } + addEmailedAspect(postNode, message); + } + + /** + * Finds first child with specified name + * + * @param nodeRef Parent node for the search + * @param subject String for search + * @return Reference to found node or null if node isn't found + */ + protected NodeRef getTopicNode(NodeRef nodeRef, String subject) + { + List assocRefList = getNodeService().getChildAssocs(nodeRef); + Iterator assocRefIter = assocRefList.iterator(); + + while (assocRefIter.hasNext()) + { + + ChildAssociationRef assocRef = assocRefIter.next(); + if (assocRef.getQName().getLocalName().equals(subject)) + { + return assocRef.getChildRef(); + } + } + return null; + } + + /** + * Adds topic node into Alfresco repository + * + * @param parentNode Parent node + * @param name Topic name + * @return Reference to created node + */ + protected NodeRef addTopicNode(NodeRef parentNode, String name) + { + + 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(); + + // Add necessary aspects + properties.clear(); + properties.put(ApplicationModel.PROP_ICON, "topic"); + getNodeService().addAspect(topic, ApplicationModel.ASPECT_UIFACETS, properties); + + return topic; + } + +} diff --git a/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java new file mode 100644 index 0000000000..984f0139f6 --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java @@ -0,0 +1,149 @@ +/* + * 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.handler; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +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.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Handler implementation address to document node. + * + * @author maxim + * @since 2.2 + */ +public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandler +{ + private static final String forumNodeName = "EmailForum"; + + public void processMessage(NodeRef nodeRef, EmailMessage message) + { + String messageSubject; + + if (message.getSubject() != null) + { + messageSubject = message.getSubject(); + } + else + { + messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis(); + } + + QName contentType = getNodeService().getType(nodeRef); + + if (contentType.equals(ContentModel.TYPE_CONTENT)) + { + NodeRef forumNode = getForumNode(nodeRef); + + if (forumNode == null) + { + forumNode = addForumNode(nodeRef); + } + + // Try to find existed node + NodeRef topicNode = getTopicNode(forumNode, messageSubject); + + if (topicNode == null) + { + topicNode = addTopicNode(forumNode, messageSubject); + } + + addPostNode(topicNode, message); + } + else + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-type")); + } + } + + /** + * Adds forum node + * + * @param nodeRef Paren node + * @return Reference to created node + */ + private NodeRef addForumNode(NodeRef nodeRef) + { + NodeService nodeService=getNodeService(); + //Add discussable aspect to content node + if (!nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE)) + { + nodeService.addAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE, null); + } + + //Create forum node and associate it with content node + Map properties = new HashMap(1); + properties.put(ContentModel.PROP_NAME, forumNodeName); + ChildAssociationRef childAssoc = nodeService.createNode(nodeRef, ForumModel.ASSOC_DISCUSSION, ForumModel.ASSOC_DISCUSSION, ForumModel.TYPE_FORUM, properties); + NodeRef forumNode = childAssoc.getChildRef(); + + //Add necessary aspects to forum node + properties.clear(); + properties.put(ApplicationModel.PROP_ICON, "forum"); + nodeService.addAspect(forumNode, ApplicationModel.ASPECT_UIFACETS, properties); + + return forumNode; + } + + /** + * Finds the first forum node + * + * @param nodeService Alfresco Node Service + * @param nodeRef Parent node + * @return Found node or null + */ + private NodeRef getForumNode(NodeRef nodeRef) + { + + if (getNodeService().hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE)) + { + List assocRefList = getNodeService().getChildAssocs(nodeRef); + Iterator assocRefIter = assocRefList.iterator(); + + while (assocRefIter.hasNext()) + { + ChildAssociationRef assocRef = assocRefIter.next(); + QName nodeTypeName = getNodeService().getType(assocRef.getChildRef()); + + if (nodeTypeName.equals(ForumModel.TYPE_FORUM)) + return assocRef.getChildRef(); + } + } + return null; + } +} diff --git a/source/java/org/alfresco/email/server/handler/EmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/EmailMessageHandler.java new file mode 100644 index 0000000000..335b737843 --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/EmailMessageHandler.java @@ -0,0 +1,50 @@ +/* + * 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.handler; + +import org.alfresco.service.cmr.email.EmailMessage; +import org.alfresco.service.cmr.email.EmailMessageException; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Interface for email handler for processing email message. + * + * @author maxim + * @since 2.2 + */ +public interface EmailMessageHandler +{ + /** + * Method invokes for processing email message. + * + * @param nodeRef Target node + * @param message Email message + * @exception EmailMessageException Exception is thrown if processing was failed + * @exception DuplicateChildNodeNameException Exception is thrown if node name is duplicate. + */ + void processMessage(NodeRef nodeRef, EmailMessage message); + +} diff --git a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java new file mode 100644 index 0000000000..0c4950b63a --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java @@ -0,0 +1,236 @@ +/* + * 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.handler; + +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import javax.mail.MessagingException; + +import org.alfresco.email.server.EmailServerModel; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +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.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Handler implementation address to folder node. + * + * @author Yan O + * @since 2.2 + */ +public class FolderEmailMessageHandler extends AbstractEmailMessageHandler +{ + private static final Log log = LogFactory.getLog(FolderEmailMessageHandler.class); + + /** + * {@inheritDoc} + */ + public void processMessage(NodeRef nodeRef, EmailMessage message) + { + if (log.isDebugEnabled()) + { + log.debug("Message is psocessing by SpaceMailMessageHandler"); + } + try + { + // Check type of the node. It must be a SPACE + QName nodeTypeName = getNodeService().getType(nodeRef); + + if (nodeTypeName.equals(ContentModel.TYPE_FOLDER)) + { + // Add the content into the system + addAlfrescoContent(nodeRef, message, null); + } + else + { + if (log.isDebugEnabled()) + { + log.debug("Addressed node type isn't a folder. Message has been passed without any actions."); + } + } + } + catch (IOException ex) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.content-error"), ex); + } + } + + /** + * 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. + */ + public void addAlfrescoContent(NodeRef spaceNodeRef, EmailMessage message, String nameConflictResolver) throws IOException + { + // Set default values for email fields + if (nameConflictResolver == null) + nameConflictResolver = ""; + String messageSubject = "EMPTY_SUBJECT_" + nameConflictResolver; + if (message.getSubject().length() != 0) + { + messageSubject = message.getSubject() + nameConflictResolver; + } + + // Create node + if (log.isDebugEnabled()) + { + log.debug("Adding main content node ..."); + } + NodeRef contentNodeRef; + contentNodeRef = addContentNode(getNodeService(), spaceNodeRef, messageSubject); + + // Add titled aspect + addTitledAspect(contentNodeRef, messageSubject); + + // 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."); + } + + // 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 + ")"; + } + + attachmentNode = addAttachment(getNodeService(), spaceNodeRef, contentNodeRef, fileName); + writeContent(attachmentNode, attachment.getContent(), attachment.getContentType(), attachment.getEncoding()); + } + } + + /** + * Add new node into Alfresco repository with specified parameters. Node content isn't added. New node will be created with ContentModel.ASSOC_CONTAINS association with parent. + * + * @param nodeService Alfresco Node Service + * @param parent Parent node + * @param name Name of the new node + * @return Reference to created node + */ + private NodeRef addContentNode(NodeService nodeService, NodeRef parent, String name) + { + return addContentNode(nodeService, parent, name, ContentModel.ASSOC_CONTAINS); + } + + /** + * Add new node into Alfresco repository with specified parameters. Node content isn't added. + * + * @param nodeService Alfresco Node Service + * @param parent Parent node + * @param name Name of the new node + * @param assocType Association type that should be set between parent node and the new one. + * @return Reference to created node + */ + 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(); + } + + /** + * Adds new node into Alfresco repository and mark its as an attachment. + * + * @param nodeService Alfresco Node Service. + * @param folder Space/Folder to add. + * @param mainContentNode Main content node. Any mail is added into Alfresco as one main content node and several its attachments. Each attachment related with its main node. + * @param fileName File name for the attachment. + * @return Reference to created node. + */ + private NodeRef addAttachment(NodeService nodeService, NodeRef folder, NodeRef mainContentNode, String fileName) + { + if (log.isDebugEnabled()) + { + log.debug("Adding attachment node (name=" + fileName + ")."); + } + + NodeRef attachmentNode = addContentNode(nodeService, folder, fileName); + + // Add attached aspect + Map attachedProps = new HashMap(); + nodeService.addAspect(attachmentNode, EmailServerModel.ASPECT_ATTACHED, attachedProps); + nodeService.createAssociation(attachmentNode, mainContentNode, EmailServerModel.ASSOC_ATTACHMENT); + + if (log.isDebugEnabled()) + { + log.debug("Attachment has been added."); + } + return attachmentNode; + } + + /** + * Adds titled aspect to the specified node. + * + * @param nodeRef Target node. + * @param title Title + */ + private void addTitledAspect(NodeRef nodeRef, String title) + { + Map titledProps = new HashMap(); + titledProps.put(ContentModel.PROP_TITLE, title); + titledProps.put(ContentModel.PROP_DESCRIPTION, "Received by SMTP"); + getNodeService().addAspect(nodeRef, ContentModel.ASPECT_TITLED, titledProps); + + if (log.isDebugEnabled()) + { + log.debug("Titled aspect has been added."); + } + } +} diff --git a/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java new file mode 100644 index 0000000000..1f75c1075a --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/ForumEmailMessageHandler.java @@ -0,0 +1,75 @@ +/* + * 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.handler; + +import org.alfresco.i18n.I18NUtil; +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; + +/** + * Handler implementation address to forum node. + * + * @author maxim + * @since 2.2 + */ +public class ForumEmailMessageHandler extends AbstractForumEmailMessageHandler +{ + /** + * {@inheritDoc} + */ + public void processMessage(NodeRef nodeRef, EmailMessage message) + { + String messageSubject; + + if (message.getSubject() != null) + { + messageSubject = message.getSubject(); + } + else + { + messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis(); + } + + QName nodeType = getNodeService().getType(nodeRef); + + if (nodeType.equals(ForumModel.TYPE_FORUM)) + { + NodeRef topicNode = getTopicNode(nodeRef, messageSubject); + + if (topicNode == null) + { + topicNode = addTopicNode(nodeRef, messageSubject); + } + addPostNode(topicNode, message); + } + else + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-type")); + } + } +} diff --git a/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java new file mode 100644 index 0000000000..357d476875 --- /dev/null +++ b/source/java/org/alfresco/email/server/handler/TopicEmailMessageHandler.java @@ -0,0 +1,64 @@ +/* + * 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.handler; + +import org.alfresco.i18n.I18NUtil; +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; + +/** + * Handler implementation address to topic node. + * + * @author maxim + * @since 2.2 + */ +public class TopicEmailMessageHandler extends AbstractForumEmailMessageHandler +{ + /** + * {@inheritDoc} + */ + public void processMessage(NodeRef nodeRef, EmailMessage message) + { + QName nodeType = getNodeService().getType(nodeRef); + NodeRef topicNode = null; + + if (nodeType.equals(ForumModel.TYPE_TOPIC)) + { + topicNode = nodeRef; + } + else if (nodeType.equals(ForumModel.TYPE_POST)) + { + topicNode = getNodeService().getPrimaryParent(nodeRef).getChildRef(); + } + if (topicNode == null) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-node-ref")); + } + addPostNode(topicNode, message); + } +} diff --git a/source/java/org/alfresco/email/server/impl/EmailMessageImpl.java b/source/java/org/alfresco/email/server/impl/EmailMessageImpl.java new file mode 100644 index 0000000000..d08a8f3715 --- /dev/null +++ b/source/java/org/alfresco/email/server/impl/EmailMessageImpl.java @@ -0,0 +1,105 @@ +/* + * 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.impl; + +import java.util.Date; + +import org.alfresco.service.cmr.email.EmailMessage; +import org.alfresco.service.cmr.email.EmailMessagePart; + +/** + * Implementation EmailMessage interface. + * + * @author maxim + * @since 2.2 + */ +public class EmailMessageImpl implements EmailMessage +{ + private static final long serialVersionUID = 8215537693963343756L; + + private String to; + private String from; + private String subject; + private Date sentDate; + private EmailMessagePart body; + + + public EmailMessageImpl(String to, String from, String subject, String body) + { + if (to == null) + { + throw new IllegalArgumentException("To cannot be null"); + } + this.to = to; + if (from == null) + { + throw new IllegalArgumentException("From cannot be null"); + } + this.from = from; + if (subject == null) + { + throw new IllegalArgumentException("Subject cannot be null"); + } + this.subject = subject; + if (body == null) + { + throw new IllegalArgumentException("Body cannot be null"); + } + this.body = new EmailMessagePartImpl("Content.txt", body.getBytes()); + + this.sentDate = new Date(); + } + + public String getTo() + { + return to; + } + + public String getFrom() + { + return from; + } + + public String getSubject() + { + return subject; + } + + public Date getSentDate() + { + return sentDate; + } + + public EmailMessagePart getBody() + { + return body; + } + + public EmailMessagePart[] getAttachments() + { + return new EmailMessagePart[0]; + } + +} diff --git a/source/java/org/alfresco/email/server/impl/EmailMessagePartImpl.java b/source/java/org/alfresco/email/server/impl/EmailMessagePartImpl.java new file mode 100644 index 0000000000..99639b8a36 --- /dev/null +++ b/source/java/org/alfresco/email/server/impl/EmailMessagePartImpl.java @@ -0,0 +1,100 @@ +/* + * 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.impl; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.alfresco.service.cmr.email.EmailMessagePart; + +/** + * Implementation EmailMessagePart interface. + * + * @author maxim + * @since 2.2 + */ +public class EmailMessagePartImpl implements EmailMessagePart +{ + private static final long serialVersionUID = 779186820993301580L; + + private byte[] content; + private String encoding; + private String fileName; + + + public EmailMessagePartImpl(String fileName, byte[] content) + { + this(fileName, null, content); + } + + public EmailMessagePartImpl(String fileName, String encoding, byte[] content) + { + if (fileName == null) + { + throw new IllegalArgumentException("FileName cannot be null"); + } + this.fileName = fileName; + + if (content == null) + { + throw new IllegalArgumentException("Content cannot be null"); + } + this.content = content; + + if (encoding == null) + { + this.encoding = "utf8"; + } + else + { + this.encoding = encoding; + } + } + + public InputStream getContent() + { + return new ByteArrayInputStream(content); + } + + public String getContentType() + { + return "text/plain"; + } + + public String getEncoding() + { + return encoding; + } + + public String getFileName() + { + return fileName; + } + + public int getSize() + { + return content.length; + } +} diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java new file mode 100644 index 0000000000..4c2e1db75f --- /dev/null +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java @@ -0,0 +1,405 @@ +/* + * 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.impl.subetha; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Part; +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; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Concrete representation of an email message as implemented for the SubEtha mail server. + * + * @since 2.2 + */ +public class SubethaEmailMessage implements EmailMessage +{ + private static final long serialVersionUID = -3735187524926395261L; + + private static final Log log = LogFactory.getLog(SubethaEmailMessage.class); + + private static final String MIME_PLAIN_TEXT = "text/plain"; + private static final String MIME_HTML_TEXT = "text/html"; + private static final String MIME_XML_TEXT = "text/xml"; + private static final String MIME_APPLICATION = "application/*"; + private static final String MIME_IMAGE = "image/*"; + private static final String MIME_MULTIPART = "multipart/*"; + private static final String MIME_RFC822 = "message/rfc822"; + private static final String FILENAME_ATTACHMENT_PREFIX = "Attachment"; + + private String from; + private String to; + private String subject; + private Date sentDate; + private EmailMessagePart body; + private EmailMessagePart[] attachments; + transient private int bodyNumber = 0; + transient private int attachmentNumber = 0; + transient private List attachmentList = new LinkedList(); + + protected SubethaEmailMessage() + { + super(); + } + + public SubethaEmailMessage(MimeMessage mimeMessage) + { + processMimeMessage(mimeMessage); + } + + public SubethaEmailMessage(String from, String to, InputStream dataInputStream) + { + this.to = to; + this.from = from; + + MimeMessage mimeMessage = null; + try + { + mimeMessage = new MimeMessage(Session.getDefaultInstance(System.getProperties()), dataInputStream); + } + catch (MessagingException e) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.error-creating-message"), e); + } + + processMimeMessage(mimeMessage); + } + + private void processMimeMessage(MimeMessage mimeMessage) + { + if (from == null) + { + Address[] addresses = null; + try + { + addresses = mimeMessage.getFrom(); + } + catch (MessagingException e) + { + throw new EmailMessageException("Error extract from.", e); + } + if (addresses == null || addresses.length == 0) + { + throw new EmailMessageException("There is no one from."); + } + from = addresses[0].toString(); + } + + if (to == null) + { + Address[] addresses = null; + try + { + addresses = mimeMessage.getAllRecipients(); + } + catch (MessagingException e) + { + throw new EmailMessageException("Error extract recepient.", e); + } + if (addresses == null || addresses.length == 0) + { + throw new EmailMessageException("There is no one recepient."); + } + to = addresses[0].toString(); + } + + try + { + subject = mimeMessage.getSubject(); + } + catch (MessagingException e) + { + throw new EmailMessageException("Error extract subject.", e); + } + if (subject == null) + { + subject = ""; // Just anti-null stub :) + } + + try + { + sentDate = mimeMessage.getSentDate(); + } + catch (MessagingException e) + { + throw new EmailMessageException("Error extract sentDate.", e); + } + if (sentDate == null) + { + sentDate = new Date(); // Just anti-null stub :) + } + + parseMesagePart(mimeMessage); + attachments = new EmailMessagePart[attachmentList.size()]; + attachmentList.toArray(attachments); + attachmentList = null; + } + + private void parseMesagePart(Part messagePart) + { + try + { + if (messagePart.isMimeType(MIME_PLAIN_TEXT) || messagePart.isMimeType(MIME_HTML_TEXT)) + { + if (log.isDebugEnabled()) + { + log.debug("Text or HTML part was found. ContentType: " + messagePart.getContentType()); + } + addBody(messagePart); + } + else if (messagePart.isMimeType(MIME_XML_TEXT)) + { + if (log.isDebugEnabled()) + { + log.debug("XML part was found."); + } + addAttachment(messagePart); + } + else if (messagePart.isMimeType(MIME_APPLICATION)) + { + if (log.isDebugEnabled()) + { + log.debug("Application part was found."); + } + addAttachment(messagePart); + } + else if (messagePart.isMimeType(MIME_IMAGE)) + { + if (log.isDebugEnabled()) + { + log.debug("Image part was found."); + } + addAttachment(messagePart); + } + else if (messagePart.isMimeType(MIME_MULTIPART)) + { + // if multipart, this method will be called recursively + // for each of its parts + Multipart mp = (Multipart) messagePart.getContent(); + int count = mp.getCount(); + + if (log.isDebugEnabled()) + { + log.debug("MULTIPART with " + count + " part(s) found. Processin each part..."); + } + for (int i = 0; i < count; i++) + { + parseMesagePart(mp.getBodyPart(i)); + } + + if (log.isDebugEnabled()) + { + log.debug("MULTIPART processed."); + } + + } + else if (messagePart.isMimeType(MIME_RFC822)) + { + // if rfc822, call this method with its content as the part + if (log.isDebugEnabled()) + { + log.debug("MIME_RFC822 part found. Processing inside part..."); + } + + parseMesagePart((Part) messagePart.getContent()); + + if (log.isDebugEnabled()) + { + log.debug("MIME_RFC822 processed."); + } + + } + else + { + // if all else fails, put this in the attachments map. + // Actually we don't know what it is. + if (log.isDebugEnabled()) + { + log.debug("Unrecognized part was found. Put it into attachments."); + } + addAttachment(messagePart); + } + } + catch (IOException e) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.error-parse-message"), e); + } + catch (MessagingException e) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.error-parse-message"), e); + } + } + + private void addBody(Part messagePart) throws MessagingException + { + if (body != null) + { + if (!MIME_PLAIN_TEXT.equals(body.getContentType()) && messagePart.isMimeType(MIME_PLAIN_TEXT)) + { + attachmentList.add(body); + body = new SubethaEmailMessagePart(messagePart); + if (log.isDebugEnabled()) + { + log.debug("Body has been changed to the new one."); + } + } + else + { + attachmentList.add(new SubethaEmailMessagePart(messagePart, getPartFileName(getSubject() + " (part " + ++bodyNumber + ")", messagePart))); + if (log.isInfoEnabled()) + { + log.info(String.format("Attachment \"%s\" has been added.", attachmentList.get(attachmentList.size() - 1).getFileName())); + } + } + } + else + { + body = new SubethaEmailMessagePart(messagePart, getPartFileName(getSubject() + " (part " + ++bodyNumber + ")", messagePart)); + if (log.isDebugEnabled()) + { + log.debug("Boby has been added."); + } + } + + } + + /** + * Method adds a message part to the attachments list + * + * @param messagePart A part of message + * @throws EmailMessageException + * @throws MessagingException + */ + private void addAttachment(Part messagePart) throws MessagingException + { + String fileName = getPartFileName(FILENAME_ATTACHMENT_PREFIX + ++attachmentNumber, messagePart); + attachmentList.add(new SubethaEmailMessagePart(messagePart, fileName)); + if (log.isDebugEnabled()) + { + log.debug("Attachment added: " + fileName); + } + } + + /** + * Method extracts file name from a message part for saving its as aa attachment. If the file name can't be extracted, it will be generated based on defaultPrefix parameter. + * + * @param defaultPrefix This prefix fill be used for generating file name. + * @param messagePart A part of message + * @return File name. + * @throws MessagingException + */ + private String getPartFileName(String defaultPrefix, Part messagePart) throws MessagingException + { + String fileName = messagePart.getFileName(); + if (fileName != null) + { + try + { + fileName = MimeUtility.decodeText(fileName); + } + catch (UnsupportedEncodingException ex) + { + // Nothing to do :) + } + } + else + { + fileName = defaultPrefix; + if (messagePart.isMimeType(MIME_PLAIN_TEXT)) + fileName += ".txt"; + else if (messagePart.isMimeType(MIME_HTML_TEXT)) + fileName += ".html"; + else if (messagePart.isMimeType(MIME_XML_TEXT)) + fileName += ".xml"; + else if (messagePart.isMimeType(MIME_IMAGE)) + fileName += ".gif"; + } + return fileName; + } + + public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort) + { + if (body instanceof SubethaEmailMessagePart) + { + ((SubethaEmailMessagePart) body).setRmiRegistry(rmiRegistryHost, rmiRegistryPort); + } + + for (EmailMessagePart attachment : attachments) + { + if (attachment instanceof SubethaEmailMessagePart) { + ((SubethaEmailMessagePart) attachment).setRmiRegistry(rmiRegistryHost, rmiRegistryPort); + } + } + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + public Date getSentDate() + { + return sentDate; + } + + public String getSubject() + { + return subject; + } + + public EmailMessagePart getBody() + { + return body; + } + + public EmailMessagePart[] getAttachments() + { + return attachments; + } + +} diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java new file mode 100644 index 0000000000..a4d63d2a0c --- /dev/null +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessagePart.java @@ -0,0 +1,160 @@ +/* + * 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.impl.subetha; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.charset.Charset; +import java.util.regex.Matcher; +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.remote.RemotableInputStream; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @since 2.2 + */ +public class SubethaEmailMessagePart implements EmailMessagePart +{ + private static final long serialVersionUID = -8530238872199733096L; + + static final Log log = LogFactory.getLog(SubethaEmailMessagePart.class); + + private static final Pattern encodingExtractor = Pattern.compile("charset\\s*=[\\s\"]*([^\";\\s]*)"); + + private String encoding; + private String fileName; + private int fileSize = -1; + private String contentType; + private InputStream contentInputStream; + + private String rmiRegistryHost; + private int rmiRegistryPort; + + protected SubethaEmailMessagePart() + { + super(); + } + + /** + * Object can be built on existing message part only. + * + * @param messagePart Message part. + */ + public SubethaEmailMessagePart(Part messagePart) + { + if (messagePart == null) + throw new IllegalArgumentException("messagePart"); + + try + { + fileSize = messagePart.getSize(); + fileName = messagePart.getFileName(); + contentType = messagePart.getContentType(); + + Matcher matcher = encodingExtractor.matcher(contentType); + if (matcher.find()) + { + encoding = matcher.group(1); + if (!Charset.isSupported(encoding)) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.usupported-encoding", encoding)); + } + } + + try + { + contentInputStream = messagePart.getInputStream(); + } + catch (Exception ex) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.error-getting-content-stream"), ex); + } + } + catch (MessagingException e) + { + throw new EmailMessageException(I18NUtil.getMessage("email.server.incorrect-message-part"), e); + } + } + + public SubethaEmailMessagePart(Part messagePart, String fileName) + { + this(messagePart); + this.fileName = fileName; + } + + + public InputStream getContent() + { + return contentInputStream; + } + + public String getContentType() + { + return contentType; + } + + public String getEncoding() + { + return encoding; + } + + public String getFileName() + { + return fileName; + } + + public int getSize() + { + return fileSize; + } + + + public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort) + { + this.rmiRegistryHost = rmiRegistryHost; + this.rmiRegistryPort = rmiRegistryPort; + } + + private void writeObject(ObjectOutputStream out) throws IOException + { + contentInputStream = new RemotableInputStream(rmiRegistryHost, rmiRegistryPort, contentInputStream); + out.defaultWriteObject(); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + } +} diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java new file mode 100644 index 0000000000..660d502a9b --- /dev/null +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java @@ -0,0 +1,209 @@ +/* + * 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.impl.subetha; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +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; +import org.apache.commons.logging.LogFactory; +import org.subethamail.smtp.MessageContext; +import org.subethamail.smtp.MessageHandler; +import org.subethamail.smtp.MessageHandlerFactory; +import org.subethamail.smtp.RejectException; +import org.subethamail.smtp.TooMuchDataException; +import org.subethamail.smtp.server.SMTPServer; +import org.subethamail.smtp.server.io.DeferredFileOutputStream; + +/** + * @since 2.2 + */ +public class SubethaEmailServer extends EmailServer +{ + private final static Log log = LogFactory.getLog(SubethaEmailServer.class); + + private SMTPServer serverImpl; + + protected SubethaEmailServer(EmailServerConfiguration serverConfiguration) + { + super(serverConfiguration); + serverImpl = new SMTPServer(new HandlerFactory()); + serverImpl.setPort(serverConfiguration.getPort()); + serverImpl.setHostName(serverConfiguration.getDomain()); + } + + @Override + public void startup() + { + serverImpl.start(); + log.info("Email Server has started successfully"); + } + + @Override + public void shutdown() + { + serverImpl.stop(); + log.info("Email Server has stopped successfully"); + } + + class HandlerFactory implements MessageHandlerFactory + { + public MessageHandler create(MessageContext messageContext) + { + return new Handler(messageContext); + } + }; + + class Handler implements MessageHandler + { + + /** + * 7 megs by default. The server will buffer incoming messages to disk when they hit this limit in the DATA received. + */ + private int DEFAULT_DATA_DEFERRED_SIZE = 1024 * 1024 * 7; + + private List EMPTY_LIST = new LinkedList(); + + private String from; + private MessageContext messageContext; + List deliveries = new ArrayList(); + + public Handler(MessageContext messageContext) + { + this.messageContext = messageContext; + } + + public MessageContext getMessageContext() + { + return messageContext; + } + + public void from(String from) throws RejectException + { + this.from = from; + try + { + blackAndWhiteListFiltering(from); + } + catch (EmailMessageException e) + { + throw new RejectException(554, e.getMessage()); + } + } + + public void recipient(String recipient) throws RejectException + { + deliveries.add(new Delivery(recipient)); + } + + public void data(InputStream data) throws TooMuchDataException, IOException, RejectException + { + if (deliveries.size() == 1) + { + Delivery delivery = deliveries.get(0); + processDelivery(delivery, data); + } + else if (deliveries.size() > 1) + { + DeferredFileOutputStream dfos = null; + try + { + dfos = new DeferredFileOutputStream(DEFAULT_DATA_DEFERRED_SIZE); + + byte[] bytes = new byte[1024 * 8]; + for (int len = -1; (len = data.read(bytes)) != -1;) + { + dfos.write(bytes, 0, len); + } + for (Delivery delivery : deliveries) + { + processDelivery(delivery, dfos.getInputStream()); + } + } + finally + { + try + { + dfos.close(); + } + catch (Exception e) + { + } + } + } + } + + private void processDelivery(Delivery delivery, InputStream data) throws RejectException + { + EmailMessage emailMessage; + try + { + emailMessage = new SubethaEmailMessage(from, delivery.getRecipient(), data); + configuration.getEmailService().importMessage(emailMessage); + } + catch (EmailMessageException e) + { + throw new RejectException(554, e.getMessage()); + } + } + + public List getAuthenticationMechanisms() + { + return EMPTY_LIST; + } + + public boolean auth(String clientInput, StringBuffer response) throws RejectException + { + return true; + } + + public void resetState() + { + } + }; + + class Delivery + { + private String recipient; + + public Delivery(String recipient) + { + this.recipient = recipient; + } + + public String getRecipient() + { + return recipient; + } + }; + +} diff --git a/source/java/org/alfresco/service/cmr/email/EmailMessage.java b/source/java/org/alfresco/service/cmr/email/EmailMessage.java new file mode 100644 index 0000000000..409126bf29 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/email/EmailMessage.java @@ -0,0 +1,68 @@ +/* + * 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.service.cmr.email; + +import java.io.Serializable; +import java.util.Date; + +/** + * Interface to process email messages. + * + * @author maxim + * @since 2.2 + */ +public interface EmailMessage extends Serializable +{ + /** + * @return FROM address. + */ + public String getFrom(); + + /** + * @return TO address. + */ + public String getTo(); + + /** + * @return sent date. + */ + public Date getSentDate(); + + /** + * @return subject of the message. + */ + public String getSubject(); + + /** + * @return part of the mail body. + */ + public EmailMessagePart getBody(); + + /** + * @return parts of the mail attachments. + */ + public EmailMessagePart[] getAttachments(); + +} diff --git a/source/java/org/alfresco/service/cmr/email/EmailMessageException.java b/source/java/org/alfresco/service/cmr/email/EmailMessageException.java new file mode 100644 index 0000000000..6b51515b28 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/email/EmailMessageException.java @@ -0,0 +1,67 @@ +/* + * 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.service.cmr.email; + +/** + * @since 2.2 + */ +public class EmailMessageException extends RuntimeException +{ + private static final long serialVersionUID = 5039365329619219256L; + + /** + * Empty contructor + */ + public EmailMessageException() + { + + 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); + } +} diff --git a/source/java/org/alfresco/service/cmr/email/EmailMessagePart.java b/source/java/org/alfresco/service/cmr/email/EmailMessagePart.java new file mode 100644 index 0000000000..32a4abc974 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/email/EmailMessagePart.java @@ -0,0 +1,62 @@ +/* + * 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.service.cmr.email; + +import java.io.InputStream; +import java.io.Serializable; + +/** + * Interface to process email parts. + * + * @author maxim + * @since 2.2 + */ +public interface EmailMessagePart extends Serializable +{ + /** + * @return size. + */ + public int getSize(); + + /** + * @return file name. + */ + public String getFileName(); + + /** + * @return encoding. + */ + public String getEncoding(); + + /** + * @return content type. + */ + public String getContentType(); + + /** + * @return InputStream reference. + */ + public InputStream getContent(); +} diff --git a/source/java/org/alfresco/service/cmr/email/EmailService.java b/source/java/org/alfresco/service/cmr/email/EmailService.java new file mode 100644 index 0000000000..a04ae89471 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/email/EmailService.java @@ -0,0 +1,66 @@ +/* + * 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.service.cmr.email; + +import org.alfresco.service.Auditable; +import org.alfresco.service.PublicService; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Service to process email messages. The incoming messages are treated as content that need + * to be created or modified. The target node can be the address of the node: + * + *
+ *    14232@alfresco.mycorp.com
+ *    where
+ *        14232 is a the node's unique identifier (sys:node-dbid)
+ * 
+ * + * @since 2.2 + * @author Derek Hulley + */ +@PublicService +public interface EmailService +{ + /** + * Processes an email message. The message's content is intended for a node found by + * examining the email's target address. + * + * @param message the email message + * @throws EmailMessageRejectException if the message is rejected for any reason + */ + @Auditable(parameters = { "message" }) + void importMessage(EmailMessage message); + + /** + * Process an email message. The message's content is intended for a specific node. + * + * @param nodeRef the node to import the message to + * @param message the email message + * @throws EmailMessageRejectException if the message is rejected for any reason + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = { "nodeRef", "message" }) + void importMessage(NodeRef nodeRef, EmailMessage message); +} diff --git a/source/java/org/alfresco/service/namespace/NamespaceService.java b/source/java/org/alfresco/service/namespace/NamespaceService.java index e9720fe97b..afe2973947 100644 --- a/source/java/org/alfresco/service/namespace/NamespaceService.java +++ b/source/java/org/alfresco/service/namespace/NamespaceService.java @@ -117,6 +117,12 @@ public interface NamespaceService extends NamespacePrefixResolver /** WCM Application Model Prefix */ static final String WCMAPP_MODEL_PREFIX = "wca"; + + /** Email Server Application Model URI */ + static final String EMAILSERVER_MODEL_URI = "http://www.alfresco.org/model/emailserver/1.0"; + + /** Email Server Application Model Prefix */ + static final String EMAILSERVER_MODEL_PREFIX = "emailserver"; /** WCM Workflow Model Prefix */ static final String WCMWF_MODEL = "wcmwf"; diff --git a/source/java/org/alfresco/util/remote/RemotableInputStream.java b/source/java/org/alfresco/util/remote/RemotableInputStream.java new file mode 100644 index 0000000000..9e580d45ca --- /dev/null +++ b/source/java/org/alfresco/util/remote/RemotableInputStream.java @@ -0,0 +1,143 @@ +/* + * 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.util.remote; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.InetAddress; + +import org.alfresco.util.remote.server.RemoteInputStreamServer; +import org.alfresco.util.remote.server.RmiRemoteInputStreamServer; + +/** + * The data consuming side of the remote connection that the InputStream spans. + * + * @author Michael Shavnev + * @since Alfresco 2.2 + */ +public class RemotableInputStream extends InputStream implements Serializable +{ + private static final long serialVersionUID = 2434858590717000057L; + + private int port; + private String host; + private String name; + + transient private RemoteInputStreamServer inputStreamServer; + + public RemotableInputStream(String host, int port, InputStream inputStream) + { + this.host = host; + this.port = port; + this.inputStreamServer = new RmiRemoteInputStreamServer(inputStream); + } + + public void close() throws IOException + { + inputStreamServer.close(); + } + + public int read() throws IOException + { + return inputStreamServer.read(); + } + + public int read(byte[] bytes) throws IOException + { + return inputStreamServer.read(bytes); + } + + public int read(byte[] bytes, int off, int len) throws IOException + { + return inputStreamServer.read(bytes, off, len); + } + + public long skip(long n) throws IOException + { + return inputStreamServer.skip(n); + } + + public int available() throws IOException + { + return inputStreamServer.available(); + } + + public void mark(int readlimit) + { + inputStreamServer.mark(readlimit); + } + + public boolean markSupported() + { + return inputStreamServer.markSupported(); + } + + public void reset() throws IOException + { + inputStreamServer.reset(); + } + + private void writeObject(ObjectOutputStream out) throws IOException + { + name = inputStreamServer.start(host, port); + out.defaultWriteObject(); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + inputStreamServer = (RemoteInputStreamServer) RmiRemoteInputStreamServer.obtain(host, port, name); + } + + public static void main(String[] args) throws Exception + { + RemotableInputStream remotableInputStream = new RemotableInputStream(InetAddress.getLocalHost().getHostName(), 7777, new ByteArrayInputStream("test".getBytes())); + + for (int b = -1; (b = remotableInputStream.read()) != -1;) + { + System.out.println((char) b); + } + + remotableInputStream = new RemotableInputStream(InetAddress.getLocalHost().getHostName(), 7777, new ByteArrayInputStream("test".getBytes())); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(remotableInputStream); + + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); + remotableInputStream = (RemotableInputStream) ois.readObject(); + + for (int b = -1; (b = remotableInputStream.read()) != -1;) + { + System.out.println((char) b); + } + remotableInputStream.close(); + } +} diff --git a/source/java/org/alfresco/util/remote/server/AbstractRemoteInputStreamServer.java b/source/java/org/alfresco/util/remote/server/AbstractRemoteInputStreamServer.java new file mode 100644 index 0000000000..d410a4339b --- /dev/null +++ b/source/java/org/alfresco/util/remote/server/AbstractRemoteInputStreamServer.java @@ -0,0 +1,89 @@ +/* + * 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.util.remote.server; + +import java.io.IOException; +import java.io.InputStream; + +/** + * The data producing side of the remote connection that the InputStream spans. + * + * @author Michael Shavnev + * @since Alfresco 2.2 + */ +public abstract class AbstractRemoteInputStreamServer implements RemoteInputStreamServer +{ + protected InputStream inputStream; + + protected AbstractRemoteInputStreamServer(InputStream inputStream) + { + this.inputStream = inputStream; + } + + public int read() throws IOException + { + return inputStream.read(); + } + + public int read(byte[] bytes) throws IOException + { + return inputStream.read(bytes); + } + + public int read(byte[] bytes, int off, int len) throws IOException + { + return inputStream.read(bytes, off, len); + } + + public long skip(long n) throws IOException + { + return inputStream.skip(n); + } + + public int available() throws IOException + { + return inputStream.available(); + } + + public void mark(int readlimit) + { + inputStream.mark(readlimit); + } + + public boolean markSupported() + { + return inputStream.markSupported(); + } + + public void reset() throws IOException + { + inputStream.reset(); + } + + public void close() throws IOException + { + inputStream.close(); + } +} diff --git a/source/java/org/alfresco/util/remote/server/RemoteInputStreamServer.java b/source/java/org/alfresco/util/remote/server/RemoteInputStreamServer.java new file mode 100644 index 0000000000..8b52369f0a --- /dev/null +++ b/source/java/org/alfresco/util/remote/server/RemoteInputStreamServer.java @@ -0,0 +1,57 @@ +/* + * 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.util.remote.server; + +import java.io.IOException; +import java.rmi.RemoteException; + +/** + * Interface for remote input stream support. + * + * @author Michael Shavnev + * @since Alfresco 2.2 + */ +public interface RemoteInputStreamServer +{ + public String start(String host, int port) throws RemoteException; + + public int read() throws IOException; + + public int read(byte[] bytes) throws IOException; + + public int read(byte[] bytes, int off, int len) throws IOException; + + public long skip(long n) throws IOException; + + public int available() throws IOException; + + public void mark(int readlimit); + + public boolean markSupported(); + + public void reset() throws IOException; + + public void close() throws IOException; +} diff --git a/source/java/org/alfresco/util/remote/server/RmiRemoteInputStreamServer.java b/source/java/org/alfresco/util/remote/server/RmiRemoteInputStreamServer.java new file mode 100644 index 0000000000..8b1def432f --- /dev/null +++ b/source/java/org/alfresco/util/remote/server/RmiRemoteInputStreamServer.java @@ -0,0 +1,107 @@ +/* + * 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.util.remote.server; + +import java.io.IOException; +import java.io.InputStream; +import java.rmi.RemoteException; +import java.util.UUID; + +import org.springframework.remoting.rmi.RmiProxyFactoryBean; +import org.springframework.remoting.rmi.RmiServiceExporter; + +/** + * Concrete implementation of a remoting InputStream based on RMI. + * + * @author Michael Shavnev + * @since Alfresco 2.2 + */ +public class RmiRemoteInputStreamServer extends AbstractRemoteInputStreamServer +{ + private RmiServiceExporter rmiServiceExporter; + + public RmiRemoteInputStreamServer(InputStream inputStream) + { + super(inputStream); + } + + public String start(String host, int port) throws RemoteException + { + String name = inputStream.getClass().getName() + UUID.randomUUID(); + rmiServiceExporter = new RmiServiceExporter(); + rmiServiceExporter.setServiceName(name); + rmiServiceExporter.setRegistryPort(port); + rmiServiceExporter.setRegistryHost(host); + rmiServiceExporter.setServiceInterface(RemoteInputStreamServer.class); + rmiServiceExporter.setService(this); + rmiServiceExporter.afterPropertiesSet(); + return name; + } + + /** + * Closes the stream and the RMI connection to the peer. + */ + public void close() throws IOException + { + try + { + inputStream.close(); + } + finally + { + if (rmiServiceExporter != null) + { + try + { + rmiServiceExporter.destroy(); + } + catch (Throwable e) + { + throw new IOException(e.getMessage(), e); + } + } + } + } + + /** + * Utility method to lookup a remote stream peer over RMI. + */ + public static RemoteInputStreamServer obtain(String host, int port, String name) throws RemoteException + { + RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean(); + rmiProxyFactoryBean.setServiceUrl("rmi://" + host + ":" + port + "/" + name); + rmiProxyFactoryBean.setServiceInterface(RemoteInputStreamServer.class); + rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true); + try + { + rmiProxyFactoryBean.afterPropertiesSet(); + } + catch (Exception e) + { + throw new RemoteException("Error create rmi proxy"); + } + return (RemoteInputStreamServer) rmiProxyFactoryBean.getObject(); + } +}