diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index e416e5f22d..a6e95dd079 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -414,12 +414,30 @@ + org.alfresco.repo.imap.config.ImapConfigMountPointsBean + + org.alfresco.repo.imap.config.ImapConfigBean + + + + + + ImapService + + + + org.alfresco.repo.imap.ImapService + + + + + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 82670e0520..2c2a8237b2 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -437,6 +437,10 @@ class="org.alfresco.repo.content.transform.MailContentTransformer" parent="baseContentTransformer" /> + + diff --git a/config/alfresco/extension/imap-bootsrap-context.xml.sample b/config/alfresco/extension/imap-bootsrap-context.xml.sample deleted file mode 100755 index 307e8f2996..0000000000 --- a/config/alfresco/extension/imap-bootsrap-context.xml.sample +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - patch.imapFolders - patch.imapFolders.description - 0 - ${version.schema} - 10000 - - - - - - - - - - alfresco/templates/imap/imap_config_space.acp - alfresco/templates/imap/email_actions_space.acp - alfresco/templates/imap/command_processor_scripts.acp - - - - patch.imapUserFolders - patch.imapUserFolders.description - 0 - ${version.schema} - 10000 - - - - - - - - - - \ No newline at end of file diff --git a/config/alfresco/imap/scripts/command-processor.js b/config/alfresco/imap/scripts/command-processor.js index 963f147c10..48844f53ec 100755 --- a/config/alfresco/imap/scripts/command-processor.js +++ b/config/alfresco/imap/scripts/command-processor.js @@ -44,7 +44,7 @@ function processCommand() { var message = "Unknown command: " + command; logger.log(message); - createEmail(message, message, message, false); + createEmail(message, message, message); return; } @@ -54,7 +54,7 @@ function processCommand() if (commandFolder == null) { var message = "Command Processor: wrong command=" + command; - createEmail(message, message, message, false); + createEmail(message, message, message); logger.log(message); return; } diff --git a/config/alfresco/imap/scripts/command-search.js b/config/alfresco/imap/scripts/command-search.js index 5ffecaa983..2f273d3eaa 100755 --- a/config/alfresco/imap/scripts/command-search.js +++ b/config/alfresco/imap/scripts/command-search.js @@ -75,7 +75,17 @@ function createResponseTextHtml(nodes) */ function createContentTextHtml(nodes) { - var content = "Command: " + title + "\n

\n"; + var content ="" + + "" + + "" + + "" + + "" + + "" + + "
" + "Command: " + title + "\n

\n"; content += "\n"; content += ""; content += ""; @@ -83,7 +93,7 @@ function createContentTextHtml(nodes) content += ""; content += ""; content += ""; - content += "\n" + content += "\n" + ""; for (var i = 0; i < nodes.length; i++) @@ -133,7 +143,7 @@ function commandSearch(params) if (query == null) { - createEmail(errorParameter, errorParameter, errorParameter, false); + createEmail(errorParameter, errorParameter, errorParameter); return; } @@ -154,7 +164,7 @@ function commandSearch(params) } else { - createEmail(errorXPathNotValid, errorXPathNotValid, errorXPathNotValid, false); + createEmail(errorXPathNotValid, errorXPathNotValid, errorXPathNotValid); return; } break; @@ -180,8 +190,8 @@ function commandSearch(params) createEmail(message, message, subject); return; } - /*createEmail(createResponseTextHtml(nodes), createContentTextPlain(nodes), subject, true);*/ - createEmail(createContentTextHtml(nodes), createContentTextPlain(nodes), subject, false); + /*createEmail(createContentTextPlain(nodes), createResponseTextHtml(nodes), subject);*/ + createEmail(createContentTextPlain(nodes), createContentTextHtml(nodes), subject); } /** * Decode subject @@ -224,13 +234,13 @@ function main() else { var message = unknownCommand + ": '" + title + "'"; - createEmail(message, message, message, false); + createEmail(message, message, message); } } else { var message = unknownCommand + ": '" + title + "'"; - createEmail(message, message, message, false); + createEmail(message, message, message); } document.remove(); diff --git a/config/alfresco/imap/scripts/command-utils.js b/config/alfresco/imap/scripts/command-utils.js index 64039946f5..513c394127 100755 --- a/config/alfresco/imap/scripts/command-utils.js +++ b/config/alfresco/imap/scripts/command-utils.js @@ -1,9 +1,8 @@ /** * Create e-mail -* contentTextHtml (string) html content -* contentTextPlain (string) text content +* contentEML (string) content message */ -function createEmail(contentTextHtml, contentTextPlain, subject, templateUsed) +function createEmail(messageTXT, messageHTML, subject) { var command = document.properties["cm:title"]; var userName = person.properties["cm:userName"]; @@ -15,42 +14,42 @@ function createEmail(contentTextHtml, contentTextPlain, subject, templateUsed) return; } - var nextMessageUID = inboxFolder.properties["imap:nextMessageUID"]; - inboxFolder.properties["imap:nextMessageUID"] = nextMessageUID + 1; - inboxFolder.save(); - - var response = inboxFolder.createNode("response" + Date.now(), "imap:imapContent"); + var response = inboxFolder.createNode("response" + Date.now() + ".eml", "cm:content"); response.properties["imap:messageFrom"] = "command@alfresco.com"; response.properties["imap:messageSubject"] = subject; response.properties["imap:messageTo"] = document.properties["cm:originator"]; response.properties["imap:messageCc"] = ""; - response.properties["imap:messageUID"] = nextMessageUID; + response.addAspect("imap:imapContent", null); + response.content = createRFC822Message("command@alfresco.com", document.properties["cm:originator"], subject, messageTXT, messageHTML); response.save(); - - var textBody = response.createNode("Body.txt", "imap:imapBody"); - textBody.content = contentTextPlain; - textBody.save(); - - var htmlBody = response.createNode("Body.html", "imap:imapBody"); - if (templateUsed == true) - { - htmlBody.content = contentTextHtml; } - else - { - htmlBody.content = "" + - "" + - "" + - "" + - "" + - "" + - "
" + contentTextHtml + "
"; - } - htmlBody.save(); +function createRFC822Message(from, to, subject, textPart, htmlPart) +{ + var id = new Number(Date.now()).toString(16); + var boundary = "----------" + id; + var date = new Date().toGMTString(); + var messageHeaders = "MIME-Version: 1.0\r\n" + + "Date: " + date + "\r\n" + + "From: " + from + "\r\n" + + "To: " + to + "\r\n" + + "Subject: " + subject + "\r\n" + + "Message-ID: " + id + "\r\n" + + "X-Priority: 3 (Normal)\r\n" + + "Content-Type: multipart/alternative; boundary=\"" + boundary + "\"\r\n\r\n"; + var messageBody = ""; + messageBody += messageHeaders; + messageBody += "--" + boundary + "\r\n"; + messageBody += "Content-Type: text/plain; charset=utf-8\r\n"; + //TODO Content-Transfer-Encoding + messageBody += "\r\n"; + messageBody += textPart + "\r\n\r\n"; + messageBody += "--" + boundary + "\r\n"; + messageBody += "Content-Type: text/html; charset=utf-8\r\n"; + //TODO Content-Transfer-Encoding + messageBody += "\r\n"; + messageBody += htmlPart + "\r\n\r\n"; + messageBody += "--" + boundary + "--\r\n\r\n"; + return messageBody; } \ No newline at end of file diff --git a/config/alfresco/messages/imap-service.properties b/config/alfresco/messages/imap-service.properties index fdfd12856a..4eeec7fd8e 100755 --- a/config/alfresco/messages/imap-service.properties +++ b/config/alfresco/messages/imap-service.properties @@ -7,4 +7,8 @@ imap.server.info.message_body_not_found = "The message body parts are not found. # Error messages. prefix 'imap.server.error' imap.server.error.properties_dont_exist = "Appropriate properties do not exist." +imap.server.error.permission_denied = "Cannot create folder - Permission denied." +imap.server.error.folder_already_exist = "Folder already exists." +imap.server.error.mailbox_name_is_mandatory = "Mailbox name is mandatory parameter." +imap.server.error.cannot_get_a_folder = "Cannot get a folder with name ''{0}''." diff --git a/config/alfresco/mimetype/mimetype-map.xml b/config/alfresco/mimetype/mimetype-map.xml index 1e63fbff57..7837cb7214 100644 --- a/config/alfresco/mimetype/mimetype-map.xml +++ b/config/alfresco/mimetype/mimetype-map.xml @@ -353,6 +353,9 @@ dwt + eml + + msg diff --git a/config/alfresco/model/imapModel.xml b/config/alfresco/model/imapModel.xml index ee93e810b2..f52ee75b8e 100755 --- a/config/alfresco/model/imapModel.xml +++ b/config/alfresco/model/imapModel.xml @@ -19,7 +19,7 @@ IMAP Folder cm:folder - + d:boolean @@ -30,29 +30,6 @@ - - IMAP File - cm:folder - - - - d:text - - - d:text - - - d:text - - - d:text - - - - imap:flaggable - - - Attachment to the IMAP message cm:content @@ -69,11 +46,63 @@ - - - + + + IMAP File + imap:flaggable + + + + d:text + + + d:text + + + d:text + + + d:text + + + d:text + + + d:text + + + + + Attachment + + false + false + + + cm:cmobject + false + true + + + + Attachments Folder + + false + false + + + cm:cmobject + false + false + + + + + + + - d:boolean + d:boolean d:boolean @@ -90,11 +119,10 @@ d:boolean - - - - + + + \ No newline at end of file diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 9c25c30fda..e141bf8f53 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1808,4 +1808,40 @@ + + patch.imapFolders + patch.imapFolders.description + 0 + 2012 + 2013 + + + + + + + + + + alfresco/templates/imap/imap_config_space.acp + alfresco/templates/imap/email_actions_space.acp + alfresco/templates/imap/command_processor_scripts.acp + + + + patch.imapUserFolders + patch.imapUserFolders.description + 0 + 2012 + 2013 + + + + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index efc9ddd4c0..1dbf8cfb7b 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -338,14 +338,22 @@ nfs.user.mappings.default.gid=0 # IMAP imap.server.enabled=false imap.server.port=143 +imap.server.host=localhost +imap.server.attachments.extraction.enabled=true # Default IMAP mount points -imap.server.mountPoints=Repository_virtual,Repository_archive +imap.server.mountPoints=Repository_virtual,Repository_archive,Repository_mixed imap.server.mountPoints.default.store=${spaces.store} imap.server.mountPoints.default.rootPath=/${spaces.company_home.childname} imap.server.mountPoints.default.mode=virtual imap.server.mountPoints.value.Repository_virtual.mode=virtual imap.server.mountPoints.value.Repository_archive.mode=archive +imap.server.mountPoints.value.Repository_mixed.mode=mixed + +# Folders that will be excluded from the automatic extraction capability +#imap.ignore.extraction=adminInbox +#imap.ignore.extraction.value.adminInbox.store=${spaces.store} +#imap.ignore.extraction.value.adminInbox.rootPath=/${spaces.company_home.childname}/imap:imap_home/cm:admin/cm:INBOX # Activity feed max size and max age (eg. 44640 mins = 31 days) activities.feed.max.size=100 diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml index 471428e36c..3e39ce89db 100755 --- a/config/alfresco/subsystems/imap/default/imap-server-context.xml +++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml @@ -11,41 +11,10 @@ - - - - - - - - - - - - - - - - - - - ${mail.from.default} - - - - ${web.application.context.url} - - - - ${spaces.store}/${spaces.company_home.childname}/${spaces.imap_home.childname} - - - - ${spaces.store}/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.imapConfig.childname}/${spaces.imap_templates.childname} - - - + + ${imap.server.host} + ${imap.server.port} @@ -58,16 +27,13 @@ - - - - + virtual @@ -78,7 +44,7 @@ /${spaces.company_home.childname} - + archive @@ -93,17 +59,101 @@ + + + + + + + ${spaces.store} + + + /${spaces.company_home.childname} + + + + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + ${server.transaction.mode.default} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + + + + + + + + ${spaces.store}/${spaces.company_home.childname}/${spaces.imap_home.childname} + + + ${mail.from.default} + + + ${web.application.context.url} + + + ${spaces.store}/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.imapConfig.childname}/${spaces.imap_templates.childname} + + + ${imap.server.attachments.extraction.enabled} + + + + + + + + + org.alfresco.repo.imap.ImapService + + + + + + + + + + + + + - - - - + + + + + + - - - diff --git a/config/alfresco/subsystems/imap/default/imap-server.properties b/config/alfresco/subsystems/imap/default/imap-server.properties index 42d301e12e..d7b69af91e 100755 --- a/config/alfresco/subsystems/imap/default/imap-server.properties +++ b/config/alfresco/subsystems/imap/default/imap-server.properties @@ -1,4 +1,5 @@ imap.server.enabled=false +imap.server.host=localhost imap.server.port=143 #imap.server.web.application.context.url=http://localhost:8080/alfresco diff --git a/config/alfresco/templates/imap/command_processor_scripts.acp b/config/alfresco/templates/imap/command_processor_scripts.acp index dda4f430b3..9ed2769b74 100755 Binary files a/config/alfresco/templates/imap/command_processor_scripts.acp and b/config/alfresco/templates/imap/command_processor_scripts.acp differ diff --git a/config/alfresco/templates/imap/imap_config_space.acp b/config/alfresco/templates/imap/imap_config_space.acp index d2dee2323c..31b4c744ee 100755 Binary files a/config/alfresco/templates/imap/imap_config_space.acp and b/config/alfresco/templates/imap/imap_config_space.acp differ diff --git a/config/alfresco/templates/imap/imap_message_text_html.ftl b/config/alfresco/templates/imap/imap_message_text_html.ftl index 5af0c44148..41c0433af7 100755 --- a/config/alfresco/templates/imap/imap_message_text_html.ftl +++ b/config/alfresco/templates/imap/imap_message_text_html.ftl @@ -84,7 +84,7 @@ Content links
UrlDownload Url
- + @@ -101,7 +101,7 @@ Start Workflow diff --git a/config/alfresco/templates/imap/imap_message_text_plain.ftl b/config/alfresco/templates/imap/imap_message_text_plain.ftl index b8edc2195e..9bd374f831 100755 --- a/config/alfresco/templates/imap/imap_message_text_plain.ftl +++ b/config/alfresco/templates/imap/imap_message_text_plain.ftl @@ -21,7 +21,7 @@ Size: ${document.size / 1024} Kb CONTENT LINKS -Content folder: ${contextUrl}${document.displayPath} +Content folder: ${contextUrl}/navigate/browse${document.displayPath} Content URL: ${contextUrl}${document.url} Download URL: ${contextUrl}${document.downloadUrl} WebDAV URL: ${contextUrl}${document.webdavUrl} diff --git a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java index 152d302634..e82d4a00f3 100644 --- a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java index 91c344c1a5..02fc47edda 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 @@ -49,6 +49,8 @@ import org.apache.commons.logging.LogFactory; /** * Concrete representation of an email message as implemented for the SubEtha mail server. * + * @author Mike Shavnev + * * @since 2.2 */ public class SubethaEmailMessage implements EmailMessage diff --git a/source/java/org/alfresco/model/ImapModel.java b/source/java/org/alfresco/model/ImapModel.java index f918e53452..3dfce73713 100755 --- a/source/java/org/alfresco/model/ImapModel.java +++ b/source/java/org/alfresco/model/ImapModel.java @@ -35,14 +35,18 @@ public interface ImapModel { static final String IMAP_MODEL_1_0_URI = "http://www.alfresco.org/model/imap/1.0"; - static final QName ASPECT_IMAP_FOLDER_SUBSCRIBED = QName.createQName(IMAP_MODEL_1_0_URI, "subscribed"); + static final QName ASPECT_IMAP_FOLDER_NONSUBSCRIBED = QName.createQName(IMAP_MODEL_1_0_URI, "nonSubscribed"); static final QName ASPECT_IMAP_FOLDER_NONSELECTABLE = QName.createQName(IMAP_MODEL_1_0_URI, "nonselectable"); - static final QName TYPE_IMAP_CONTENT = QName.createQName(IMAP_MODEL_1_0_URI, "imapContent"); + static final QName ASPECT_IMAP_CONTENT = QName.createQName(IMAP_MODEL_1_0_URI, "imapContent"); static final QName PROP_MESSAGE_FROM = QName.createQName(IMAP_MODEL_1_0_URI, "messageFrom"); static final QName PROP_MESSAGE_TO = QName.createQName(IMAP_MODEL_1_0_URI, "messageTo"); static final QName PROP_MESSAGE_CC = QName.createQName(IMAP_MODEL_1_0_URI, "messageCc"); static final QName PROP_MESSAGE_SUBJECT = QName.createQName(IMAP_MODEL_1_0_URI, "messageSubject"); + static final QName PROP_MESSAGE_ID = QName.createQName(IMAP_MODEL_1_0_URI, "messageId"); + static final QName PROP_THREAD_INDEX = QName.createQName(IMAP_MODEL_1_0_URI, "threadIndex"); + static final QName ASSOC_IMAP_ATTACHMENT = QName.createQName(IMAP_MODEL_1_0_URI, "attachment"); + static final QName ASSOC_IMAP_ATTACHMENTS_FOLDER = QName.createQName(IMAP_MODEL_1_0_URI, "attachmentsFolder"); static final QName ASPECT_FLAGGABLE = QName.createQName(IMAP_MODEL_1_0_URI, "flaggable"); static final QName PROP_FLAG_ANSWERED = QName.createQName(IMAP_MODEL_1_0_URI, "flagAnswered"); diff --git a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java index e82dd044a2..4d09fbc653 100644 --- a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java index 787d4c7a7f..18043d0dc0 100755 --- a/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java @@ -41,6 +41,7 @@ import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.springframework.context.MessageSource; import org.springframework.core.io.ClassPathResource; + /** * Builds folders tree necessary for IMAP functionality and imports email action scripts. * @@ -176,6 +177,7 @@ public class ImapFoldersPatch extends AbstractPatch throw new PatchException("XPath returned too many results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath + "\n" + " results: " + nodeRefs); } this.dictionaryNodeRef = nodeRefs.get(0); + sb.append("/").append(scriptsChildName); xpath = sb.toString(); nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); @@ -188,6 +190,7 @@ public class ImapFoldersPatch extends AbstractPatch throw new PatchException("XPath returned too many results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath + "\n" + " results: " + nodeRefs); } this.scriptsNodeRef = nodeRefs.get(0); + // get the ImapConfig node sb.delete((sb.length() - (scriptsChildName.length() + 1)), sb.length()); sb.append("/").append(imapConfigChildName); diff --git a/source/java/org/alfresco/repo/content/MimetypeMap.java b/source/java/org/alfresco/repo/content/MimetypeMap.java index ec80768e98..2d33dc2f82 100644 --- a/source/java/org/alfresco/repo/content/MimetypeMap.java +++ b/source/java/org/alfresco/repo/content/MimetypeMap.java @@ -79,6 +79,7 @@ public class MimetypeMap implements MimetypeService public static final String MIMETYPE_ATOM = "application/atom+xml"; public static final String MIMETYPE_RSS = "application/rss+xml"; public static final String MIMETYPE_RFC822 = "message/rfc822"; + public static final String MIMETYPE_OUTLOOK_MSG = "application/vnd.ms-outlook"; // Open Document public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text"; public static final String MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE = "application/vnd.oasis.opendocument.text-template"; diff --git a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java new file mode 100755 index 0000000000..c0edd16798 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2009 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.repo.content.transform; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import javax.mail.Multipart; +import javax.mail.Part; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptions; + +public class EMLTransformer extends AbstractContentTransformer2 +{ + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + if (!MimetypeMap.MIMETYPE_RFC822.equals(sourceMimetype) || !MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype)) + { + // only support RFC822 -> TEXT + return false; + } + else + { + return true; + } + } + + @Override + protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception + { + InputStream is = null; + try + { + is = reader.getContentInputStream(); + + MimeMessage mimeMessage = new MimeMessage(Session.getDefaultInstance(new Properties()), is); + + final StringBuilder sb = new StringBuilder(); + Object content = mimeMessage.getContent(); + if (content instanceof Multipart) + { + Multipart multipart = (Multipart) content; + Part part = multipart.getBodyPart(0); + + if (part.getContent() instanceof Multipart) + { + multipart = (Multipart) part.getContent(); + for (int i = 0, n = multipart.getCount(); i < n; i++) + { + part = multipart.getBodyPart(i); + if (part.getContentType().contains("text")) + { + sb.append(part.getContent().toString()).append("\n"); + + } + + } + + } + else if (part.getContentType().contains("text")) + { + sb.append(part.getContent().toString()); + + } + + } + else + { + sb.append(content.toString()); + } + + writer.putContent(sb.toString()); + } + finally + { + if (is != null) + { + try + { + is.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + } + +} diff --git a/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java b/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java index d49e284eec..4edad6fe64 100644 --- a/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java @@ -56,7 +56,7 @@ public class MailContentTransformer extends AbstractContentTransformer2 */ public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) { - if (!MimetypeMap.MIMETYPE_RFC822.equals(sourceMimetype) || + if (!MimetypeMap.MIMETYPE_OUTLOOK_MSG.equals(sourceMimetype) || !MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype)) { // only support MSG -> TEXT diff --git a/source/java/org/alfresco/repo/imap/AbstractImapFolder.java b/source/java/org/alfresco/repo/imap/AbstractImapFolder.java new file mode 100755 index 0000000000..8d35173b69 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AbstractImapFolder.java @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import javax.mail.Flags; +import javax.mail.internet.MimeMessage; +import javax.mail.search.SearchTerm; + +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.icegreen.greenmail.foedus.util.MsgRangeFilter; +import com.icegreen.greenmail.mail.MovingMessage; +import com.icegreen.greenmail.store.FolderException; +import com.icegreen.greenmail.store.FolderListener; +import com.icegreen.greenmail.store.MailFolder; +import com.icegreen.greenmail.store.SimpleStoredMessage; + +/** + * Implementation of greenmail MailFolder. It represents an Alfresco content folder and handles appendMessage, copyMessage, expunge (delete), getMessages, getMessage and so + * requests. + * + * @author Ivan Rybnikov + */ +public abstract class AbstractImapFolder implements MailFolder +{ + + private static Log logger = LogFactory.getLog(AbstractImapFolder.class); + + private List listeners = new LinkedList(); + + protected ServiceRegistry serviceRegistry; + protected static int MAX_RETRIES = 1; + + + public AbstractImapFolder(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * Method that checks mandatory parameter. + * @param The parameter instance to check. + * @param The name of the parameter. + */ + protected void checkParameter(Object parameter, String name) + { + if (parameter == null) + { + throw new IllegalArgumentException(name + " parameter is null."); + } + } + + + /** + * Appends message to the folder. + * + * @param message - message. + * @param flags - message flags. + * @param internalDate - not used. Current date used instead. + * @return + */ + public long appendMessage(final MimeMessage message, final Flags flags, final Date internalDate) throws FolderException + { + if (isReadOnly()) + { + throw new FolderException("Can't append message - Permission denied"); + } + + CommandCallback command = new CommandCallback() + { + public Long command() throws Throwable + { + return appendMessageInternal(message, flags, internalDate); + } + }; + return command.runFeedback(); + } + + + /** + * Copies message with the given UID to the specified {@link MailFolder}. + * + * @param uid - UID of the message + * @param toFolder - reference to the destination folder. + */ + public void copyMessage(final long uid, final MailFolder toFolder) throws FolderException + { + AbstractImapFolder toImapMailFolder = (AbstractImapFolder) toFolder; + + if (toImapMailFolder.isReadOnly()) + { + throw new FolderException("Can't create folder - Permission denied"); + } + + CommandCallback command = new CommandCallback() + { + public Object command() throws Throwable + { + copyMessageInternal(uid, toFolder); + return null; + } + }; + command.runFeedback(); + } + + /** + * Marks all messages in the folder as deleted using {@link Flags.Flag#DELETED} flag. + */ + public void deleteAllMessages() throws FolderException + { + CommandCallback command = new CommandCallback() + { + public Object command() throws Throwable + { + deleteAllMessagesInternal(); + return null; + } + }; + command.runFeedback(); + } + + + + /** + * Deletes messages marked with {@link Flags.Flag#DELETED}. Note that this message deletes all messages with this flag. + */ + public void expunge() throws FolderException + { + if (isReadOnly()) + { + throw new FolderException("Can't expunge - Permission denied"); + } + CommandCallback command = new CommandCallback() + { + public Object command() throws Throwable + { + expungeInternal(); + return null; + } + }; + command.runFeedback(); + } + + + /** + * Returns the number of the first unseen message. + * + * @return Number of the first unseen message. + */ + public int getFirstUnseen() + { + return getFirstUnseenInternal(); + } + + + /** + * Returns full name of the folder with namespace and full path delimited with the hierarchy delimiter (see {@link AlfrescoImapConst#HIERARCHY_DELIMITER})

E.g.:

+ * #mail.admin."Repository_archive.Data Dictionary.Space Templates.Software Engineering Project"

This is required by GreenMail implementation. + */ + public String getFullName() + { + CommandCallback command = new CommandCallback() + { + public String command() throws Throwable + { + return getFullNameInternal(); + } + }; + return command.run(); + } + + + /** + * Returns message by its UID. + * + * @param uid - UID of the message. + * @return message. + */ + public SimpleStoredMessage getMessage(final long uid) + { + CommandCallback command = new CommandCallback() + { + public SimpleStoredMessage command() throws Throwable + { + return getMessageInternal(uid); + } + }; + return command.run(); + } + + /** + * Returns count of the messages in the folder. + * + * @return Count of the messages. + */ + public int getMessageCount() + { + CommandCallback command = new CommandCallback() + { + public Integer command() throws Throwable + { + return getMessageCountInternal(); + } + }; + return command.run(); + } + + /** + * Returns list of all messages in the folder. + * + * @return list of {@link SimpleStoredMessage} objects. + */ + public List getMessages() + { + CommandCallback> command = new CommandCallback>() + { + public List command() throws Throwable + { + return getMessagesInternal(); + } + }; + return command.run(); + } + + /** + * Returns list of messages by filter. + * + * @param msgRangeFilter - {@link MsgRangeFilter} object representing filter. + * @return list of filtered messages. + */ + public List getMessages(final MsgRangeFilter msgRangeFilter) + { + CommandCallback > command = new CommandCallback >() + { + public List command() throws Throwable + { + return getMessagesInternal(msgRangeFilter); + } + }; + return command.run(); + } + + /** + * Returns message sequence number in the folder by its UID. + * + * @param uid - message UID. + * @return message sequence number. + * @throws FolderException if no message with given UID. + */ + public int getMsn(final long uid) throws FolderException + { + CommandCallback command = new CommandCallback() + { + public Integer command() throws Throwable + { + return getMsnInternal(uid); + } + }; + return command.runFeedback(true); + } + + /** + * Returns folder name. + * + * @return folder name. + */ + public String getName() + { + CommandCallback command = new CommandCallback() + { + public String command() throws Throwable + { + return getNameInternal(); + } + }; + return command.run(); + } + + /** + * Returns UIDs of all messages in the folder. + * + * @return UIDS of the messages. + */ + public long[] getMessageUids() + { + CommandCallback command = new CommandCallback() + { + public Object command() throws Throwable + { + return getMessageUidsInternal(); + } + }; + return (long[])command.run(); + } + + /** + * Returns the list of messages that have no {@link Flags.Flag#DELETED} flag set for current user. + * + * @return the list of non-deleted messages. + */ + public List getNonDeletedMessages() + { + CommandCallback > command = new CommandCallback>() + { + public List command() throws Throwable + { + return getNonDeletedMessagesInternal(); + } + }; + List result = (List)command.run(); + return result; + } + + /** + * Returns permanent flags. + * + * @return {@link Flags} object containing flags. + */ + public Flags getPermanentFlags() + { + CommandCallback command = new CommandCallback() + { + public Flags command() throws Throwable + { + return getPermanentFlagsInternal(); + } + }; + return command.run(true); + } + + /** + * Returns count of messages with {@link Flags.Flag#RECENT} flag. If {@code reset} parameter is {@code true} - removes {@link Flags.Flag#RECENT} flag from the message for + * current user. + * + * @param reset - if true the {@link Flags.Flag#RECENT} will be deleted for current user if exists. + * @return returns count of recent messages. + */ + public int getRecentCount(final boolean reset) + { + CommandCallback command = new CommandCallback() + { + public Integer command() throws Throwable + { + return getRecentCountInternal(reset); + } + }; + return command.run(true); + } + + /** + * Returns UIDNEXT value of the folder. + * + * @return UIDNEXT value. + */ + public long getUidNext() + { + CommandCallback command = new CommandCallback() + { + public Long command() throws Throwable + { + return getUidNextInternal(); + } + }; + return command.run(true); + } + + /** + * Returns UIDVALIDITY value of the folder. + * + * @return UIDVALIDITY value. + */ + public long getUidValidity() + { + CommandCallback command = new CommandCallback() + { + public Long command() throws Throwable + { + return getUidValidityInternal(); + } + }; + return command.run(true); + } + + /** + * Returns count of the messages with {@link Flags.Flag#SEEN} in the folder for the current user. + * + * @return Count of the unseen messages for current user. + */ + public int getUnseenCount() + { + CommandCallback command = new CommandCallback() + { + public Integer command() throws Throwable + { + return getUnseenCountInternal(); + } + }; + return command.run(); + } + + /** + * Whether the folder is selectable. + * + * @return {@code boolean}. + */ + public boolean isSelectable() + { + CommandCallback command = new CommandCallback() + { + public Boolean command() throws Throwable + { + return isSelectableInternal(); + } + }; + return command.run(true); + } + + /** + * Replaces flags for the message with the given UID. If {@code addUid} is set to {@code true} {@link FolderListener} objects defined for this folder will be notified. + * {@code silentListener} can be provided - this listener wouldn't be notified. + * + * @param flags - new flags. + * @param uid - message UID. + * @param silentListener - listener that shouldn't be notified. + * @param addUid - defines whether or not listeners be notified. + */ + public void replaceFlags(final Flags flags, final long uid, final FolderListener silentListener, final boolean addUid) throws FolderException + { + CommandCallback command = new CommandCallback() + { + public Object command() throws Throwable + { + replaceFlagsInternal(flags, uid, silentListener, addUid); + return null; + } + }; + command.runFeedback(true); + } + + + /** + * Simply returns UIDs of all messages in the folder. + * + * @param searchTerm - not used + * @return UIDs of the messages + */ + public long[] search(SearchTerm searchTerm) + { + return getMessageUids(); + } + + /** + * Sets flags for the message with the given UID. If {@code addUid} is set to {@code true} {@link FolderListener} objects defined for this folder will be notified. + * {@code silentListener} can be provided - this listener wouldn't be notified. + * + * @param flags - new flags. + * @param value - flags value. + * @param uid - message UID. + * @param silentListener - listener that shouldn't be notified. + * @param addUid - defines whether or not listeners be notified. + */ + public void setFlags(final Flags flags, final boolean value, final long uid, final FolderListener silentListener, final boolean addUid) throws FolderException + { + CommandCallback command = new CommandCallback() + { + public Object command() throws Throwable + { + setFlagsInternal(flags, value, uid, silentListener, addUid); + return null; + } + }; + command.runFeedback(); + } + + + + /** + * Not supported. Added to implement {@link MailFolder#store(MovingMessage)}. + */ + public void store(MovingMessage mail) throws Exception + { + throw new UnsupportedOperationException("Method store(MovingMessage) is not suppoted."); + } + + /** + * Not supported. Added to implement {@link MailFolder#store(MimeMessage)}. + */ + public void store(MimeMessage message) throws Exception + { + throw new UnsupportedOperationException("Method store(MimeMessage) is not suppoted."); + } + + + /** + * Adds {@link FolderListener} to the folder. + * + * @param listener - new listener. + */ + public void addListener(FolderListener listener) + { + listeners.add(listener); + } + + + + /** + * Removes {@link FolderListener} from the folder. + * + * @param listener - Listener to remove. + */ + public void removeListener(FolderListener listener) + { + listeners.remove(listener); + } + + /** + * Method is called before the deletion of the folder. Notifies {@link FolderListener} objects with {@link FolderListener#mailboxDeleted()} method calls. + */ + public void signalDeletion() + { + synchronized (listeners) + { + for (int i = 0; i < listeners.size(); i++) + { + FolderListener listener = (FolderListener) listeners.get(i); + listener.mailboxDeleted(); + } + } + } + + + protected void notifyFlagUpdate(int msn, Flags flags, Long uidNotification, FolderListener silentListener) + { + synchronized (listeners) + { + for (FolderListener listener : listeners) + { + if (listener == silentListener) + { + continue; + } + + listener.flagsUpdated(msn, flags, uidNotification); + } + } + } + + + protected abstract boolean isReadOnly(); + + protected abstract long appendMessageInternal(MimeMessage message, Flags flags, Date internalDate) throws Exception; + + protected abstract void copyMessageInternal(long uid, MailFolder toFolder) throws Exception; + + protected abstract void deleteAllMessagesInternal() throws Exception; + + protected abstract void expungeInternal() throws Exception; + + protected abstract int getFirstUnseenInternal(); + + protected abstract String getFullNameInternal() throws Exception; + + protected abstract SimpleStoredMessage getMessageInternal(long uid) throws Exception; + + protected abstract int getMessageCountInternal(); + + protected abstract List getMessagesInternal(); + + protected abstract List getMessagesInternal(MsgRangeFilter msgRangeFilter); + + protected abstract int getMsnInternal(long uid) throws Exception; + + protected abstract String getNameInternal(); + + protected abstract long[] getMessageUidsInternal(); + + protected abstract List getNonDeletedMessagesInternal(); + + protected abstract Flags getPermanentFlagsInternal(); + + protected abstract int getRecentCountInternal(boolean reset); + + protected abstract long getUidNextInternal(); + + protected abstract long getUidValidityInternal(); + + protected abstract int getUnseenCountInternal(); + + protected abstract boolean isSelectableInternal(); + + protected abstract void replaceFlagsInternal(Flags flags, long uid, FolderListener silentListener, boolean addUid) throws Exception; + + protected abstract void setFlagsInternal(Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) throws Exception; + + + + + protected abstract class CommandCallback + { + public abstract T command() throws Throwable; + + public T runFeedback() throws FolderException + { + return this.runFeedback(false); + } + + public T runFeedback(boolean readOnly) throws FolderException + { + try + { + RetryingTransactionHelper txHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper(); + txHelper.setMaxRetries(MAX_RETRIES); + txHelper.setReadOnly(readOnly); + T result = txHelper.doInTransaction(new RetryingTransactionCallback() { + public T execute() throws Throwable + { + return command(); + } + }, readOnly); + + return result; + } + catch (Exception e) + { + Throwable cause = e.getCause(); + String message; + if (cause != null) + { + message = cause.getMessage(); + } + else + { + message = e.getMessage(); + } + throw new FolderException(message); + } + } + + + public T run() + { + return this.run(false); + } + + + public T run(boolean readOnly) + { + RetryingTransactionHelper txHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper(); + txHelper.setMaxRetries(MAX_RETRIES); + txHelper.setReadOnly(readOnly); + T result = txHelper.doInTransaction(new RetryingTransactionCallback() { + public T execute() throws Throwable + { + return command(); + } + }, readOnly); + + return result; + } + + } + + +} diff --git a/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java b/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java new file mode 100755 index 0000000000..a2d90ad509 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import static org.alfresco.repo.imap.AlfrescoImapConst.CLASSPATH_TEXT_HTML_TEMPLATE; +import static org.alfresco.repo.imap.AlfrescoImapConst.CLASSPATH_TEXT_PLAIN_TEMPLATE; +import static org.alfresco.repo.imap.AlfrescoImapConst.DICTIONARY_TEMPLATE_PREFIX; +import static org.alfresco.repo.imap.AlfrescoImapConst.MIME_VERSION; +import static org.alfresco.repo.imap.AlfrescoImapConst.X_ALF_NODEREF_ID; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.mail.Address; +import javax.mail.Flags; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.template.TemplateNode; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Arseny Kovalchuk + */ +public abstract class AbstractMimeMessage extends MimeMessage +{ + /** Used if imapHelper.getDefaultFromAddress is not set */ + protected static final String DEFAULT_EMAIL_FROM = "alfresco@alfresco.org"; + protected static final String DEFAULT_EMAIL_TO = DEFAULT_EMAIL_FROM; + protected static final String KOI8R_CHARSET = "koi8-r"; + + protected static int MAX_RETRIES = 1; + + private Log logger = LogFactory.getLog(AbstractMimeMessage.class); + + protected boolean generateBody = true; + + protected ServiceRegistry serviceRegistry; + protected ImapService imapService; + protected FileInfo messageFileInfo; + protected MimeMessage wrappedMessage; + + protected AbstractMimeMessage(Session session) + { + super(session); + } + + protected AbstractMimeMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry, boolean generateBody) throws MessagingException + { + super(Session.getDefaultInstance(new Properties())); + this.generateBody = generateBody; + buildMessage(fileInfo, serviceRegistry); + } + + public static enum EmailBodyType + { + TEXT_PLAIN, TEXT_HTML; + + public String getSubtype() + { + return name().toLowerCase().substring(5); + } + + public String getTypeSubtype() + { + return name().toLowerCase().replaceAll("_", ""); + } + + public String getMimeType() + { + return name().toLowerCase().replaceAll("_", "/"); + } + + } + + protected void buildMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry) throws MessagingException + { + checkParameter(serviceRegistry, "ServiceRegistry"); + this.content = null; + this.serviceRegistry = serviceRegistry; + this.imapService = serviceRegistry.getImapService(); + this.messageFileInfo = fileInfo; + RetryingTransactionHelper txHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper(); + txHelper.setMaxRetries(MAX_RETRIES); + txHelper.doInTransaction(new RetryingTransactionCallback() { + public Object execute() throws Throwable + { + buildMessageInternal(); + return null; + } + }); + } + + /** + * Method must be implemented in subclasses. It usually should be used to generate message body. + * + * @throws MessagingException + */ + public abstract void buildMessageInternal() throws MessagingException; + + /** + * Method that checks mandatory parameter. + * @param The parameter instance to check. + * @param The name of the parameter. + */ + protected void checkParameter(Object parameter, String name) + { + if (parameter == null) + { + throw new IllegalArgumentException(name + " parameter is null."); + } + } + + protected void setMessageHeaders() throws MessagingException + { + setHeader(MIME_VERSION, "1.0"); + // Optional headers for further implementation of multiple Alfresco server support. + setHeader(X_ALF_NODEREF_ID, messageFileInfo.getNodeRef().getId()); + // setHeader(X_ALF_SERVER_UID, imapService.getAlfrescoServerUID()); + } + + /** + * Builds the InternetAddress from the Content Author name if provided. If name not specified, it takes Content Creator name. If content creator does not exists, the default + * from address will be returned. + * + * @param contentAuthor The content author full name. + * @return Generated InternetAddress[] array. + * @throws AddressException + */ + protected InternetAddress[] buildSenderFromAddress() throws AddressException + { + // Generate FROM address (Content author) + InternetAddress[] addressList = null; + Map properties = messageFileInfo.getProperties(); + String prop = (String) properties.get(ContentModel.PROP_AUTHOR); + String defaultFromAddress = imapService.getDefaultFromAddress(); + defaultFromAddress = defaultFromAddress == null ? DEFAULT_EMAIL_FROM : defaultFromAddress; + try + { + + if (prop != null) + { + StringBuilder contentAuthor = new StringBuilder(); + contentAuthor.append("\"").append(prop).append("\" <").append(defaultFromAddress).append(">"); + addressList = InternetAddress.parse(contentAuthor.toString()); + } + else + { + prop = (String) properties.get(ContentModel.PROP_CREATOR); + if (prop != null) + { + StringBuilder creator = new StringBuilder(); + creator.append("\"").append(prop).append("\" <").append(defaultFromAddress).append(">"); + addressList = InternetAddress.parse(creator.toString()); + } + else + { + throw new AddressException(I18NUtil.getMessage("imap.server.error.properties_dont_exist")); + } + } + } + catch (AddressException e) + { + addressList = InternetAddress.parse(DEFAULT_EMAIL_FROM); + } + return addressList; + } + + /** + * Returns {@link FileInfo} object representing message in Alfresco. + * + * @return reference to the {@link FileInfo} object. + */ + public FileInfo getMessageInfo() + { + return messageFileInfo; + } + + /** + * Returns message flags. + * + * @return {@link Flags} + */ + @Override + public Flags getFlags() + { + return imapService.getFlags(messageFileInfo); + } + + + /** + * Sets message flags. + * + * @param flags - {@link Flags} object. + * @param value - flags value. + */ + @Override + public void setFlags(Flags flags, boolean value) throws MessagingException + { + imapService.setFlags(messageFileInfo, flags, value); + } + + /** + * Returns the text representing email body for ContentModel node. + * + * @param nodeRef NodeRef of the target content. + * @param type The type of the returned body. May be the one of {@link EmailBodyType}. + * @return Text representing email body for ContentModel node. + */ + public String getEmailBodyText(EmailBodyType type) + { + return serviceRegistry.getTemplateService().processTemplate(getDefaultEmailBodyTemplate(type), createEmailTemplateModel(messageFileInfo.getNodeRef())); + } + + /** + * TODO USE CASE 2: "The To/addressee will be the first email alias found in the parent folders or a default one (TBD)". It seems to be more informative as alike + * {@code @}... + * + * @return Generated TO address {@code @} + * @throws AddressException + */ + protected InternetAddress[] buildRecipientToAddress() throws AddressException + { + InternetAddress[] result = null; + String defaultEmailTo = null; + final String escapedUserName = AuthenticationUtil.getFullyAuthenticatedUser().replaceAll("[/,\\,@]", "."); + final String userDomain = DEFAULT_EMAIL_TO.split("@")[1]; + defaultEmailTo = escapedUserName + "@" + userDomain; + try + { + result = InternetAddress.parse(defaultEmailTo); + } + catch (AddressException e) + { + logger.error(String.format("Wrong email address '%s'.", defaultEmailTo), e); + result = InternetAddress.parse(DEFAULT_EMAIL_TO); + } + return result; + } + + protected void addFromInternal(String addressesString) throws MessagingException + { + if (addressesString != null) + { + addFrom(InternetAddress.parse(addressesString)); + } + else + { + addFrom(new Address[] { new InternetAddress(DEFAULT_EMAIL_FROM) }); + } + } + + /** + * Returns default email body template. This method trying to find a template on the path in the repository first e.g. {@code "Data Dictionary > IMAP Templates >"}. This path + * should be set as the property of the "imapHelper" bean. In this case it returns {@code NodeRef.toString()} of the template. If there are no template in the repository it + * returns a default template on the classpath. + * + * @param type One of the {@link EmailBodyType}. + * @return String representing template classpath path or NodeRef.toString(). + */ + private String getDefaultEmailBodyTemplate(EmailBodyType type) + { + String result = null; + switch (type) + { + case TEXT_HTML: + result = CLASSPATH_TEXT_HTML_TEMPLATE; + break; + case TEXT_PLAIN: + result = CLASSPATH_TEXT_PLAIN_TEMPLATE; + break; + } + final StringBuilder templateName = new StringBuilder(DICTIONARY_TEMPLATE_PREFIX).append("-").append(type.getTypeSubtype()).append(".ftl"); + final String repositoryTemplatePath = imapService.getRepositoryTemplatePath(); + int indexOfStoreDelim = repositoryTemplatePath.indexOf(StoreRef.URI_FILLER); + if (indexOfStoreDelim == -1) + { + logger.error("Bad path format, " + StoreRef.URI_FILLER + " not found"); + return result; + } + indexOfStoreDelim += StoreRef.URI_FILLER.length(); + int indexOfPathDelim = repositoryTemplatePath.indexOf("/", indexOfStoreDelim); + if (indexOfPathDelim == -1) + { + logger.error("Bad path format, / not found"); + return result; + } + final String storePath = repositoryTemplatePath.substring(0, indexOfPathDelim); + final String rootPathInStore = repositoryTemplatePath.substring(indexOfPathDelim); + final String query = String.format("+PATH:\"%1$s/*\" +@cm\\:name:\"%2$s\"", rootPathInStore, templateName.toString()); + if (logger.isDebugEnabled()) + { + logger.debug("Using template path :" + repositoryTemplatePath + "/" + templateName); + logger.debug("Query: " + query); + } + StoreRef storeRef = new StoreRef(storePath); + ResultSet resultSet = serviceRegistry.getSearchService().query(storeRef, "lucene", query); + if (resultSet == null || resultSet.length() == 0) + { + logger.error(String.format("IMAP message template '%1$s' does not exist in the path '%2$s'.", templateName, repositoryTemplatePath)); + return result; + } + result = resultSet.getNodeRef(0).toString(); + return result; + } + + /** + * Builds default email template model for TemplateProcessor + * + * @param ref NodeRef of the target content. + * @return Map that includes template model objects. + */ + private Map createEmailTemplateModel(NodeRef ref) + { + Map model = new HashMap(8, 1.0f); + TemplateNode tn = new TemplateNode(ref, serviceRegistry, null); + model.put("document", tn); + NodeRef parent = serviceRegistry.getNodeService().getPrimaryParent(ref).getParentRef(); + model.put("space", new TemplateNode(parent, serviceRegistry, null)); + model.put("date", new Date()); + model.put("contextUrl", new String(imapService.getWebApplicationContextUrl())); + model.put("alfTicket", new String(serviceRegistry.getAuthenticationService().getCurrentTicket())); + return model; + } + + + +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java b/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java index 6fa28f2150..f6507227e2 100755 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java @@ -30,7 +30,7 @@ package org.alfresco.repo.imap; public interface AlfrescoImapConst { - public static final char HIERARCHY_DELIMITER = '.'; + public static final char HIERARCHY_DELIMITER = '/'; public static final String NAMESPACE_PREFIX = "#"; public static final String USER_NAMESPACE = "#mail"; public static final String INBOX_NAME = "INBOX"; @@ -38,17 +38,23 @@ public interface AlfrescoImapConst public static final String BODY_TEXT_PLAIN_NAME = "Body.txt"; public static final String BODY_TEXT_HTML_NAME = "Body.html"; public static final String MESSAGE_PREFIX = "Message_"; + public static final String EML_EXTENSION = ".eml"; // Separator for user enties in flag and subscribe properties public static final String USER_SEPARATOR = ";"; /** - * Defines {@link AlfrescoImapMailFolder} view mode as archive mode. Used for Email Archive View. + * Defines {@link AlfrescoImapFolder} view mode as archive mode. Used for Email Archive View. */ public static final String MODE_ARCHIVE = "archive"; /** - * Defines {@link AlfrescoImapMailFolder} view mode as virtual mode. Used for IMAP Virtualised View. + * Defines {@link AlfrescoImapFolder} view mode as virtual mode. Used for IMAP Virtualised View. */ public static final String MODE_VIRTUAL = "virtual"; + /** + * Defines {@link AlfrescoImapFolder} view mode as mixed mode. Used for IMAP Mixed View. + */ + public static final String MODE_MIXED = "mixed"; + // Default content model email message templates public static final String CLASSPATH_TEXT_PLAIN_TEMPLATE = "/alfresco/templates/imap/imap_message_text_plain.ftl"; diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java similarity index 50% rename from source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java rename to source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java index 3537733ec6..631c3c3ac3 100755 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java @@ -24,58 +24,53 @@ */ package org.alfresco.repo.imap; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Serializable; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import javax.mail.BodyPart; import javax.mail.Flags; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.internet.ContentType; -import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; -import javax.mail.search.SearchTerm; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.model.ImapModel; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.ContentIOException; 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.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; -import org.apache.commons.io.IOUtils; +import org.alfresco.util.Utf7; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.FileCopyUtils; import com.icegreen.greenmail.foedus.util.MsgRangeFilter; import com.icegreen.greenmail.imap.ImapConstants; -import com.icegreen.greenmail.mail.MovingMessage; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.FolderListener; import com.icegreen.greenmail.store.MailFolder; import com.icegreen.greenmail.store.MessageFlags; import com.icegreen.greenmail.store.SimpleStoredMessage; -import com.icegreen.greenmail.util.GreenMailUtil; /** * Implementation of greenmail MailFolder. It represents an Alfresco content folder and handles appendMessage, copyMessage, expunge (delete), getMessages, getMessage and so @@ -83,10 +78,10 @@ import com.icegreen.greenmail.util.GreenMailUtil; * * @author Mike Shavnev */ -public class AlfrescoImapMailFolder implements MailFolder +public class AlfrescoImapFolder extends AbstractImapFolder { - private static Log logger = LogFactory.getLog(AlfrescoImapMailFolder.class); + private static Log logger = LogFactory.getLog(AlfrescoImapFolder.class); /** * Reference to the {@link FileInfo} object representing the folder. @@ -119,28 +114,27 @@ public class AlfrescoImapMailFolder implements MailFolder private String mountPointName; /** - * Reference to the {@link ImapHelper} object. + * Reference to the {@link ImapService} object. */ - private ImapHelper imapHelper; + private ImapService imapService; /** * Defines whether the folder is selectable or not. */ - private Boolean selectable; + private boolean selectable; /** * Defines whether the folder is read-only for user or not. */ private Boolean readOnly; - + private boolean extractAttachmentsEnabled; + private Map messages = new TreeMap(); - private boolean isBodyGenerated = false; + private Map msnCache = new HashMap(); private static final Flags PERMANENT_FLAGS = new Flags(); - private List listeners = new LinkedList(); - static { PERMANENT_FLAGS.add(Flags.Flag.ANSWERED); @@ -149,9 +143,19 @@ public class AlfrescoImapMailFolder implements MailFolder PERMANENT_FLAGS.add(Flags.Flag.FLAGGED); PERMANENT_FLAGS.add(Flags.Flag.SEEN); } + + public boolean isExtractAttachmentsEnabled() + { + return extractAttachmentsEnabled; + } + /*package*/ AlfrescoImapFolder(String qualifiedMailboxName, ServiceRegistry serviceRegistry) + { + this(qualifiedMailboxName, null, null, null, null, null, false, serviceRegistry); + } + /** - * Constructs {@link AlfrescoImapMailFolder} object. + * Constructs {@link AlfrescoImapFolder} object. * * @param qualifiedMailboxName - name of the mailbox (e.g. "admin" for admin user). * @param folderInfo - reference to the {@link FileInfo} object representing the folder. @@ -161,14 +165,14 @@ public class AlfrescoImapMailFolder implements MailFolder * @param mountPointName - name of the mount point. * @param imapHelper - reference to the {@link ImapHelper} object. */ - public AlfrescoImapMailFolder(String qualifiedMailboxName, FileInfo folderInfo, String folderName, String viewMode, NodeRef rootNodeRef, String mountPointName, - ImapHelper imapHelper) + public AlfrescoImapFolder(String qualifiedMailboxName, FileInfo folderInfo, String folderName, String viewMode, NodeRef rootNodeRef, String mountPointName, + boolean extractAttachmentsEnabled, ServiceRegistry serviceRegistry) { - this(qualifiedMailboxName, folderInfo, folderName, viewMode, rootNodeRef, mountPointName, imapHelper, null); + this(qualifiedMailboxName, folderInfo, folderName, viewMode, rootNodeRef, mountPointName, serviceRegistry, null, extractAttachmentsEnabled); } /** - * Constructs {@link AlfrescoImapMailFolder} object. + * Constructs {@link AlfrescoImapFolder} object. * * @param qualifiedMailboxName - name of the mailbox (e.g. "admin" for admin user). * @param folderInfo - reference to the {@link FileInfo} object representing the folder. @@ -176,19 +180,25 @@ public class AlfrescoImapMailFolder implements MailFolder * @param viewMode - defines view mode. Can be one of the following: {@link AlfrescoImapConst#MODE_ARCHIVE} or {@link AlfrescoImapConst#MODE_VIRTUAL}. * @param rootNodeRef - reference to the root node of the store where folder is placed. * @param mountPointName - name of the mount point. - * @param imapHelper - reference to the {@link ImapHelper} object. + * @param imapService - reference to the {@link ImapHelper} object. * @param selectable - defines whether the folder is selectable or not. */ - public AlfrescoImapMailFolder(String qualifiedMailboxName, FileInfo folderInfo, String folderName, String viewMode, NodeRef rootNodeRef, String mountPointName, - ImapHelper imapHelper, Boolean selectable) + public AlfrescoImapFolder(String qualifiedMailboxName, FileInfo folderInfo, String folderName, String viewMode, NodeRef rootNodeRef, String mountPointName, + ServiceRegistry serviceRegistry, Boolean selectable, boolean extractAttachmentsEnabled) { + super(serviceRegistry); this.qualifiedMailboxName = qualifiedMailboxName; this.folderInfo = folderInfo; this.rootNodeRef = rootNodeRef; - this.imapHelper = imapHelper; this.folderName = folderName != null ? folderName : (folderInfo != null ? folderInfo.getName() : null); this.viewMode = viewMode != null ? viewMode : AlfrescoImapConst.MODE_ARCHIVE; this.mountPointName = mountPointName; + this.extractAttachmentsEnabled = extractAttachmentsEnabled; + + if (serviceRegistry != null) + { + this.imapService = serviceRegistry.getImapService(); + } // MailFolder object can be null if it is used to obtain hierarchy delimiter by LIST command: // Example: @@ -200,7 +210,7 @@ public class AlfrescoImapMailFolder implements MailFolder if (selectable == null) { // isSelectable(); - Boolean storedSelectable = !imapHelper.getNodeService().hasAspect(folderInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE); + Boolean storedSelectable = !serviceRegistry.getNodeService().hasAspect(folderInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE); if (storedSelectable == null) { setSelectable(true); @@ -215,7 +225,7 @@ public class AlfrescoImapMailFolder implements MailFolder setSelectable(selectable); } - AccessStatus status = imapHelper.hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE); + AccessStatus status = serviceRegistry.getPermissionService().hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE); if (status == AccessStatus.DENIED) { readOnly = true; @@ -228,88 +238,11 @@ public class AlfrescoImapMailFolder implements MailFolder } else { - setSelectable(false); + setSelectable(true); } } - /** - * Adds {@link FolderListener} to the folder. - * - * @param listener - new listener. - */ - public void addListener(FolderListener listener) - { - listeners.add(listener); - - } - - protected void processTextMessage(MimeMessage message, FileInfo messageHome) throws MessagingException, ContentIOException, IOException - { - FileInfo messageBody = imapHelper.getFileFolderService().create(messageHome.getNodeRef(), AlfrescoImapConst.BODY_TEXT_PLAIN_NAME, ImapModel.TYPE_IMAP_BODY); - ContentWriter writer = imapHelper.getFileFolderService().getWriter(messageBody.getNodeRef()); - writer.setMimetype(message.getContentType()); - writer.setEncoding("UTF-8"); - writer.putContent(message.getInputStream()); - } - - protected void processTextMessage(BodyPart part, FileInfo messageHome, boolean isBody) throws MessagingException, ContentIOException, IOException - { - FileInfo messageBody = null; - ContentType ct = new ContentType(part.getContentType()); - ContentWriter writer = null; - if (isBody) - { - if ("plain".equalsIgnoreCase(ct.getSubType())) - { - messageBody = imapHelper.getFileFolderService().create(messageHome.getNodeRef(), AlfrescoImapConst.BODY_TEXT_PLAIN_NAME, ImapModel.TYPE_IMAP_BODY); - writer = imapHelper.getFileFolderService().getWriter(messageBody.getNodeRef()); - writer.setEncoding(MimeUtility.javaCharset(ct.getParameter("charset"))); - writer.setMimetype(ct.toString()); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - IOUtils.copy(part.getInputStream(), outputStream); - OutputStreamWriter outputWriter = new OutputStreamWriter(writer.getContentOutputStream()); - outputWriter.write(outputStream.toString()); - outputWriter.flush(); - outputWriter.close(); - } - else if ("html".equalsIgnoreCase(ct.getSubType())) - { - messageBody = imapHelper.getFileFolderService().create(messageHome.getNodeRef(), AlfrescoImapConst.BODY_TEXT_HTML_NAME, ImapModel.TYPE_IMAP_BODY); - writer = imapHelper.getFileFolderService().getWriter(messageBody.getNodeRef()); - writer.setMimetype(ct.toString()); - String javaCharset = MimeUtility.javaCharset(ct.getParameter("charset")); - writer.setEncoding(javaCharset); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - IOUtils.copy(part.getInputStream(), os); - OutputStreamWriter cosw = new OutputStreamWriter(writer.getContentOutputStream()); - cosw.write(os.toString()); - cosw.flush(); - cosw.close(); - } - } - else - { - saveAttachment(part, messageHome); - } - } - - protected void saveAttachment(BodyPart part, FileInfo messageHome) throws FileExistsException, MessagingException, ContentIOException, IOException - { - FileInfo messageBody = imapHelper.getFileFolderService().create(messageHome.getNodeRef(), MimeUtility.decodeText(part.getFileName()), ImapModel.TYPE_IMAP_ATTACH); - ContentWriter writer = imapHelper.getFileFolderService().getWriter(messageBody.getNodeRef()); - writer.setMimetype(part.getContentType()); - writer.setEncoding("UTF-8"); - writer.putContent(part.getInputStream()); - - String[] attachId = part.getHeader("Content-ID"); - if (attachId != null && attachId.length > 0) - { - imapHelper.getNodeService().setProperty(messageBody.getNodeRef(), ImapModel.PROP_ATTACH_ID, attachId[0]); - } - } - /** * Appends message to the folder. * @@ -317,82 +250,24 @@ public class AlfrescoImapMailFolder implements MailFolder * @param flags - message flags. * @param internalDate - not used. Current date used instead. * @return + * @throws MessagingException + * @throws IOException + * @throws FileNotFoundException + * @throws FileExistsException + * @throws MessagingException */ - public long appendMessage(MimeMessage message, Flags flags, Date internalDate) throws FolderException + @Override + protected long appendMessageInternal(MimeMessage message, Flags flags, Date internalDate) throws FileExistsException, FileNotFoundException, IOException, MessagingException { - if (this.readOnly) - { - throw new FolderException("Can't append message - Permission denied"); - } - - //TODO FILE EXIST - String name = AlfrescoImapConst.MESSAGE_PREFIX + GUID.generate(); - FileInfo messageHome = imapHelper.getFileFolderService().create(folderInfo.getNodeRef(), name, ImapModel.TYPE_IMAP_CONTENT); - final long newMessageUid = (Long) messageHome.getProperties().get(ContentModel.PROP_NODE_DBID); - - try - { - name = AlfrescoImapConst.MESSAGE_PREFIX + newMessageUid; - imapHelper.getFileFolderService().rename(messageHome.getNodeRef(), name); - - Object content = message.getContent(); - if (content instanceof Multipart) - { - Multipart multipart = (Multipart) content; - - for (int i = 0, n = multipart.getCount(); i < n; i++) - { - Part part = multipart.getBodyPart(i); - createMessageFiles(messageHome, (MimeBodyPart) part); - - } - } - else - { - processTextMessage(message, messageHome); - } - - imapHelper.setFlags(messageHome, flags, true); - SimpleStoredMessage storedMessage = new SimpleStoredMessage(new AlfrescoImapMessage(messageHome, imapHelper, message), new Date(), newMessageUid); - messages.put(newMessageUid, storedMessage); - } - catch (Exception e) - { - throw new AlfrescoRuntimeException("Internal error", e); - } + AbstractMimeMessage internalMessage = createMimeMessageInFolder(this.folderInfo, message); + long newMessageUid = (Long) internalMessage.getMessageInfo().getProperties().get(ContentModel.PROP_NODE_DBID); + SimpleStoredMessage storedMessage = new SimpleStoredMessage(internalMessage, new Date(), newMessageUid); + messages.put(newMessageUid, storedMessage); + + // Saving message sequence number to cache + msnCache.put(newMessageUid, messages.size()); return newMessageUid; - - } - - private void createMessageFiles(FileInfo messageHome, MimeBodyPart part) throws IOException, MessagingException - { - - Object content = part.getContent(); - - if (content instanceof MimeMultipart) - { - int count = ((MimeMultipart) content).getCount(); - for (int i = 0; i < count; i++) - { - createMessageFiles(messageHome, (MimeBodyPart) ((MimeMultipart) content).getBodyPart(i)); - } - } - else - { - - String partName = part.getFileName(); - if (partName == null) - { - processTextMessage(part, messageHome, true); - } - else - { - processTextMessage(part, messageHome, false); - } - - } - } /** @@ -400,77 +275,38 @@ public class AlfrescoImapMailFolder implements MailFolder * * @param uid - UID of the message * @param toFolder - reference to the destination folder. + * @throws MessagingException + * @throws IOException + * @throws FileNotFoundException + * @throws FileExistsException */ - public void copyMessage(long uid, MailFolder toFolder) throws FolderException + @Override + protected void copyMessageInternal(long uid, MailFolder toFolder) throws MessagingException, FileExistsException, FileNotFoundException, IOException { - AlfrescoImapMailFolder toImapMailFolder = (AlfrescoImapMailFolder) toFolder; + AlfrescoImapFolder toImapMailFolder = (AlfrescoImapFolder) toFolder; - if (toImapMailFolder.isReadOnly()) - { - throw new FolderException("Can't create folder - Permission denied"); - } - - NodeRef toNodeRef = toImapMailFolder.getFolderInfo().getNodeRef(); + NodeRef destFolderNodeRef = toImapMailFolder.getFolderInfo().getNodeRef(); SimpleStoredMessage message = messages.get(uid); - FileInfo copyMess = ((AlfrescoImapMessage) message.getMimeMessage()).getMessageInfo(); + FileInfo sourceMessageFileInfo = ((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(); - List fis = new LinkedList(); - - if (imapHelper.getNodeService().getType(copyMess.getNodeRef()).equals(ImapModel.TYPE_IMAP_CONTENT)) + if (serviceRegistry.getNodeService().hasAspect(sourceMessageFileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) { - - //TODO FILE EXIST - NodeRef messageFolder = imapHelper.getFileFolderService().create(toNodeRef, AlfrescoImapConst.MESSAGE_PREFIX + GUID.generate(), ImapModel.TYPE_IMAP_CONTENT).getNodeRef(); - - final long nextUid = (Long) imapHelper.getNodeService().getProperty(messageFolder, ContentModel.PROP_NODE_DBID); - - String name = AlfrescoImapConst.MESSAGE_PREFIX + nextUid; - try - { - imapHelper.getFileFolderService().rename(messageFolder, name); - } - catch (FileNotFoundException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - - Map srcMesProps = imapHelper.getNodeService().getProperties(copyMess.getNodeRef()); - Map dstMessProps = imapHelper.getNodeService().getProperties(messageFolder); - srcMesProps.putAll(dstMessProps); - imapHelper.getNodeService().setProperties(messageFolder, srcMesProps); - - - fis = imapHelper.getFileFolderService().search(copyMess.getNodeRef(), "*", false); - toNodeRef = messageFolder; + //Generate body of message + MimeMessage newMessage = new ImapModelMessage(sourceMessageFileInfo, serviceRegistry, true); + toImapMailFolder.appendMessageInternal(newMessage, message.getFlags(), new Date()); } else { - fis.add(copyMess); - } - for (FileInfo fi : fis) - { - try - { - imapHelper.getFileFolderService().copy(fi.getNodeRef(), toNodeRef, null); - } - catch (FileExistsException e) - { - logger.error(e); - } - catch (FileNotFoundException e) - { - logger.error(e); - } + serviceRegistry.getFileFolderService().copy(sourceMessageFileInfo.getNodeRef(), destFolderNodeRef, null); } } /** * Marks all messages in the folder as deleted using {@link Flags.Flag#DELETED} flag. */ - public void deleteAllMessages() throws FolderException + @Override + public void deleteAllMessagesInternal() throws FolderException { if (this.readOnly) { @@ -479,19 +315,21 @@ public class AlfrescoImapMailFolder implements MailFolder for (SimpleStoredMessage mess : messages.values()) { - AlfrescoImapMessage message = (AlfrescoImapMessage) mess.getMimeMessage(); + AbstractMimeMessage message = (AbstractMimeMessage) mess.getMimeMessage(); FileInfo fileInfo = message.getMessageInfo(); - imapHelper.setFlag(fileInfo, Flags.Flag.DELETED, true); + imapService.setFlag(fileInfo, Flags.Flag.DELETED, true); // comment out to physically remove content. // fileFolderService.delete(fileInfo.getNodeRef()); messages.remove(mess.getUid()); + msnCache.remove(mess.getUid()); } } /** * Deletes messages marked with {@link Flags.Flag#DELETED}. Note that this message deletes all messages with this flag. */ - public void expunge() throws FolderException + @Override + protected void expungeInternal() throws FolderException { if (this.readOnly) { @@ -505,8 +343,8 @@ public class AlfrescoImapMailFolder implements MailFolder Flags flags = getFlags(mess); if (flags.contains(Flags.Flag.DELETED)) { - NodeRef nodeRef = ((AlfrescoImapMessage) mess.getMimeMessage()).getMessageInfo().getNodeRef(); - imapHelper.getFileFolderService().delete(nodeRef); + NodeRef nodeRef = ((AbstractMimeMessage) mess.getMimeMessage()).getMessageInfo().getNodeRef(); + serviceRegistry.getFileFolderService().delete(nodeRef); } } } @@ -516,7 +354,8 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return Number of the first unseen message. */ - public int getFirstUnseen() + @Override + protected int getFirstUnseenInternal() { return 0; } @@ -524,8 +363,11 @@ public class AlfrescoImapMailFolder implements MailFolder /** * Returns full name of the folder with namespace and full path delimited with the hierarchy delimiter (see {@link AlfrescoImapConst#HIERARCHY_DELIMITER})

E.g.:

* #mail.admin."Repository_archive.Data Dictionary.Space Templates.Software Engineering Project"

This is required by GreenMail implementation. + * + * @throws FileNotFoundException */ - public String getFullName() + @Override + protected String getFullNameInternal() throws FileNotFoundException { // If MailFolder object is used to obtain hierarchy delimiter by LIST command: @@ -541,44 +383,37 @@ public class AlfrescoImapMailFolder implements MailFolder StringBuilder fullName = new StringBuilder(); List pathList; - try - { - pathList = imapHelper.getFileFolderService().getNamePath(rootNodeRef, folderInfo.getNodeRef()); - fullName.append(ImapConstants.USER_NAMESPACE).append(AlfrescoImapConst.HIERARCHY_DELIMITER).append(qualifiedMailboxName); + pathList = serviceRegistry.getFileFolderService().getNamePath(rootNodeRef, folderInfo.getNodeRef()); + fullName.append(ImapConstants.USER_NAMESPACE).append(AlfrescoImapConst.HIERARCHY_DELIMITER).append(qualifiedMailboxName); - boolean isFirst = true; - for (FileInfo path : pathList) + boolean isFirst = true; + for (FileInfo path : pathList) + { + fullName.append(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (isFirst) { - fullName.append(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (isFirst) + fullName.append("\""); + isFirst = false; + if (mountPointName != null) { - fullName.append("\""); - isFirst = false; - if (mountPointName != null) - { - fullName.append(mountPointName); - } - else - { - fullName.append(path.getName()); - } + fullName.append(mountPointName); } else { fullName.append(path.getName()); } } - fullName.append("\""); - } - catch (FileNotFoundException e) - { - logger.error(e); + else + { + fullName.append(path.getName()); + } } + fullName.append("\""); if (logger.isDebugEnabled()) { logger.debug("fullName: " + fullName); } - return GreenMailUtil.convertInUtf7(fullName.toString()); + return Utf7.encode(fullName.toString(), Utf7.UTF7_MODIFIED); } /** @@ -586,15 +421,14 @@ public class AlfrescoImapMailFolder implements MailFolder * * @param uid - UID of the message. * @return message. + * @throws MessagingException */ - public SimpleStoredMessage getMessage(long uid) - { - if (!isBodyGenerated) + @Override + protected SimpleStoredMessage getMessageInternal(long uid) throws MessagingException { - // regenerate messages list and include message body into result - getMessages(); - } - return messages.get(uid); + AbstractMimeMessage mes = (AbstractMimeMessage) messages.get(uid).getMimeMessage(); + FileInfo mesInfo = mes.getMessageInfo(); + return createImapMessage(mesInfo, uid, true); } /** @@ -602,12 +436,13 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return Count of the messages. */ - public int getMessageCount() + @Override + protected int getMessageCountInternal() { if (messages.size() == 0) { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - getMessages(fileInfos, false); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos); } if (logger.isDebugEnabled()) { @@ -621,12 +456,13 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return UIDS of the messages. */ - public long[] getMessageUids() + @Override + protected long[] getMessageUidsInternal() { if (messages == null || messages.size() == 0) { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - getMessages(fileInfos, false); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos); } int len = messages.size(); long[] uids = new long[len]; @@ -644,28 +480,32 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return list of {@link SimpleStoredMessage} objects. */ - public List getMessages() + @Override + protected List getMessagesInternal() { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - return getMessages(fileInfos, true); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + return getMessages(fileInfos); } - private List getMessages(List fileInfos, boolean generateBody) + private List getMessages(List fileInfos) { - isBodyGenerated = generateBody; if (fileInfos == null || fileInfos.size() == 0) { - messages = Collections.emptyMap(); + return Collections.emptyList(); } - if (fileInfos.size() != messages.size() || generateBody) + if (fileInfos.size() != messages.size()) { for (FileInfo fileInfo : fileInfos) { try { Long key = getMessageUid(fileInfo); - SimpleStoredMessage message = new SimpleStoredMessage(new AlfrescoImapMessage(fileInfo, imapHelper, generateBody), new Date(), key); + SimpleStoredMessage message = createImapMessage(fileInfo, key, false); messages.put(key, message); + + // Saving message sequence number to cache + msnCache.put(key, messages.size()); + if (logger.isDebugEnabled()) { logger.debug("Message added: " + fileInfo.getName()); @@ -680,18 +520,31 @@ public class AlfrescoImapMailFolder implements MailFolder return new LinkedList(messages.values()); } + protected SimpleStoredMessage createImapMessage(FileInfo fileInfo, Long key, boolean generateBody) throws MessagingException + { + if (serviceRegistry.getNodeService().hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) + { + return new SimpleStoredMessage(new ImapModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key); + } + else + { + return new SimpleStoredMessage(new ContentModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key); + } + } + /** * Returns list of messages by filter. * * @param msgRangeFilter - {@link MsgRangeFilter} object representing filter. * @return list of filtered messages. */ - public List getMessages(MsgRangeFilter msgRangeFilter) + @Override + protected List getMessagesInternal(MsgRangeFilter msgRangeFilter) { - if (messages == null || messages.size() == 0 || !isBodyGenerated) + if (messages == null || messages.size() == 0) { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - getMessages(fileInfos, true); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos); } List ret = new ArrayList(); for (int i = 0; i < messages.size(); i++) @@ -712,20 +565,13 @@ public class AlfrescoImapMailFolder implements MailFolder * @return message sequence number. * @throws FolderException if no message with given UID. */ - public int getMsn(long uid) throws FolderException + @Override + protected int getMsnInternal(long uid) throws FolderException { - // Um not sure in this because the getMsn is not documented... - // Implemented alike GreenMail implementation. - Set keys = messages.keySet(); - int msn = 0; - for (Long key : keys) + Integer msn = msnCache.get(uid); + if (msn != null) { - // "==" is legal with primitives and autoboxing - if (key == uid) - { - return msn + 1; - } - msn++; + return msn; } throw new FolderException("No such message."); } @@ -735,7 +581,8 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return folder name. */ - public String getName() + @Override + protected String getNameInternal() { return folderName; } @@ -745,14 +592,15 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return the list of non-deleted messages. */ - public List getNonDeletedMessages() + @Override + protected List getNonDeletedMessagesInternal() { List result = new ArrayList(); - if (messages.size() == 0 || !isBodyGenerated) + if (messages.size() == 0) { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - getMessages(fileInfos, true); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos); } Collection values = messages.values(); @@ -776,7 +624,8 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return {@link Flags} object containing flags. */ - public Flags getPermanentFlags() + @Override + protected Flags getPermanentFlagsInternal() { return PERMANENT_FLAGS; } @@ -788,12 +637,13 @@ public class AlfrescoImapMailFolder implements MailFolder * @param reset - if true the {@link Flags.Flag#RECENT} will be deleted for current user if exists. * @return returns count of recent messages. */ - public int getRecentCount(boolean reset) + @Override + protected int getRecentCountInternal(boolean reset) { if (messages.size() == 0) { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - getMessages(fileInfos, false); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos); } int count = 0; @@ -805,7 +655,7 @@ public class AlfrescoImapMailFolder implements MailFolder count++; if (reset) { - imapHelper.setFlag(((AlfrescoImapMessage) message.getMimeMessage()).getMessageInfo(), Flags.Flag.RECENT, false); + imapService.setFlag(((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(), Flags.Flag.RECENT, false); } } @@ -823,7 +673,8 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return UIDNEXT value. */ - public long getUidNext() + @Override + protected long getUidNextInternal() { return getUidValidity(); } @@ -833,9 +684,10 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return UIDVALIDITY value. */ - public long getUidValidity() + @Override + protected long getUidValidityInternal() { - return ((Date) imapHelper.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); + return ((Date) serviceRegistry.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); } /** @@ -843,12 +695,13 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return Count of the unseen messages for current user. */ - public int getUnseenCount() + @Override + protected int getUnseenCountInternal() { if (messages.size() == 0) { - List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); - getMessages(fileInfos, false); + List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos); } int count = 0; @@ -868,16 +721,6 @@ public class AlfrescoImapMailFolder implements MailFolder return count; } - /** - * Removes {@link FolderListener} from the folder. - * - * @param listener - Listener to remove. - */ - public void removeListener(FolderListener listener) - { - listeners.remove(listener); - } - /** * Replaces flags for the message with the given UID. If {@code addUid} is set to {@code true} {@link FolderListener} objects defined for this folder will be notified. * {@code silentListener} can be provided - this listener wouldn't be notified. @@ -886,55 +729,24 @@ public class AlfrescoImapMailFolder implements MailFolder * @param uid - message UID. * @param silentListener - listener that shouldn't be notified. * @param addUid - defines whether or not listeners be notified. + * @throws FolderException + * @throws MessagingException */ - public void replaceFlags(Flags flags, long uid, FolderListener silentListener, boolean addUid) throws FolderException + @Override + protected void replaceFlagsInternal(Flags flags, long uid, FolderListener silentListener, boolean addUid) throws FolderException, MessagingException { int msn = getMsn(uid); SimpleStoredMessage message = messages.get(uid); - FileInfo fileInfo = ((AlfrescoImapMessage) message.getMimeMessage()).getMessageInfo(); - try - { - imapHelper.setFlags(fileInfo, MessageFlags.ALL_FLAGS, false); - imapHelper.setFlags(fileInfo, flags, true); - message = new SimpleStoredMessage(message.getMimeMessage(), message.getInternalDate(), uid); - messages.put(uid, message); - } - catch (MessagingException e) - { - logger.warn("Can't set flags due to an error:", e); - } + FileInfo fileInfo = ((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(); + imapService.setFlags(fileInfo, MessageFlags.ALL_FLAGS, false); + imapService.setFlags(fileInfo, flags, true); + message = new SimpleStoredMessage(message.getMimeMessage(), message.getInternalDate(), uid); + messages.put(uid, message); Long uidNotification = addUid ? uid : null; notifyFlagUpdate(msn, message.getFlags(), uidNotification, silentListener); } - private void notifyFlagUpdate(int msn, Flags flags, Long uidNotification, FolderListener silentListener) - { - synchronized (listeners) - { - for (FolderListener listener : listeners) - { - if (listener == silentListener) - { - continue; - } - - listener.flagsUpdated(msn, flags, uidNotification); - } - } - } - - /** - * Simply returns UIDs of all messages in the folder. - * - * @param searchTerm - not used - * @return UIDs of the messages - */ - public long[] search(SearchTerm searchTerm) - { - return getMessageUids(); - } - /** * Sets flags for the message with the given UID. If {@code addUid} is set to {@code true} {@link FolderListener} objects defined for this folder will be notified. * {@code silentListener} can be provided - this listener wouldn't be notified. @@ -944,22 +756,18 @@ public class AlfrescoImapMailFolder implements MailFolder * @param uid - message UID. * @param silentListener - listener that shouldn't be notified. * @param addUid - defines whether or not listeners be notified. + * @throws MessagingException + * @throws FolderException */ - public void setFlags(Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) throws FolderException + @Override + protected void setFlagsInternal(Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) throws MessagingException, FolderException { int msn = getMsn(uid); SimpleStoredMessage message = (SimpleStoredMessage) messages.get(uid); - try - { - imapHelper.setFlags(((AlfrescoImapMessage) message.getMimeMessage()).getMessageInfo(), flags, value); - message = new SimpleStoredMessage(message.getMimeMessage(), message.getInternalDate(), uid); - messages.put(uid, message); - } - catch (MessagingException e) - { - logger.warn("Can't set flags due to an error:", e); - } + imapService.setFlags(((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(), flags, value); + message = new SimpleStoredMessage(message.getMimeMessage(), message.getInternalDate(), uid); + messages.put(uid, message); Long uidNotification = null; if (addUid) @@ -970,54 +778,23 @@ public class AlfrescoImapMailFolder implements MailFolder } - /** - * Method is called before the deletion of the folder. Notifies {@link FolderListener} objects with {@link FolderListener#mailboxDeleted()} method calls. - */ - public void signalDeletion() - { - synchronized (listeners) - { - for (int i = 0; i < listeners.size(); i++) - { - FolderListener listener = (FolderListener) listeners.get(i); - listener.mailboxDeleted(); - } - } - } - - /** - * Not supported. Added to implement {@link MailFolder#store(MovingMessage)}. - */ - public void store(MovingMessage mail) throws Exception - { - throw new UnsupportedOperationException("Method store(MovingMessage) is not suppoted."); - } - - /** - * Not supported. Added to implement {@link MailFolder#store(MimeMessage)}. - */ - public void store(MimeMessage message) throws Exception - { - throw new UnsupportedOperationException("Method store(MimeMessage) is not suppoted."); - } - /** * @param fileInfo - {@link FileInfo} representing message. * @return UID of the message. */ private long getMessageUid(FileInfo fileInfo) { - if (imapHelper.getNodeService().getType(fileInfo.getNodeRef()).equals(ContentModel.TYPE_FOLDER)) + if (serviceRegistry.getNodeService().getType(fileInfo.getNodeRef()).equals(ContentModel.TYPE_FOLDER)) { - return ((Date) imapHelper.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); + return ((Date) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); } - return (Long) imapHelper.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_NODE_DBID); + return (Long) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_NODE_DBID); } private Flags getFlags(SimpleStoredMessage mess) { - return ((AlfrescoImapMessage) mess.getMimeMessage()).getFlags(); + return ((AbstractMimeMessage) mess.getMimeMessage()).getFlags(); } // ----------------------Getters and Setters---------------------------- @@ -1052,7 +829,8 @@ public class AlfrescoImapMailFolder implements MailFolder * * @return {@code boolean}. */ - public boolean isSelectable() + @Override + protected boolean isSelectableInternal() { return this.selectable; @@ -1071,14 +849,121 @@ public class AlfrescoImapMailFolder implements MailFolder // imapHelper.setProperties(folderInfo, properties); } - /** * Whether the folder is read-only for user. + * * @return {@code boolean} */ - public boolean isReadOnly() + @Override + protected boolean isReadOnly() { return readOnly; } + public String getViewMode() + { + return viewMode; + } + + /** + * Creates the EML message in the specified folder. + * + * @param folderFileInfo The folder to create message in. + * @param message The original MimeMessage. + * @return Wrapped AbstractMimeMessage which was created. + * @throws FileNotFoundException + * @throws FileExistsException + * @throws MessagingException + * @throws IOException + */ + private AbstractMimeMessage createMimeMessageInFolder(FileInfo folderFileInfo, MimeMessage message) throws FileExistsException, FileNotFoundException, IOException, MessagingException + { + String name = AlfrescoImapConst.MESSAGE_PREFIX + GUID.generate(); + FileFolderService fileFolderService = serviceRegistry.getFileFolderService(); + FileInfo messageFile = fileFolderService.create(folderFileInfo.getNodeRef(), name, ContentModel.TYPE_CONTENT); + final long newMessageUid = (Long) messageFile.getProperties().get(ContentModel.PROP_NODE_DBID); + name = AlfrescoImapConst.MESSAGE_PREFIX + newMessageUid + AlfrescoImapConst.EML_EXTENSION; + fileFolderService.rename(messageFile.getNodeRef(), name); + + if (extractAttachmentsEnabled) + { + extractAttachments(folderFileInfo, messageFile, message); + } + return new IncomingImapMessage(messageFile, serviceRegistry, message); + } + + private void extractAttachments(FileInfo parentFolder, FileInfo messageFile, MimeMessage originalMessage) throws IOException, MessagingException + { + NodeService nodeService = serviceRegistry.getNodeService(); + FileFolderService fileFolderService = serviceRegistry.getFileFolderService(); + + String messageName = (String)nodeService.getProperty(messageFile.getNodeRef(), ContentModel.PROP_NAME); + String attachmentsFolderName = messageName + "-attachments"; + FileInfo attachmentsFolderFileInfo = null; + Object content = originalMessage.getContent(); + if (content instanceof Multipart) + { + Multipart multipart = (Multipart) content; + + for (int i = 0, n = multipart.getCount(); i < n; i++) + { + Part part = multipart.getBodyPart(i); + if ("attachment".equalsIgnoreCase(part.getDisposition())) + { + if (attachmentsFolderFileInfo == null) + { + attachmentsFolderFileInfo = fileFolderService.create(parentFolder.getNodeRef(), attachmentsFolderName, ContentModel.TYPE_FOLDER); + serviceRegistry.getNodeService().createAssociation(messageFile.getNodeRef(), attachmentsFolderFileInfo.getNodeRef(), + ImapModel.ASSOC_IMAP_ATTACHMENTS_FOLDER); + } + createAttachment(messageFile, attachmentsFolderFileInfo, part); + } + } + } + + } + + private void createAttachment(FileInfo messageFile, FileInfo attachmentsFolderFileInfo, Part part) throws MessagingException, IOException + { + String fileName = part.getFileName(); + try + { + fileName = MimeUtility.decodeText(fileName); + } + catch (UnsupportedEncodingException e) + { + if (logger.isWarnEnabled()) + { + logger.warn("Cannot decode file name '" + fileName + "'", e); + } + } + + ContentType contentType = new ContentType(part.getContentType()); + FileFolderService fileFolderService = serviceRegistry.getFileFolderService(); + List result = fileFolderService.search(attachmentsFolderFileInfo.getNodeRef(), fileName, false); + // The one possible behaviour + /* + if (result.size() > 0) + { + for (FileInfo fi : result) + { + fileFolderService.delete(fi.getNodeRef()); + } + } + */ + // And another one behaviour which will overwrite the content of the existing file. It is performance preferable. + FileInfo attachmentFile = null; + if (result.size() == 0) + { + FileInfo createdFile = fileFolderService.create(attachmentsFolderFileInfo.getNodeRef(), fileName, ContentModel.TYPE_CONTENT); + serviceRegistry.getNodeService().createAssociation(messageFile.getNodeRef(), createdFile.getNodeRef(), ImapModel.ASSOC_IMAP_ATTACHMENT); + result.add(createdFile); + } + attachmentFile = result.get(0); + ContentWriter writer = fileFolderService.getWriter(attachmentFile.getNodeRef()); + writer.setMimetype(contentType.getBaseType()); + OutputStream os = writer.getContentOutputStream(); + FileCopyUtils.copy(part.getInputStream(), os); + } + } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java b/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java index d158c1bf4a..996206740f 100755 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java @@ -24,27 +24,11 @@ */ package org.alfresco.repo.imap; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import org.alfresco.model.ContentModel; -import org.alfresco.model.ImapModel; -import org.alfresco.repo.imap.config.ImapConfigBean; -import org.alfresco.repo.imap.exception.AlfrescoImapFolderException; -import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,7 +37,6 @@ import com.icegreen.greenmail.imap.ImapHostManager; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; import com.icegreen.greenmail.user.GreenMailUser; -import com.icegreen.greenmail.util.GreenMailUtil; /** * @author Mike Shavnev @@ -63,11 +46,8 @@ public class AlfrescoImapHostManager implements ImapHostManager private Log logger = LogFactory.getLog(AlfrescoImapHostManager.class); - private ServiceRegistry serviceRegistry; - - private NodeService nodeService; - private FileFolderService fileFolderService; - private ImapHelper imapHelper; + private ImapService imapService; + private TransactionService transactionService; /** * Returns the hierarchy delimiter for mailboxes on this host. @@ -90,18 +70,14 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public Collection listMailboxes(GreenMailUser user, String mailboxPattern) throws FolderException { - mailboxPattern = GreenMailUtil.convertFromUtf7(mailboxPattern); - - if (logger.isDebugEnabled()) + try { - logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); + return new ArrayList(imapService.listMailboxes(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxPattern)); } - mailboxPattern = imapHelper.getMailPathInRepo(mailboxPattern); - if (logger.isDebugEnabled()) + catch (Throwable e) { - logger.debug("Listing mailboxes: mailboxPattern in alfresco=" + mailboxPattern); + throw new FolderException(e.getMessage()); } - return listMailboxes(user, mailboxPattern, false); } /** @@ -116,251 +92,14 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public Collection listSubscribedMailboxes(GreenMailUser user, String mailboxPattern) throws FolderException { - mailboxPattern = GreenMailUtil.convertFromUtf7(mailboxPattern); - - if (logger.isDebugEnabled()) + try { - logger.debug("Listing subscribed mailboxes: mailboxPattern=" + mailboxPattern); + return new ArrayList(imapService.listSubscribedMailboxes(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxPattern)); } - mailboxPattern = imapHelper.getMailPathInRepo(mailboxPattern); - if (logger.isDebugEnabled()) + catch (Throwable e) { - logger.debug("Listing subscribed mailboxes: mailboxPattern in alfresco=" + mailboxPattern); + throw new FolderException(e.getMessage()); } - - return listMailboxes(user, mailboxPattern, true); - } - - /** - * Depend on listSubscribed param, list Mailboxes or list subscribed Mailboxes - */ - private Collection listMailboxes(GreenMailUser user, String mailboxPattern, boolean listSubscribed) throws FolderException - { - Collection result = new LinkedList(); - - Map mountPoints = imapHelper.getMountPoints(); - Map imapConfigs = imapHelper.getImapConfig(); - - NodeRef mountPoint; - - // List mailboxes that are in mount points - for (String mountPointName : mountPoints.keySet()) - { - - mountPoint = mountPoints.get(mountPointName); - FileInfo mountPointFileInfo = imapHelper.getFileFolderService().getFileInfo(mountPoint); - NodeRef mountParent = imapHelper.getNodeService().getParentAssocs(mountPoint).get(0).getParentRef(); - String viewMode = imapConfigs.get(mountPointName).getMode(); - - if (!mailboxPattern.equals("*")) - { - mountPoint = mountParent; - } - - boolean isVirtualView = imapConfigs.get(mountPointName).getMode().equals(AlfrescoImapConst.MODE_VIRTUAL); - Collection folders = listFolder(mountPoint, mountPoint, user, mailboxPattern, listSubscribed, isVirtualView); - if (folders != null) - { - for (MailFolder mailFolder : folders) - { - AlfrescoImapMailFolder folder = (AlfrescoImapMailFolder) mailFolder; - folder.setMountPointName(mountPointName); - folder.setViewMode(viewMode); - folder.setMountParent(mountParent); - } - result.addAll(folders); - } - - // Add mount point to the result list - if (mailboxPattern.equals("*")) - { - if ((listSubscribed && isSubscribed(mountPointFileInfo, user.getLogin())) || (!listSubscribed)) - { - result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, viewMode, mountParent, mountPointName, imapHelper)); - } - // \NoSelect - else if (listSubscribed && hasSubscribedChild(mountPointFileInfo, user.getLogin(), isVirtualView)) - { - result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, viewMode, mountParent, mountPointName, imapHelper, - false)); - } - } - - } - - // List mailboxes that are in user IMAP Home - NodeRef root = imapHelper.getUserImapHomeRef(user.getLogin()); - Collection imapFolders = listFolder(root, root, user, mailboxPattern, listSubscribed, false); - - if (imapFolders != null) - { - for (MailFolder mailFolder : imapFolders) - { - AlfrescoImapMailFolder folder = (AlfrescoImapMailFolder) mailFolder; - folder.setViewMode(AlfrescoImapConst.MODE_ARCHIVE); - folder.setMountParent(root); - } - result.addAll(imapFolders); - } - - return result; - - } - - private Collection listFolder(NodeRef mailboxRoot, NodeRef root, GreenMailUser user, String mailboxPattern, boolean listSubscribed, boolean isVirtualView) - throws FolderException - { - if (logger.isDebugEnabled()) - { - logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); - } - - int index = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - - String name = null; - String remainName = null; - - if (index < 0) - { - name = mailboxPattern; - } - else - { - name = mailboxPattern.substring(0, index); - remainName = mailboxPattern.substring(index + 1); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Listing mailboxes: name=" + name); - } - - if (index < 0) - { - if ("*".equals(name)) - { - Collection list = imapHelper.searchFolders(root, name, true, isVirtualView); - if (listSubscribed) - { - list = getSubscribed(list, user.getLogin()); - } - - if (list.size() > 0) - { - return createMailFolderList(user, list, mailboxRoot); - } - return null; - } - else if (name.endsWith("*")) - { - List fullList = new LinkedList(); - List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); - Collection subscribedList = list; - if (listSubscribed) - { - subscribedList = getSubscribed(list, user.getLogin()); - } - - if (list.size() > 0) - { - fullList.addAll(subscribedList); - for (FileInfo fileInfo : list) - { - List childList = imapHelper.searchFolders(fileInfo.getNodeRef(), "*", true, isVirtualView); - if (listSubscribed) - { - fullList.addAll(getSubscribed(childList, user.getLogin())); - } - else - { - fullList.addAll(childList); - } - } - return createMailFolderList(user, fullList, mailboxRoot); - } - return null; - } - else if ("%".equals(name)) - { - List list = imapHelper.searchFolders(root, "*", false, isVirtualView); - LinkedList subscribedList = new LinkedList(); - - if (listSubscribed) - { - for (FileInfo fileInfo : list) - { - if (isSubscribed(fileInfo, user.getLogin())) - { - // folderName, viewMode, mountPointName will be setted in listMailboxes() method - subscribedList.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), fileInfo, null, null, mailboxRoot, null, imapHelper)); - } - // \NoSelect - else if (hasSubscribedChild(fileInfo, user.getLogin(), isVirtualView)) - { - // folderName, viewMode, mountPointName will be setted in listMailboxes() method - subscribedList.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), fileInfo, null, null, mailboxRoot, null, imapHelper, false)); - } - } - } - else - { - return createMailFolderList(user, list, mailboxRoot); - } - - return subscribedList; - } - else if (name.contains("%") || name.contains("*")) - { - List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); - Collection subscribedList = list; - if (listSubscribed) - { - subscribedList = getSubscribed(list, user.getLogin()); - } - - if (subscribedList.size() > 0) - { - return createMailFolderList(user, subscribedList, mailboxRoot); - } - return null; - } - else - { - List list = imapHelper.searchFolders(root, name, false, isVirtualView); - Collection subscribedList = list; - if (listSubscribed) - { - subscribedList = getSubscribed(list, user.getLogin()); - } - - if (subscribedList.size() > 0) - { - return createMailFolderList(user, subscribedList, mailboxRoot); - } - return null; - } - } - - // If (index != -1) this is not the last level - Collection result = new LinkedList(); - - List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); - for (FileInfo folder : list) - { - Collection childFolders = listFolder(mailboxRoot, folder.getNodeRef(), user, remainName, listSubscribed, isVirtualView); - - if (childFolders != null) - { - result.addAll(childFolders); - } - } - - if (result.isEmpty()) - { - return null; - } - - return result; } /** @@ -375,97 +114,16 @@ public class AlfrescoImapHostManager implements ImapHostManager * @throws com.icegreen.greenmail.store.FolderException if an existing folder with the new name. * @throws AlfrescoImapFolderException if user does not have rights to create the new mailbox. */ - public void renameMailbox(GreenMailUser user, String oldMailboxName, String newMailboxName) throws FolderException, AuthorizationException { - oldMailboxName = GreenMailUtil.convertFromUtf7(oldMailboxName); - newMailboxName = GreenMailUtil.convertFromUtf7(newMailboxName); - if (logger.isDebugEnabled()) + try { - logger.debug("Renaming folder: oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName); + imapService.renameMailbox(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), oldMailboxName, newMailboxName); } - - AlfrescoImapMailFolder sourceNode = (AlfrescoImapMailFolder) getFolder(user, GreenMailUtil.convertInUtf7(oldMailboxName)); - - NodeRef root = imapHelper.getMailboxRootRef(oldMailboxName, user.getLogin()); - String mailboxRepoName = imapHelper.getMailPathInRepo(newMailboxName); - - StringTokenizer tokenizer = new StringTokenizer(mailboxRepoName, String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); - - NodeRef parentNodeRef = root; - while (tokenizer.hasMoreTokens()) + catch (Throwable e) { - String folderName = tokenizer.nextToken(); - - if (!tokenizer.hasMoreTokens()) - { - try - { - if (oldMailboxName.equalsIgnoreCase(AlfrescoImapConst.INBOX_NAME)) - { - // If you trying to rename INBOX - // - just copy it to another folder with new name - // and leave INBOX (with children) intact. - fileFolderService.copy(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName); - List itemsForRemove = fileFolderService.list(sourceNode.getFolderInfo().getNodeRef()); - for (FileInfo fileInfo : itemsForRemove) - { - fileFolderService.delete(fileInfo.getNodeRef()); - } - - } - else - { - fileFolderService.move(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName); - } - return; - } - catch (FileExistsException e) - { - throw new FolderException(FolderException.ALREADY_EXISTS_LOCALLY); - } - catch (FileNotFoundException e) - { - if (logger.isDebugEnabled()) - { - logger.error(e); - } - } - - } - else - { - List folders = imapHelper.searchFolders(parentNodeRef, folderName, false, true); - - if (folders.size() == 0) - { - AccessStatus status = imapHelper.hasPermission(parentNodeRef, PermissionService.WRITE); - if (status == AccessStatus.DENIED) - { - if (logger.isDebugEnabled()) - { - logger.debug("Creating folder: Cant't create folder - Permission denied"); - } - throw new AlfrescoImapFolderException(AlfrescoImapFolderException.PERMISSION_DENIED); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Create mailBox: " + folderName); - } - FileFolderServiceImpl.makeFolders(fileFolderService, parentNodeRef, Arrays.asList(folderName), ContentModel.TYPE_FOLDER); - } - else - { - parentNodeRef = folders.get(0).getNodeRef(); - if (logger.isDebugEnabled()) - { - logger.debug("MailBox: " + folderName + " already exists"); - } - } - } + throw new FolderException(e.getMessage()); } - } /** @@ -480,58 +138,14 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public MailFolder createMailbox(GreenMailUser user, String mailboxName) throws AuthorizationException, FolderException { - mailboxName = GreenMailUtil.convertFromUtf7(mailboxName); - if (logger.isDebugEnabled()) + try { - logger.debug("Creating folder: " + mailboxName); + return imapService.createMailbox(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); } - - NodeRef root = imapHelper.getMailboxRootRef(mailboxName, user.getLogin()); - - String mountPointName = imapHelper.getMountPointName(mailboxName); - String mailboxRepoNam = imapHelper.getMailPathInRepo(mailboxName); - StringTokenizer tokenizer = new StringTokenizer(mailboxRepoNam, String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); - - NodeRef parentNodeRef = root; - - while (tokenizer.hasMoreTokens()) + catch (Throwable e) { - String folderName = tokenizer.nextToken(); - - List folders = imapHelper.searchFolders(parentNodeRef, folderName, false, true); - - if (folders.size() == 0) - { - AccessStatus status = imapHelper.hasPermission(parentNodeRef, PermissionService.WRITE); - if (status == AccessStatus.DENIED) - { - if (logger.isDebugEnabled()) - { - logger.debug("Creating folder: Cant't create folder - Permission denied"); - } - throw new AlfrescoImapFolderException(AlfrescoImapFolderException.PERMISSION_DENIED); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Create mailBox: " + mailboxName); - } - FileInfo mailFolder = FileFolderServiceImpl.makeFolders(fileFolderService, parentNodeRef, Arrays.asList(folderName), ContentModel.TYPE_FOLDER); - - return new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), mailFolder, folderName, imapHelper.getViewMode(mailboxName), root, mountPointName, imapHelper); - - } - else - { - parentNodeRef = folders.get(0).getNodeRef(); - if (logger.isDebugEnabled()) - { - logger.debug("MailBox: " + folderName + " already exists"); - } - } + throw new FolderException(e.getMessage()); } - - throw new FolderException(FolderException.ALREADY_EXISTS_LOCALLY); } /** @@ -544,34 +158,13 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public void deleteMailbox(GreenMailUser user, String mailboxName) throws FolderException, AuthorizationException { - AlfrescoImapMailFolder folder = (AlfrescoImapMailFolder) getFolder(user, mailboxName); - NodeRef nodeRef = folder.getFolderInfo().getNodeRef(); - - List childFolders = imapHelper.searchFolders(nodeRef, "*", false, false); - - if (childFolders.isEmpty()) + try { - folder.signalDeletion(); - // Delete child folders and messages - fileFolderService.delete(nodeRef); + imapService.deleteMailbox(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); } - else + catch (Throwable e) { - if (folder.isSelectable()) - { - // Delete all messages for this folder - // Don't delete subfolders and their messages - List messages = imapHelper.searchFiles(nodeRef, "*", ImapModel.TYPE_IMAP_CONTENT, false); - for (FileInfo message : messages) - { - fileFolderService.delete(message.getNodeRef()); - } - nodeService.addAspect(nodeRef, ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE, null); - } - else - { - throw new FolderException(mailboxName + " - Can't delete a non-selectable store with children."); - } + throw new FolderException(e.getMessage()); } } @@ -586,80 +179,22 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public MailFolder getFolder(GreenMailUser user, String mailboxName) { - mailboxName = GreenMailUtil.convertFromUtf7(mailboxName); - if (logger.isDebugEnabled()) - { - logger.debug("Getting folder: " + mailboxName); - } - - // If MailFolder object is used to obtain hierarchy delimiter by LIST command: - // Example: - // C: 2 list "" "" - // S: * LIST () "." "" - // S: 2 OK LIST completed. - if ("".equals(mailboxName)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Request for the hierarchy delimiter"); - } - return new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), null, null, null, null, null, null); - } - - NodeRef root = imapHelper.getMailboxRootRef(mailboxName, user.getLogin()); - String mountPointName = imapHelper.getMountPointName(mailboxName); - String mailboxRepoName = imapHelper.getMailPathInRepo(mailboxName); - - StringTokenizer tokenizer = new StringTokenizer(mailboxRepoName, String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); - int count = tokenizer.countTokens(); - NodeRef nodeRef = root; - - while (tokenizer.hasMoreTokens()) - { - String t = tokenizer.nextToken(); - if (logger.isDebugEnabled()) - { - logger.debug("token=" + t); - } - count--; - - List list = imapHelper.searchFolders(nodeRef, t, false, true); - - if (count == 0) - { - if (!list.isEmpty()) - { - - return new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), list.get(0), list.get(0).getName(), imapHelper.getViewMode(mailboxName), root, - mountPointName, imapHelper); - } - else - { - return new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), null, null, null, null, null, null); - } - } - else - { - if (!list.isEmpty()) - { - nodeRef = list.get(0).getNodeRef(); - } - else - { - return new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), null, null, null, null, null, null); - } - } - } - - throw new IllegalStateException("Error state"); + return imapService.getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); } /** * Simply calls {@link #getFolder(GreenMailUser, String)}.

Added to implement {@link ImapHostManager}. */ - public MailFolder getFolder(GreenMailUser user, String mailboxName, boolean mustExist) throws FolderException + public MailFolder getFolder(final GreenMailUser user, final String mailboxName, boolean mustExist) throws FolderException { - return getFolder(user, mailboxName); + try + { + return getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); + } + catch (Throwable e) + { + throw new FolderException(e.getMessage()); + } } /** @@ -670,7 +205,14 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public MailFolder getInbox(GreenMailUser user) throws FolderException { - return getFolder(user, AlfrescoImapConst.INBOX_NAME); + try + { + return getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), AlfrescoImapConst.INBOX_NAME); + } + catch (Throwable e) + { + throw new FolderException(e.getMessage()); + } } /** @@ -687,39 +229,16 @@ public class AlfrescoImapHostManager implements ImapHostManager * @param user User making the request * @param mailbox String representation of a mailbox name. */ - public void subscribe(final GreenMailUser user, final String mailbox) throws FolderException + public void subscribe(GreenMailUser user, String mailbox) throws FolderException { - if (logger.isDebugEnabled()) + try { - logger.debug("Subscribing: " + mailbox); + imapService.subscribe(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailbox); + } + catch (Throwable e) + { + throw new FolderException(e.getMessage()); } - AlfrescoImapMailFolder mailFolder = (AlfrescoImapMailFolder) getFolder(user, mailbox); - nodeService.addAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_SUBSCRIBED, null); -// This is a multiuser support. Commented due new requirements -// AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() -// { -// public Void doWork() throws Exception -// { -// AlfrescoImapMailFolder mailFolder = (AlfrescoImapMailFolder) getFolder(user, mailbox); -// FileInfo fileInfo = mailFolder.getFolderInfo(); -// if (fileInfo != null) -// { -// String subscribedList = (String) nodeService.getProperty(fileInfo.getNodeRef(), ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED); -// if (subscribedList == null) -// { -// subscribedList = ""; -// } -// subscribedList = subscribedList.replaceAll(imapHelper.formatUserEntry(user.getLogin()), ""); -// subscribedList += imapHelper.formatUserEntry(user.getLogin()); -// nodeService.setProperty(fileInfo.getNodeRef(), ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED, subscribedList); -// } -// else -// { -// logger.debug("MailBox: " + mailbox + "doesn't exsist. Maybe it was deleted earlier."); -// } -// return null; -// } -// }, AuthenticationUtil.getSystemUserName()); } /** @@ -728,137 +247,46 @@ public class AlfrescoImapHostManager implements ImapHostManager * @param user User making the request * @param mailbox String representation of a mailbox name. */ - public void unsubscribe(final GreenMailUser user, final String mailbox) throws FolderException + public void unsubscribe(GreenMailUser user, String mailbox) throws FolderException { - if (logger.isDebugEnabled()) + try { - logger.debug("Unsubscribing: " + mailbox); + imapService.unsubscribe(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailbox); + } + catch (Throwable e) + { + throw new FolderException(e.getMessage()); } - AlfrescoImapMailFolder mailFolder = (AlfrescoImapMailFolder) getFolder(user, mailbox); - nodeService.removeAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_SUBSCRIBED); - -// This is a multiuser support. Commented due new requirements -// AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() -// { -// public Void doWork() throws Exception -// { -// AlfrescoImapMailFolder mailFolder = (AlfrescoImapMailFolder) getFolder(user, mailbox); -// if (mailFolder.getFolderInfo() != null) -// { -// FileInfo fileInfo = mailFolder.getFolderInfo(); -// String subscribedList = (String) nodeService.getProperty(fileInfo.getNodeRef(), ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED); -// if (subscribedList == null) -// { -// subscribedList = ""; -// } -// subscribedList = subscribedList.replaceAll(imapHelper.formatUserEntry(user.getLogin()), ""); -// nodeService.setProperty(fileInfo.getNodeRef(), ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED, subscribedList); -// } -// else -// { -// logger.debug("MailBox: " + mailbox + " doesn't exsist. Maybe it was deleted earlier."); -// } -// -// return null; -// } -// }, AuthenticationUtil.getSystemUserName()); } /** * Not supported. Used by GreenMail class. */ - public List getAllMessages() + public List getAllMessages() { throw new UnsupportedOperationException(); } - private boolean isSubscribed(FileInfo fileInfo, String userName) - { - return nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_SUBSCRIBED); -// This is a multiuser support. Commented due new requirements - -// Map properties = fileInfo.getProperties(); -// String subscribedList = (String) properties.get(ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED); -// if (subscribedList == null) -// { -// return false; -// } -// else -// { -// return subscribedList.contains(imapHelper.formatUserEntry(userName)); -// } - - } - - private Collection getSubscribed(Collection list, String userName) - { - Collection result = new LinkedList(); - - for (FileInfo folderInfo : list) - { - if (isSubscribed(folderInfo, userName)) - { - result.add(folderInfo); - } - } - - return result; - } - - private boolean hasSubscribedChild(FileInfo parent, String userName, boolean isVirtualView) - { - List list = imapHelper.searchFolders(parent.getNodeRef(), "*", true, isVirtualView); - - for (FileInfo fileInfo : list) - { - if (isSubscribed(fileInfo, userName)) - { - return true; - } - } - - return false; - } - - private Collection createMailFolderList(GreenMailUser user, Collection list, NodeRef imapUserHomeRef) - { - Collection result = new LinkedList(); - - for (FileInfo folderInfo : list) - { - // folderName, viewMode, mountPointName will be setted in listSubscribedMailboxes() method - result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), folderInfo, null, null, imapUserHomeRef, null, imapHelper)); - } - - return result; - - } - // ----------------------Getters and Setters---------------------------- - public void setServiceRegistry(ServiceRegistry serviceRegistry) + public ImapService getImapService() { - this.serviceRegistry = serviceRegistry; + return imapService; } - public ServiceRegistry getServiceRegistry() + public void setImapService(ImapService imapService) { - return serviceRegistry; + this.imapService = imapService; } - public void setNodeService(NodeService nodeService) + public TransactionService getTransactionService() { - this.nodeService = nodeService; + return transactionService; } - public void setFileFolderService(FileFolderService fileFolderService) + public void setTransactionService(TransactionService transactionService) { - this.fileFolderService = fileFolderService; - } - - public void setImapHelper(ImapHelper imapHelper) - { - this.imapHelper = imapHelper; + this.transactionService = transactionService; } } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java b/source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java deleted file mode 100755 index 1d2155586a..0000000000 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2005-2009 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.repo.imap; - -import static org.alfresco.repo.imap.AlfrescoImapConst.BASE_64_ENCODING; -import static org.alfresco.repo.imap.AlfrescoImapConst.CONTENT_ID; -import static org.alfresco.repo.imap.AlfrescoImapConst.CONTENT_TRANSFER_ENCODING; -import static org.alfresco.repo.imap.AlfrescoImapConst.CONTENT_TYPE; -import static org.alfresco.repo.imap.AlfrescoImapConst.MIME_VERSION; -import static org.alfresco.repo.imap.AlfrescoImapConst.UTF_8; -import static org.alfresco.repo.imap.AlfrescoImapConst.X_ALF_NODEREF_ID; -import static org.alfresco.repo.imap.AlfrescoImapConst.X_ALF_SERVER_UID; - -import java.io.IOException; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.activation.DataHandler; -import javax.activation.DataSource; -import javax.mail.Address; -import javax.mail.Flags; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.internet.AddressException; -import javax.mail.internet.ContentType; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import javax.mail.internet.MimeUtility; -import javax.mail.util.ByteArrayDataSource; - -import org.alfresco.i18n.I18NUtil; -import org.alfresco.model.ContentModel; -import org.alfresco.model.ImapModel; -import org.alfresco.repo.imap.ImapHelper.EmailBodyType; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Extended MimeMessage to represent a content stored in the Alfresco repository. - * - * @author Arseny Kovalchuk - */ -public class AlfrescoImapMessage extends MimeMessage -{ - /** Used if imapHelper.getDefaultFromAddress is not set */ - private static final String DEFAULT_EMAIL_FROM = "alfresco@alfresco.org"; - private static final String DEFAULT_EMAIL_TO = DEFAULT_EMAIL_FROM; - private static final String KOI8R_CHARSET = "koi8-r"; - - private static Log logger = LogFactory.getLog(AlfrescoImapMessage.class); - - private ImapHelper imapHelper; - private FileInfo messageInfo; - - /** - * Constructs {@link AlfrescoImapMessage} object. - * - * @param fileInfo - reference to the {@link FileInfo} object representing the message. - * @param imapHelper - reference to the {@link ImapHelper} object. - * @param generateBody - if {@code true} message body will be generated. - * - * @throws MessagingException if generation of the body fails. - */ - public AlfrescoImapMessage(FileInfo fileInfo, ImapHelper imapHelper, boolean generateBody) throws MessagingException - { - super(Session.getDefaultInstance(new Properties())); - this.messageInfo = fileInfo; - this.imapHelper = imapHelper; - if (generateBody) - { - setMessageHeaders(); - buildMessage(); - } - } - - /** - * Constructs {@link AlfrescoImapMessage} object. - * - * @param fileInfo - reference to the {@link FileInfo} object representing the message. - * @param imapHelper - reference to the {@link ImapHelper} object. - * @param message - {@link MimeMessage} - * @throws MessagingException - */ - public AlfrescoImapMessage(FileInfo fileInfo, ImapHelper imapHelper, MimeMessage message) throws MessagingException - { - super(message); - this.messageInfo = fileInfo; - this.imapHelper = imapHelper; - - setMessageHeaders(); - final NodeRef nodeRef = fileInfo.getNodeRef(); - Map props = new HashMap(); - props.put(ImapModel.PROP_MESSAGE_FROM, InternetAddress.toString(message.getFrom())); - props.put(ImapModel.PROP_MESSAGE_TO, InternetAddress.toString(message.getRecipients(RecipientType.TO))); - props.put(ImapModel.PROP_MESSAGE_CC, InternetAddress.toString(message.getRecipients(RecipientType.CC))); - - String[] subj = message.getHeader("Subject"); - if (subj.length > 0) - { - props.put(ImapModel.PROP_MESSAGE_SUBJECT, subj[0]); - imapHelper.getNodeService().setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, subj[0]); - } - - Map allprops = imapHelper.getNodeService().getProperties(fileInfo.getNodeRef()); - allprops.putAll(props); - imapHelper.getNodeService().setProperties(nodeRef, allprops); - // setContent(buildMultipart(fileInfo)); - disabled for better performance. - } - - /** - * Returns message flags. - * - * @return {@link Flags} - */ - @Override - public synchronized Flags getFlags() - { - return imapHelper.getFlags(messageInfo); - } - - - /** - * Sets message flags. - * - * @param flags - {@link Flags} object. - * @param value - flags value. - */ - @Override - public synchronized void setFlags(Flags flags, boolean value) throws MessagingException - { - imapHelper.setFlags(messageInfo, flags, value); - } - - - /** - * Returns {@link FileInfo} object representing message in Alfresco. - * - * @return reference to the {@link FileInfo} object. - */ - public FileInfo getMessageInfo() - { - return messageInfo; - } - - private void setMessageHeaders() throws MessagingException - { - setHeader(MIME_VERSION, "1.0"); - // Optional headers for further implementation of multiple Alfresco server support. - setHeader(X_ALF_NODEREF_ID, messageInfo.getNodeRef().getId()); - setHeader(X_ALF_SERVER_UID, imapHelper.getAlfrescoServerUID()); - } - - /** - * This method builds MimeMessage based on either ImapModel or ContentModel type. - * - * @param fileInfo - Source file information {@link FileInfo} - * @throws MessagingException - */ - private void buildMessage() throws MessagingException - { - final NodeRef nodeRef = messageInfo.getNodeRef(); - if (ImapModel.TYPE_IMAP_CONTENT.equals(imapHelper.getNodeService().getType(nodeRef))) - { - buildImapModelMessage(); - } - else - { - buildContentModelMessage(); - } - } - - /** - * This method builds MimeMessage based on {@link ImapModel} - * - * @param fileInfo - Source file information {@link FileInfo} - * @throws MessagingException - */ - private void buildImapModelMessage() throws MessagingException - { - Map properties = messageInfo.getProperties(); - setSentDate(messageInfo.getModifiedDate()); - String prop = (String) properties.get(ImapModel.PROP_MESSAGE_FROM); - addFromInternal(prop); - prop = (String) properties.get(ImapModel.PROP_MESSAGE_TO); - - if (prop != null && prop.length() > 0) - { - addRecipients(RecipientType.TO, InternetAddress.parse(prop)); - } - else - { - addRecipients(RecipientType.TO, DEFAULT_EMAIL_TO); - } - - prop = (String) properties.get(ImapModel.PROP_MESSAGE_CC); - if (prop != null && prop.length() > 0) - { - addRecipients(RecipientType.CC, InternetAddress.parse(prop)); - } - - prop = (String) properties.get(ImapModel.PROP_MESSAGE_SUBJECT); - setSubject(prop == null ? messageInfo.getName() : prop); - - setContent(buildImapModelMultipart()); - - } - - /** - * This method builds {@link MimeMessage} based on {@link ContentModel} - * - * @param fileInfo - Source file information {@link FileInfo} - * @throws MessagingException - */ - private void buildContentModelMessage() throws MessagingException - { - Map properties = messageInfo.getProperties(); - String prop = null; - setSentDate(messageInfo.getModifiedDate()); - // Add FROM address - Address[] addressList = buildSenderFromAddress(properties); - addFrom(addressList); - // Add TO address - addressList = buildRecipientToAddress(); - addRecipients(RecipientType.TO, addressList); - prop = (String) properties.get(ContentModel.PROP_TITLE); - try - { - prop = (prop == null) ? MimeUtility.encodeText(messageInfo.getName(), KOI8R_CHARSET, null) : MimeUtility.encodeText(prop, KOI8R_CHARSET, null); - } - catch (UnsupportedEncodingException e) - { - // ignore - } - setSubject(prop); - setContent(buildContentModelMultipart()); - } - - /** - * This method builds {@link Multipart} based on {@link ContentModel} - * - * @param fileInfo - Source file information {@link FileInfo} - * @throws MessagingException - */ - private Multipart buildContentModelMultipart() throws MessagingException - { - MimeMultipart rootMultipart = new MimeMultipart("alternative"); - // Cite MOB-395: "email agent will be used to select an appropriate template" - we are not able to - // detect an email agent so we use a default template for all messages. - // See AlfrescoImapConst to see the possible templates to use. - String bodyTxt = imapHelper.getEmailBodyText(messageInfo.getNodeRef(), EmailBodyType.TEXT_PLAIN); - rootMultipart.addBodyPart(getTextBodyPart(bodyTxt, EmailBodyType.TEXT_PLAIN.getSubtype())); - String bodyHtml = imapHelper.getEmailBodyText(messageInfo.getNodeRef(), EmailBodyType.TEXT_HTML); - rootMultipart.addBodyPart(getTextBodyPart(bodyHtml, EmailBodyType.TEXT_HTML.getSubtype())); - return rootMultipart; - } - - private MimeBodyPart getTextBodyPart(String bodyText, String subtype) throws MessagingException - { - MimeBodyPart result = new MimeBodyPart(); - result.setText(bodyText, UTF_8, subtype); - result.addHeader(CONTENT_TRANSFER_ENCODING, BASE_64_ENCODING); - return result; - } - - /** - * This method builds {@link Multipart} based on {@link ImapModel} - * - * @param fileInfo - Source file information {@link FileInfo} - * @throws MessagingException - */ - private Multipart buildImapModelMultipart() throws MessagingException - { - DataSource source = null; - String errorMessage = null; - - // Root multipart - multipart/mixed - MimeMultipart rootMultipart = new MimeMultipart("mixed"); - // Message body - multipart/alternative - consists of two parts: text/plain and text/html - MimeMultipart messageBody = new MimeMultipart("alternative"); - // <------------------------ text html body part ------------------------> - List bodyHtmls = imapHelper.searchFiles(messageInfo.getNodeRef(), "*.html", ImapModel.TYPE_IMAP_BODY, false); - ContentType contentType = null; - MimeBodyPart textHtmlBodyPart = null; - if (bodyHtmls != null && bodyHtmls.size() > 0) - { - textHtmlBodyPart = new MimeBodyPart(); - FileInfo bodyHtml = bodyHtmls.get(0); - contentType = new ContentType(bodyHtml.getContentData().getMimetype()); - ContentReader reader = imapHelper.getFileFolderService().getReader(bodyHtml.getNodeRef()); - try - { - source = new ByteArrayDataSource(reader.getContentInputStream(), contentType.toString()); - } - catch (IOException e) - { - logger.error(e); - errorMessage = e.getMessage(); - } - if (source != null) - { - textHtmlBodyPart.setDataHandler(new DataHandler(source)); - textHtmlBodyPart.addHeader(CONTENT_TYPE, bodyHtml.getContentData().getMimetype()); - // textHtmlBodyPart.addHeader(CONTENT_TRANSFER_ENCODING, EIGHT_BIT_ENCODING); - textHtmlBodyPart.addHeader(CONTENT_TRANSFER_ENCODING, BASE_64_ENCODING); - } - else - { - textHtmlBodyPart.setText(errorMessage, UTF_8); - } - messageBody.addBodyPart(textHtmlBodyPart); - } - // - // <------------------------ text plain body part ------------------------> - List results = imapHelper.searchFiles(messageInfo.getNodeRef(), "*.txt", ImapModel.TYPE_IMAP_BODY, false); - MimeBodyPart textPlainBodyPart = null; - String text = null; - if (results != null && results.size() > 0) - { - textPlainBodyPart = new MimeBodyPart(); - FileInfo bodyTxt = results.get(0); - text = imapHelper.getFileFolderService().getReader(bodyTxt.getNodeRef()).getContentString(); - contentType = new ContentType(bodyTxt.getContentData().getMimetype()); - } - else if (textHtmlBodyPart == null) - { - text = I18NUtil.getMessage("imap.server.info.message_body_not_found"); - contentType = new ContentType(EmailBodyType.TEXT_PLAIN.getMimeType() + "; charset=UTF-8"); - } - - textPlainBodyPart.setText(text, contentType.getParameter("charset"), contentType.getSubType()); - textPlainBodyPart.addHeader(CONTENT_TYPE, contentType.toString()); - messageBody.addBodyPart(textPlainBodyPart); - // - - // Body part for multipart/alternative - MimeBodyPart messageBodyPart = new MimeBodyPart(); - messageBodyPart.setContent(messageBody); - // Add multipart/alternative into root multipart/mixed... - rootMultipart.addBodyPart(messageBodyPart); - - // Process attachments - List attaches = imapHelper.searchFiles(messageInfo.getNodeRef(), "*", ImapModel.TYPE_IMAP_ATTACH, false); - - for (FileInfo attach : attaches) - { - try - { - - errorMessage = null; - messageBodyPart = new MimeBodyPart(); - ContentReader reader = imapHelper.getFileFolderService().getReader(attach.getNodeRef()); - source = new ByteArrayDataSource(reader.getContentInputStream(), attach.getContentData().getMimetype()); - } - catch (IOException e) - { - logger.error(e); - errorMessage = e.getMessage(); - } - if (source != null) - { - String attachID = (String) imapHelper.getNodeService().getProperty(attach.getNodeRef(), ImapModel.PROP_ATTACH_ID); - if (attachID != null) - { - messageBodyPart.addHeader(CONTENT_ID, attachID); - } - StringBuilder ct = new StringBuilder(attach.getContentData().getMimetype()).append("; name=\"").append(attach.getName()).append("\""); - messageBodyPart.addHeader(CONTENT_TYPE, ct.toString()); - messageBodyPart.addHeader(CONTENT_TRANSFER_ENCODING, BASE_64_ENCODING); - messageBodyPart.setDataHandler(new DataHandler(source)); - try - { - messageBodyPart.setFileName(MimeUtility.encodeText(attach.getName(), KOI8R_CHARSET, null)); - } - catch (UnsupportedEncodingException e) - { - // ignore - } - } - else - { - messageBodyPart.setText(errorMessage, UTF_8); - } - rootMultipart.addBodyPart(messageBodyPart); - } - return rootMultipart; - } - - private void addFromInternal(String addressesString) throws MessagingException - { - if (addressesString != null) - { - addFrom(InternetAddress.parse(addressesString)); - } - else - { - addFrom(new Address[] { new InternetAddress(DEFAULT_EMAIL_FROM) }); - } - } - - /** - * TODO USE CASE 2: "The To/addressee will be the first email alias found in the parent folders or a default one (TBD)". It seems to be more informative as alike - * {@code @}... - * - * @return Generated TO address {@code @} - * @throws AddressException - */ - private InternetAddress[] buildRecipientToAddress() throws AddressException - { - InternetAddress[] result = null; - String defaultEmailTo = null; - // TODO : search first email alias found in the parent folders - // if (found) defaultEmailTo = foundAlias - // else - final String escapedUserName = imapHelper.getCurrentUser().replaceAll("[/,\\,@]", "."); - final String userDomain = DEFAULT_EMAIL_TO.split("@")[1]; - defaultEmailTo = escapedUserName + "@" + userDomain; - try - { - result = InternetAddress.parse(defaultEmailTo); - } - catch (AddressException e) - { - logger.error(String.format("Wrong email address '%s'.", defaultEmailTo), e); - result = InternetAddress.parse(DEFAULT_EMAIL_TO); - } - return result; - } - - /** - * Builds the InternetAddress from the Content Author name if provided. If name not specified, it takes Content Creator name. If content creator does not exists, the default - * from address will be returned. - * - * @param contentAuthor The content author full name. - * @return Generated InternetAddress[] array. - * @throws AddressException - */ - private InternetAddress[] buildSenderFromAddress(Map properties) throws AddressException - { - // Generate FROM address (Content author) - InternetAddress[] addressList = null; - String prop = (String) properties.get(ContentModel.PROP_AUTHOR); - String defaultFromAddress = imapHelper.getDefaultFromAddress(); - defaultFromAddress = defaultFromAddress == null ? DEFAULT_EMAIL_FROM : defaultFromAddress; - try - { - - if (prop != null) - { - StringBuilder contentAuthor = new StringBuilder(); - contentAuthor.append("\"").append(prop).append("\" <").append(defaultFromAddress).append(">"); - addressList = InternetAddress.parse(contentAuthor.toString()); - } - else - { - prop = (String) properties.get(ContentModel.PROP_CREATOR); - if (prop != null) - { - StringBuilder creator = new StringBuilder(); - creator.append("\"").append(prop).append("\" <").append(defaultFromAddress).append(">"); - addressList = InternetAddress.parse(creator.toString()); - } - else - { - throw new AddressException(I18NUtil.getMessage("imap.server.error.properties_dont_exist")); - } - } - } - catch (AddressException e) - { - addressList = InternetAddress.parse(DEFAULT_EMAIL_FROM); - } - return addressList; - } - -} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java index 1efdeb32fa..49d3d2467d 100755 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java @@ -47,14 +47,14 @@ public class AlfrescoImapServer extends AbstractLifecycleBean private int port = 143; + private String host = "localhost"; + private ImapHostManager imapHostManager; private UserManager imapUserManager; private boolean imapServerEnabled; - private ImapHelper imapHelper; - public void setImapServerEnabled(boolean imapServerEnabled) { this.imapServerEnabled = imapServerEnabled; @@ -65,6 +65,11 @@ public class AlfrescoImapServer extends AbstractLifecycleBean this.port = port; } + public void setHost(String host) + { + this.host = host; + } + public void setImapHostManager(ImapHostManager imapHostManager) { this.imapHostManager = imapHostManager; @@ -75,14 +80,9 @@ public class AlfrescoImapServer extends AbstractLifecycleBean this.imapUserManager = imapUserManager; } - public void setImapHelper(ImapHelper imapHelper) - { - this.imapHelper = imapHelper; - } - protected void onBootstrap(ApplicationEvent event) { - if (imapServerEnabled && imapHelper.isPatchApplied()) + if (imapServerEnabled) { Managers imapManagers = new Managers() { @@ -96,11 +96,11 @@ public class AlfrescoImapServer extends AbstractLifecycleBean return imapUserManager; } }; - serverImpl = new ImapServer(new ServerSetup(port, null, ServerSetup.PROTOCOL_IMAP), imapManagers); + serverImpl = new ImapServer(new ServerSetup(port, host, ServerSetup.PROTOCOL_IMAP), imapManagers); serverImpl.startService(null); if (logger.isInfoEnabled()) { - logger.info("IMAP service started on port " + this.port + "."); + logger.info("IMAP service started on host:port " + this.host + ":" + this.port + "."); } } else diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java b/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java index ed5d31aa71..c86f7428b1 100755 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java @@ -26,7 +26,6 @@ package org.alfresco.repo.imap; import javax.mail.internet.MimeMessage; -import com.icegreen.greenmail.imap.ImapHostManager; import com.icegreen.greenmail.mail.MovingMessage; import com.icegreen.greenmail.user.GreenMailUser; import com.icegreen.greenmail.user.UserException; @@ -42,14 +41,11 @@ public class AlfrescoImapUser implements GreenMailUser private char[] password; private String email; - private ImapHostManager imapHostManager; - - public AlfrescoImapUser(String email, String login, String password, ImapHostManager imapHostManager) + public AlfrescoImapUser(String email, String login, String password) { this.email = email; this.userName = login; this.password = password.toCharArray(); - this.imapHostManager = imapHostManager; } public void authenticate(String password) throws UserException @@ -71,12 +67,12 @@ public class AlfrescoImapUser implements GreenMailUser public void deliver(MovingMessage msg) throws UserException { - + throw new UnsupportedOperationException(); } public void deliver(MimeMessage msg) throws UserException { - + throw new UnsupportedOperationException(); } public String getEmail() diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java b/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java index eb938cb728..7d1377af4c 100755 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java @@ -37,7 +37,6 @@ import org.alfresco.service.cmr.security.PersonService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import com.icegreen.greenmail.imap.ImapHostManager; import com.icegreen.greenmail.user.GreenMailUser; import com.icegreen.greenmail.user.UserException; import com.icegreen.greenmail.user.UserManager; @@ -50,7 +49,6 @@ public class AlfrescoImapUserManager extends UserManager private Log logger = LogFactory.getLog(AlfrescoImapUserManager.class); protected Map userMap = Collections.synchronizedMap(new HashMap()); - protected ImapHostManager imapHostManager; protected AuthenticationService authenticationService; protected PersonService personService; @@ -61,17 +59,11 @@ public class AlfrescoImapUserManager extends UserManager super(null); } - public AlfrescoImapUserManager(ImapHostManager imapHostManager) - { - this(); - this.imapHostManager = imapHostManager; - } - public GreenMailUser createUser(String email, String login, String password) throws UserException { // TODO: User creation/addition code should be implemented here (in the AlfrescoImapUserManager). // Following code is not need and not used in the current implementation. - GreenMailUser user = new AlfrescoImapUser(email, login, password, imapHostManager); + GreenMailUser user = new AlfrescoImapUser(email, login, password); user.create(); addUser(user); return user; @@ -129,7 +121,7 @@ public class AlfrescoImapUserManager extends UserManager NodeRef personNodeRef = personService.getPerson(userid); email = (String) nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL); } - GreenMailUser user = new AlfrescoImapUser(email, userid, password, imapHostManager); + GreenMailUser user = new AlfrescoImapUser(email, userid, password); addUser(user); } catch (AuthenticationException ex) @@ -140,16 +132,6 @@ public class AlfrescoImapUserManager extends UserManager return true; } - public ImapHostManager getImapHostManager() - { - return this.imapHostManager; - } - - public void setImapHostManager(ImapHostManager imapHostManager) - { - this.imapHostManager = imapHostManager; - } - public void setAuthenticationService(AuthenticationService authenticationService) { this.authenticationService = authenticationService; diff --git a/source/java/org/alfresco/repo/imap/ContentModelMessage.java b/source/java/org/alfresco/repo/imap/ContentModelMessage.java new file mode 100755 index 0000000000..be0931426f --- /dev/null +++ b/source/java/org/alfresco/repo/imap/ContentModelMessage.java @@ -0,0 +1,99 @@ +package org.alfresco.repo.imap; + +import static org.alfresco.repo.imap.AlfrescoImapConst.BASE_64_ENCODING; +import static org.alfresco.repo.imap.AlfrescoImapConst.CONTENT_TRANSFER_ENCODING; +import static org.alfresco.repo.imap.AlfrescoImapConst.UTF_8; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimeUtility; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.namespace.QName; + +public class ContentModelMessage extends AbstractMimeMessage +{ + + public ContentModelMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry, boolean generateBody) throws MessagingException + { + super(fileInfo, serviceRegistry, generateBody); + } + + @Override + public void buildMessageInternal() throws MessagingException + { + if (generateBody != false) + { + setMessageHeaders(); + buildContentModelMessage(); + } + } + + /** + * This method builds {@link MimeMessage} based on {@link ContentModel} + * + * @param fileInfo - Source file information {@link FileInfo} + * @throws MessagingException + */ + private void buildContentModelMessage() throws MessagingException + { + Map properties = messageFileInfo.getProperties(); + String prop = null; + setSentDate(messageFileInfo.getModifiedDate()); + // Add FROM address + Address[] addressList = buildSenderFromAddress(); + addFrom(addressList); + // Add TO address + addressList = buildRecipientToAddress(); + addRecipients(RecipientType.TO, addressList); + prop = (String) properties.get(ContentModel.PROP_TITLE); + try + { + prop = (prop == null) ? MimeUtility.encodeText(messageFileInfo.getName(), KOI8R_CHARSET, null) : MimeUtility.encodeText(prop, KOI8R_CHARSET, null); + } + catch (UnsupportedEncodingException e) + { + // ignore + } + setSubject(prop); + setContent(buildContentModelMultipart()); + } + + /** + * This method builds {@link Multipart} based on {@link ContentModel} + * + * @param fileInfo - Source file information {@link FileInfo} + * @throws MessagingException + */ + private Multipart buildContentModelMultipart() throws MessagingException + { + MimeMultipart rootMultipart = new MimeMultipart("alternative"); + // Cite MOB-395: "email agent will be used to select an appropriate template" - we are not able to + // detect an email agent so we use a default template for all messages. + // See AlfrescoImapConst to see the possible templates to use. + String bodyTxt = getEmailBodyText(EmailBodyType.TEXT_PLAIN); + rootMultipart.addBodyPart(getTextBodyPart(bodyTxt, EmailBodyType.TEXT_PLAIN.getSubtype())); + String bodyHtml = getEmailBodyText(EmailBodyType.TEXT_HTML); + rootMultipart.addBodyPart(getTextBodyPart(bodyHtml, EmailBodyType.TEXT_HTML.getSubtype())); + return rootMultipart; + } + + private MimeBodyPart getTextBodyPart(String bodyText, String subtype) throws MessagingException + { + MimeBodyPart result = new MimeBodyPart(); + result.setText(bodyText, UTF_8, subtype); + result.addHeader(CONTENT_TRANSFER_ENCODING, BASE_64_ENCODING); + return result; + } + +} diff --git a/source/java/org/alfresco/repo/imap/ImapHelper.java b/source/java/org/alfresco/repo/imap/ImapHelper.java deleted file mode 100755 index 7e493e825a..0000000000 --- a/source/java/org/alfresco/repo/imap/ImapHelper.java +++ /dev/null @@ -1,994 +0,0 @@ -/* - * Copyright (C) 2005-2009 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.repo.imap; - -import static org.alfresco.repo.imap.AlfrescoImapConst.CLASSPATH_TEXT_HTML_TEMPLATE; -import static org.alfresco.repo.imap.AlfrescoImapConst.CLASSPATH_TEXT_PLAIN_TEMPLATE; -import static org.alfresco.repo.imap.AlfrescoImapConst.DICTIONARY_TEMPLATE_PREFIX; - -import java.io.Serializable; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import javax.mail.Flags; -import javax.mail.Flags.Flag; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.model.ImapModel; -import org.alfresco.repo.admin.patch.PatchInfo; -import org.alfresco.repo.admin.patch.PatchService; -import org.alfresco.repo.imap.config.ImapConfigBean; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.template.TemplateNode; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.preference.PreferenceService; -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.TemplateService; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.AbstractLifecycleBean; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; - -/** - * Helper class to access repository services by IMAP components. Also contains a common helper methods to search and - * manage IMAP content and other usefull methods. Configured as {@code } in the {@code imap-server-context.xml} file. - * - * @author Dmitry Vaserin - */ -/*package*/class ImapHelper extends AbstractLifecycleBean -{ - private static Log logger = LogFactory.getLog(ImapHelper.class); - - private static String PATCH_ID = "patch.imapFolders"; - - private NodeService nodeService; - private SearchService searchService; - private FileFolderService fileFolderService; - private TemplateService templateService; - private NamespaceService namespaceService; - private PermissionService permissionService; - private DictionaryService dictionaryService; - private PreferenceService preferenceService; - private SiteService siteService; - - private ServiceRegistry serviceRegistry; - - private PatchService patchService; - - private String defaultFromAddress; - private String webApplicationContextUrl = "http://localhost:8080/alfresco"; - private String repositoryTemplatePath; - private String imapRoot; - private NodeRef spacesStoreNodeRef; - private NodeRef companyHomeNodeRef; - private NodeRef imapRootNodeRef; - - private boolean patchApplied = false; - - private final static Map qNameToFlag; - private final static Map flagToQname; - - private Map imapConfigBeans = Collections.emptyMap(); - - static - { - qNameToFlag = new HashMap(); - qNameToFlag.put(ImapModel.PROP_FLAG_ANSWERED, Flags.Flag.ANSWERED); - qNameToFlag.put(ImapModel.PROP_FLAG_DELETED, Flags.Flag.DELETED); - qNameToFlag.put(ImapModel.PROP_FLAG_DRAFT, Flags.Flag.DRAFT); - qNameToFlag.put(ImapModel.PROP_FLAG_SEEN, Flags.Flag.SEEN); - qNameToFlag.put(ImapModel.PROP_FLAG_RECENT, Flags.Flag.RECENT); - qNameToFlag.put(ImapModel.PROP_FLAG_FLAGGED, Flags.Flag.FLAGGED); - - flagToQname = new HashMap(); - flagToQname.put(Flags.Flag.ANSWERED, ImapModel.PROP_FLAG_ANSWERED); - flagToQname.put(Flags.Flag.DELETED, ImapModel.PROP_FLAG_DELETED); - flagToQname.put(Flags.Flag.DRAFT, ImapModel.PROP_FLAG_DRAFT); - flagToQname.put(Flags.Flag.SEEN, ImapModel.PROP_FLAG_SEEN); - flagToQname.put(Flags.Flag.RECENT, ImapModel.PROP_FLAG_RECENT); - flagToQname.put(Flags.Flag.FLAGGED, ImapModel.PROP_FLAG_FLAGGED); - } - - public static enum EmailBodyType - { - TEXT_PLAIN, TEXT_HTML; - - public String getSubtype() - { - return name().toLowerCase().substring(5); - } - - public String getTypeSubtype() - { - return name().toLowerCase().replaceAll("_", ""); - } - - public String getMimeType() - { - return name().toLowerCase().replaceAll("_", "/"); - } - - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // Do nothing - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Void doWork() throws Exception - { - List patches = getPatchService().getPatches(null, null); - for (PatchInfo patch : patches) - { - if (patch.getId().equals(PATCH_ID)) - { - patchApplied = true; - break; - } - } - - if (!patchApplied) - { - return null; - } - - int indexOfStoreDelim = imapRoot.indexOf(StoreRef.URI_FILLER); - - if (indexOfStoreDelim == -1) - { - throw new RuntimeException("Bad path format, " + StoreRef.URI_FILLER + " not found"); - } - - indexOfStoreDelim += StoreRef.URI_FILLER.length(); - - int indexOfPathDelim = imapRoot.indexOf("/", indexOfStoreDelim); - - if (indexOfPathDelim == -1) - { - throw new java.lang.RuntimeException("Bad path format, / not found"); - } - - String storePath = imapRoot.substring(0, indexOfPathDelim); - String rootPathInStore = imapRoot.substring(indexOfPathDelim); - - StoreRef storeRef = new StoreRef(storePath); - - if (nodeService.exists(storeRef) == false) - { - throw new RuntimeException("No store for path: " + storeRef); - } - - NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); - - List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPathInStore, null, namespaceService, false); - - if (nodeRefs.size() > 1) - { - throw new RuntimeException("Multiple possible roots for : \n" + " root path: " + rootPathInStore + "\n" + " results: " + nodeRefs); - } - else if (nodeRefs.size() == 0) - { - throw new RuntimeException("No root found for : \n" + " root path: " + rootPathInStore); - } - - imapRootNodeRef = nodeRefs.get(0); - - // Get "Company Home" node reference - StoreRef store = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); - ResultSet rs = searchService.query(store, SearchService.LANGUAGE_XPATH, "/app:company_home"); - try - { - if (rs.length() == 0) - { - throw new AlfrescoRuntimeException("'Company Home' space doesn't exists."); - } - companyHomeNodeRef = rs.getNodeRef(0); - } - finally - { - rs.close(); - } - - spacesStoreNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - } - - /** - * Search for files in specified context - * - * @param contextNodeRef context folder for search - * @param namePattern name pattern for search - * @param searchType type for search - * @param includeSubFolders include SubFolders - * @return list of files with specifed type - */ - public List searchFiles(NodeRef contextNodeRef, String namePattern, QName searchType, boolean includeSubFolders) - { - return search(contextNodeRef, namePattern, searchType, true, false, includeSubFolders); - } - - /** - * Search for mailboxes in specified context - * - * @param contextNodeRef context folder for search - * @param namePattern name pattern for search - * @param includeSubFolders include SubFolders - * @param isVirtualView is folder in "Virtual" View - * @return list of mailboxes - */ - public List searchFolders(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders, boolean isVirtualView) - { - QName searchType = ContentModel.TYPE_FOLDER; - if (isVirtualView) - { - searchType = null; - } - - List result = search(contextNodeRef, namePattern, searchType, false, true, includeSubFolders); - if (isVirtualView) - { - List nonFavSites = getNonFavouriteSites(getCurrentUser()); - for (SiteInfo siteInfo : nonFavSites) - { - FileInfo nonFavSite = fileFolderService.getFileInfo(siteInfo.getNodeRef()); - List siteChilds = search(nonFavSite.getNodeRef(), namePattern, null, false, true, true); - result.removeAll(siteChilds); - result.remove(nonFavSite); - } - - } - else - { - // Remove folders from Sites - List sites = siteService.listSites(getCurrentUser()); - for (SiteInfo siteInfo : sites) - { - List siteChilds = search(siteInfo.getNodeRef(), namePattern, null, false, true, true); - result.removeAll(siteChilds); - } - - } - return result; - } - - /** - * Search for emails in specified folder depend on view mode. - * - * @param contextNodeRef context folder for search - * @param namePattern name pattern for search - * @param viewMode context folder view mode - * @param includeSubFolders includeSubFolders - * @return list of emails that context folder contains. - */ - public List searchMails(NodeRef contextNodeRef, String namePattern, String viewMode, boolean includeSubFolders) - { - - List result = new LinkedList(); - if (viewMode.equals(AlfrescoImapConst.MODE_ARCHIVE)) - { - result = search(contextNodeRef, namePattern, ImapModel.TYPE_IMAP_CONTENT, false, true, includeSubFolders); - } - else - { - if (viewMode.equals(AlfrescoImapConst.MODE_VIRTUAL)) - { - result = search(contextNodeRef, namePattern, null, true, false, includeSubFolders); - } - } - - return result; - } - - private List search(NodeRef contextNodeRef, String namePattern, QName searchType, boolean fileSearch, boolean folderSearch, boolean includeSubFolders) - { - List result = new LinkedList(); - List searchResult = fileFolderService.search(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders); - - if (searchType == null) - { - return searchResult; - } - - for (FileInfo fileInfo : searchResult) - { - if (nodeService.getType(fileInfo.getNodeRef()).equals(searchType)) - { - result.add(fileInfo); - } - } - - return result; - } - - /** - * Get root reference for the specified mailbox - * - * @param mailboxName mailbox name in IMAP client. - * @param userName - * @return - */ - public NodeRef getMailboxRootRef(String mailboxName, String userName) - { - String rootFolder; - int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailboxName.substring(0, index); - } - else - { - rootFolder = mailboxName; - } - - Map imapConfigs = getImapConfig(); - if (imapConfigs.keySet().contains(rootFolder)) - { - Map mountPoints = getMountPoints(); - NodeRef mountRef = mountPoints.get(rootFolder); - return nodeService.getParentAssocs(mountRef).get(0).getParentRef(); - } - else - { - return getUserImapHomeRef(userName); - } - } - - /** - * @param userName user name - * @return user IMAP home reference and create it if it doesn't exist. - */ - public NodeRef getUserImapHomeRef(final String userName) - { - NodeRef userHome = fileFolderService.searchSimple(imapRootNodeRef, userName); - if (userHome == null) - { - // create user home - userHome = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public NodeRef doWork() throws Exception - { - NodeRef result = fileFolderService.create(imapRootNodeRef, userName, ContentModel.TYPE_FOLDER).getNodeRef(); - nodeService.setProperty(result, ContentModel.PROP_DESCRIPTION, userName); - // create inbox - fileFolderService.create(result, AlfrescoImapConst.INBOX_NAME, ContentModel.TYPE_FOLDER); - return result; - } - }, AuthenticationUtil.getSystemUserName()); - } - return userHome; - } - - public String getCurrentUser() - { - return AuthenticationUtil.getFullyAuthenticatedUser(); - } - - public String getUserImapHomeId(String userName) - { - return getUserImapHomeRef(userName).getId(); - } - - public NodeRef getImapRootNodeRef() - { - return imapRootNodeRef; - } - - public NodeRef getCompanyHomeNodeRef() - { - return companyHomeNodeRef; - } - - public NodeRef getSpacesStoreNodeRef() - { - return spacesStoreNodeRef; - } - - public void setImapRoot(String imapRoot) - { - this.imapRoot = imapRoot; - } - - public String getDefaultFromAddress() - { - return defaultFromAddress; - } - - public void setDefaultFromAddress(String defaultFromAddress) - { - this.defaultFromAddress = defaultFromAddress; - } - - public String getWebApplicationContextUrl() - { - return this.webApplicationContextUrl; - } - - public void setWebApplicationContextUrl(String webApplicationContextUrl) - { - this.webApplicationContextUrl = webApplicationContextUrl; - } - - public String getRepositoryTemplatePath() - { - return repositoryTemplatePath; - } - - public void setRepositoryTemplatePath(String repositoryTemplatePath) - { - this.repositoryTemplatePath = repositoryTemplatePath; - } - - /** - * Return flags that belong to the specified imap folder. - * - * @param messageInfo imap folder info. - * @return flags. - */ - public Flags getFlags(FileInfo messageInfo) - { - Flags flags = new Flags(); - checkForFlaggableAspect(messageInfo.getNodeRef()); - Map props = nodeService.getProperties(messageInfo.getNodeRef()); - - for (QName key : qNameToFlag.keySet()) - { - Boolean value = (Boolean) props.get(key); - if (value != null && value) - { - flags.add(qNameToFlag.get(key)); - } - } - // This is a multiuser flag support. Commented due new requirements - // for (QName key : qNameToFlag.keySet()) - // { - // if (key.equals(ImapModel.PROP_FLAG_DELETED)) - // { - // Boolean value = (Boolean) props.get(key); - // if (value != null && value) - // { - // flags.add(qNameToFlag.get(key)); - // } - // } - // else - // { - // String users = (String) props.get(key); - // - // if (users != null && users.indexOf(formatUserEntry(getCurrentUser())) >= 0) - // { - // flags.add(qNameToFlag.get(key)); - // } - // } - // } - - return flags; - } - - /** - * Set flags to the specified imapFolder. - * - * @param messageInfo FileInfo of imap Folder. - * @param flags flags to set. - * @param value value to set. - */ - public void setFlags(FileInfo messageInfo, Flags flags, boolean value) - { - checkForFlaggableAspect(messageInfo.getNodeRef()); - for (Flags.Flag flag : flags.getSystemFlags()) - { - setFlag(messageInfo, flag, value); - } - } - - /** - * Set flags to the specified imapFolder. - * - * @param messageInfo FileInfo of imap Folder - * @param flag flag to set. - * @param value value value to set. - */ - public void setFlag(final FileInfo messageInfo, final Flag flag, final boolean value) - { - checkForFlaggableAspect(messageInfo.getNodeRef()); - nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), value); - - // This is a multiuser flag support. Commented due new requirements - // if (flagToQname.get(flag).equals(ImapModel.PROP_FLAG_DELETED)) - // { - // nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), value); - // } - // else - // { - // AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - // { - // public Void doWork() throws Exception - // { - // - // String users = (String) nodeService.getProperty(messageInfo.getNodeRef(), flagToQname.get(flag)); - // if (value) - // { - // if (users == null) - // { - // users = ""; - // } - // users += formatUserEntry(getCurrentUser()); - // - // } - // else if (users != null) - // { - // users = users.replace(formatUserEntry(getCurrentUser()), ""); - // - // } - // nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), users); - // return null; - // } - // }, AuthenticationUtil.getSystemUserName()); - // } - - } - - /** - * Check that the given authentication has a particular permission for the given node. - * - * @param nodeRef nodeRef of the node - * @param permission permission for check - * @return the access status - */ - public AccessStatus hasPermission(NodeRef nodeRef, String permission) - { - return permissionService.hasPermission(nodeRef, permission); - } - - /** - * Change userName into following format ;userName; - * - * @param userName - * @return - */ - public String formatUserEntry(String userName) - { - return AlfrescoImapConst.USER_SEPARATOR + userName + AlfrescoImapConst.USER_SEPARATOR; - } - - /** - * This method should returns a unique identifier of Alfresco server. The possible UID may be calculated based on IP address, Server port, MAC address, Web Application context. - * This UID should be parseable into initial components. This necessary for the implementation of the following case: If the message being copied (e.g. drag-and-drop) between - * two different Alfresco accounts in the IMAP client, we must unambiguously identify from which Alfresco server this message being copied. The message itself does not contain - * content data, so we must download it from the initial server (e.g. using download content servlet) and save it into destination repository. - * - * @return String representation of unique identifier of Alfresco server - */ - public String getAlfrescoServerUID() - { - // TODO Implement as javadoc says. - return "Not-Implemented"; - } - - /** - * Map of mount points. Name of mount point == key in the map. - * - * @return Map of mount points. - */ - public Map getMountPoints() - { - Map imapConfigs = getImapConfig(); - Map mountPoints = new HashMap(); - - for (ImapConfigBean config : imapConfigs.values()) - { - // Get node reference - StoreRef store = new StoreRef(config.getStore()); - ResultSet rs = searchService.query(store, SearchService.LANGUAGE_XPATH, config.getRootPath()); - if (rs.length() == 0) - { - logger.warn("Didn't find " + config.getName()); - } - else - { - NodeRef nodeRef = rs.getNodeRef(0); - mountPoints.put(config.getName(), nodeRef); - } - rs.close(); - } - return mountPoints; - } - - public void setImapConfigBeans(ImapConfigBean[] imapConfigBeans) - { - this.imapConfigBeans = new LinkedHashMap(imapConfigBeans.length * 2); - for (ImapConfigBean bean : imapConfigBeans) - { - this.imapConfigBeans.put(bean.getName(), bean); - } - } - - /** - * Return map of imap configs. Name of config == key in the map - * - * @return map of imap configs. - */ - public Map getImapConfig() - { - return this.imapConfigBeans; - } - - /** - * Return view mode ("virtual" or "archive") for specified mailbox. - * - * @param mailboxName name of the mailbox in IMAP client. - * @return view mode of the specified mailbox. - */ - public String getViewMode(String mailboxName) - { - String rootFolder; - int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailboxName.substring(0, index); - } - else - { - rootFolder = mailboxName; - } - Map imapConfigs = getImapConfig(); - if (imapConfigs.keySet().contains(rootFolder)) - { - return imapConfigs.get(rootFolder).getMode(); - } - else - { - return AlfrescoImapConst.MODE_ARCHIVE; - } - } - - /** - * Return mount point name, which was specified in imap-config.xml for the current mailbox. - * - * @param mailboxName mailbox name in IMAP client. - * @return mount point name or null. - */ - public String getMountPointName(String mailboxName) - { - String rootFolder; - int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailboxName.substring(0, index); - } - else - { - rootFolder = mailboxName; - } - Map imapConfigs = getImapConfig(); - if (imapConfigs.keySet().contains(rootFolder)) - { - return rootFolder; - } - else - { - return null; - } - - } - - /** - * Convert mailpath from IMAP client representation to the alfresco representation view. (e.g. with default settings "getMailPathInRepo(Repository_virtual.Imap Home)" will - * return "Company Home.Imap Home") - * - * @param mailPath mailbox path in IMAP client - * @return mailbox path in alfresco - */ - public String getMailPathInRepo(String mailPath) - { - String rootFolder; - String remain = ""; - int index = mailPath.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailPath.substring(0, index); - remain = mailPath.substring(index); - } - else - { - rootFolder = mailPath; - } - Map imapConfigs = getImapConfig(); - if (imapConfigs.keySet().contains(rootFolder)) - { - Map mountPoints = getMountPoints(); - NodeRef rootRef = mountPoints.get(rootFolder); - String rootName = nodeService.getProperty(rootRef, ContentModel.PROP_NAME).toString(); - - return rootName + remain; - } - else - { - return mailPath; - } - } - - /** - * Return list of sites, that belong to the specified user and not marked as "Imap favourite" - * - * @param userName name of user - * @return List of nonFavourite sites. - */ - public List getNonFavouriteSites(String userName) - { - List nonFavSites = new LinkedList(); - Map prefs = preferenceService.getPreferences(userName, AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES); - List sites = siteService.listSites(userName); - for (SiteInfo siteInfo : sites) - { - String key = AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES + "." + siteInfo.getShortName(); - Boolean isImapFavourite = (Boolean) prefs.get(key); - if (isImapFavourite == null || !isImapFavourite) - { - nonFavSites.add(siteInfo); - } - } - - return nonFavSites; - } - - /** - * Returns the text representing email body for ContentModel node. - * - * @param nodeRef NodeRef of the target content. - * @param type The type of the returned body. May be the one of {@link EmailBodyType}. - * @return Text representing email body for ContentModel node. - */ - public String getEmailBodyText(NodeRef nodeRef, EmailBodyType type) - { - return templateService.processTemplate(getDefaultEmailBodyTemplate(type), createEmailTemplateModel(nodeRef)); - } - - /** - * Returns default email body template. This method trying to find a template on the path in the repository first e.g. {@code "Data Dictionary > IMAP Templates >"}. This path - * should be set as the property of the "imapHelper" bean. In this case it returns {@code NodeRef.toString()} of the template. If there are no template in the repository it - * returns a default template on the classpath. - * - * @param type One of the {@link EmailBodyType}. - * @return String representing template classpath path or NodeRef.toString(). - */ - public String getDefaultEmailBodyTemplate(EmailBodyType type) - { - String result = null; - switch (type) - { - case TEXT_HTML: - result = CLASSPATH_TEXT_HTML_TEMPLATE; - break; - case TEXT_PLAIN: - result = CLASSPATH_TEXT_PLAIN_TEMPLATE; - break; - } - final StringBuilder templateName = new StringBuilder(DICTIONARY_TEMPLATE_PREFIX).append("-").append(type.getTypeSubtype()).append(".ftl"); - int indexOfStoreDelim = repositoryTemplatePath.indexOf(StoreRef.URI_FILLER); - if (indexOfStoreDelim == -1) - { - logger.error("Bad path format, " + StoreRef.URI_FILLER + " not found"); - return result; - } - indexOfStoreDelim += StoreRef.URI_FILLER.length(); - int indexOfPathDelim = repositoryTemplatePath.indexOf("/", indexOfStoreDelim); - if (indexOfPathDelim == -1) - { - logger.error("Bad path format, / not found"); - return result; - } - final String storePath = repositoryTemplatePath.substring(0, indexOfPathDelim); - final String rootPathInStore = repositoryTemplatePath.substring(indexOfPathDelim); - final String query = String.format("+PATH:\"%1$s/*\" +@cm\\:name:\"%2$s\"", rootPathInStore, templateName.toString()); - if (logger.isDebugEnabled()) - { - logger.debug("Using template path :" + repositoryTemplatePath + "/" + templateName); - logger.debug("Query: " + query); - } - StoreRef storeRef = new StoreRef(storePath); - ResultSet resultSet = searchService.query(storeRef, "lucene", query); - if (resultSet == null || resultSet.length() == 0) - { - logger.error(String.format("IMAP message template '%1$s' does not exist in the path '%2$s'.", templateName, repositoryTemplatePath)); - return result; - } - result = resultSet.getNodeRef(0).toString(); - return result; - } - - /** - * Builds default email template model for TemplateProcessor - * - * @param ref NodeRef of the target content. - * @return Map that includes template model objects. - */ - private Map createEmailTemplateModel(NodeRef ref) - { - Map model = new HashMap(8, 1.0f); - TemplateNode tn = new TemplateNode(ref, serviceRegistry, null); - model.put("document", tn); - NodeRef parent = nodeService.getPrimaryParent(ref).getParentRef(); - model.put("space", new TemplateNode(parent, serviceRegistry, null)); - model.put("date", new Date()); - model.put("contextUrl", new String(getWebApplicationContextUrl())); - model.put("alfTicket", new String(serviceRegistry.getAuthenticationService().getCurrentTicket())); - return model; - } - - private void checkForFlaggableAspect(NodeRef nodeRef) - { - if (!nodeService.hasAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE)) - { - Map aspectProperties = new HashMap(); - nodeService.addAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE, aspectProperties); - } - } - - public boolean isPatchApplied() - { - return patchApplied; - } - - // ----------------------Getters and Setters---------------------------- - - public NodeService getNodeService() - { - return nodeService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public SearchService getSearchService() - { - return searchService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public FileFolderService getFileFolderService() - { - return fileFolderService; - } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public TemplateService getTemplateService() - { - return templateService; - } - - public void setTemplateService(TemplateService templateService) - { - this.templateService = templateService; - } - - public NamespaceService getNamespaceService() - { - return namespaceService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public PermissionService getPermissionService() - { - return permissionService; - } - - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - public DictionaryService getDictionaryService() - { - return dictionaryService; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public PreferenceService getPreferenceService() - { - return preferenceService; - } - - public void setPreferenceService(PreferenceService preferenceService) - { - this.preferenceService = preferenceService; - } - - public SiteService getSiteService() - { - return siteService; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public ServiceRegistry getServiceRegistry() - { - return serviceRegistry; - } - - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - - public PatchService getPatchService() - { - return patchService; - } - - public void setPatchService(PatchService patchService) - { - this.patchService = patchService; - } - -} diff --git a/source/java/org/alfresco/repo/imap/ImapModelMessage.java b/source/java/org/alfresco/repo/imap/ImapModelMessage.java new file mode 100755 index 0000000000..c4a56d5e03 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/ImapModelMessage.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import java.io.IOException; +import java.io.InputStream; + +import javax.mail.MessagingException; +import javax.mail.util.SharedByteArrayInputStream; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Extended MimeMessage to represent a content stored in the Alfresco repository. + * + * @author Arseny Kovalchuk + */ +public class ImapModelMessage extends AbstractMimeMessage +{ + private static Log logger = LogFactory.getLog(ImapModelMessage.class); + + /** + * Constructs {@link ImapModelMessage} object. + * + * @param fileInfo - reference to the {@link FileInfo} object representing the message. + * @param imapHelper - reference to the {@link ImapHelper} object. + * @param generateBody - if {@code true} message body will be generated. + * + * @throws MessagingException if generation of the body fails. + */ + public ImapModelMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry, boolean generateBody) throws MessagingException + { + super(fileInfo, serviceRegistry, generateBody); + } + + @Override + public void buildMessageInternal() throws MessagingException + { + if (generateBody != false) + { + setMessageHeaders(); + buildImapMessage(); + } + } + + /** + * This method builds MimeMessage based on either ImapModel or ContentModel type. + * + * @param fileInfo - Source file information {@link FileInfo} + * @throws MessagingException + */ + private void buildImapMessage() throws MessagingException + { + modified = false; + saved = false; + buildRFC822Message(); + saved = true; + } + + private void buildRFC822Message() throws MessagingException + { + ContentService contentService = serviceRegistry.getContentService(); + ContentReader reader = contentService.getReader(messageFileInfo.getNodeRef(), ContentModel.PROP_CONTENT); + try + { + InputStream is = reader.getContentInputStream(); + this.parse(is); + is.close(); + is = null; + } + catch (ContentIOException e) + { + //logger.error(e); + throw new MessagingException("The error occured during message creation from content stream.", e); + } + catch (IOException e) + { + //logger.error(e); + throw new MessagingException("The error occured during message creation from content stream.", e); + } + } + + @Override + protected InputStream getContentStream() throws MessagingException + { + try + { + if (this.contentStream == null) + { + if (content != null) + { + return new SharedByteArrayInputStream(content); + } + else + { + throw new MessagingException("No content"); + } + } + return this.contentStream; + } + catch (Exception e) + { + throw new MessagingException(e.getMessage(),e); + } + } + + /* + protected void parse(InputStream inputstream) throws MessagingException + { + headers = createInternetHeaders(inputstream); + contentStream = inputstream; + } + */ + +} diff --git a/source/java/org/alfresco/repo/imap/ImapService.java b/source/java/org/alfresco/repo/imap/ImapService.java new file mode 100755 index 0000000000..60113841f7 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/ImapService.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import java.util.List; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; + + +/** + * @author Arseny Kovalchuk + */ +public interface ImapService +{ + public List listMailboxes(AlfrescoImapUser user, String mailboxPattern); + + public List listSubscribedMailboxes(AlfrescoImapUser user, String mailboxPattern); + + public AlfrescoImapFolder createMailbox(AlfrescoImapUser user, String mailboxName); + + public void deleteMailbox(AlfrescoImapUser user, String mailboxName); + + public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName); + + public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName); + + public void subscribe(AlfrescoImapUser user, String mailbox); + + public void unsubscribe(AlfrescoImapUser user, String mailbox); + + public List searchFiles(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders); + + public List searchFolders(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders, String viewMode); + + public List searchMails(NodeRef contextNodeRef, String namePattern, String viewMode, boolean includeSubFolders); + + public Flags getFlags(FileInfo messageFileInfo); + + public void setFlags(FileInfo messageFileInfo, Flags flags, boolean value); + + public void setFlag(FileInfo messageFileInfo, Flag flag, boolean value); + + public String getDefaultFromAddress(); + + public String getRepositoryTemplatePath(); + + public String getWebApplicationContextUrl(); + +} diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java new file mode 100755 index 0000000000..697705df35 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -0,0 +1,1284 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.model.ImapModel; +import org.alfresco.repo.imap.config.ImapConfigBean; +import org.alfresco.repo.imap.config.ImapConfigMountPointsBean; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.preference.PreferenceService; +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.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Utf7; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * @author Dmitry Vaserin + * @author Arseny Kovalchuk + */ +public class ImapServiceImpl implements ImapService +{ + + + private Log logger = LogFactory.getLog(ImapServiceImpl.class); + + private static final String ERROR_PERMISSION_DENIED = "imap.server.error.permission_denied"; + private static final String ERROR_FOLDER_ALREADY_EXISTS = "imap.server.error.folder_already_exist"; + private static final String ERROR_MAILBOX_NAME_IS_MANDATORY = "imap.server.error.mailbox_name_is_mandatory"; + private static final String ERROR_CANNOT_GET_A_FOLDER = "imap.server.error.cannot_get_a_folder"; + + private FileFolderService fileFolderService; + private NodeService nodeService; + private ServiceRegistry serviceRegistry; + + private Map imapConfigMountPointsBeans = Collections.emptyMap(); + private Set ignoreExtractionFolders = Collections.emptySet(); + + private String imapRoot; + private String defaultFromAddress; + private String webApplicationContextUrl = "http://localhost:8080/alfresco"; + private String repositoryTemplatePath; + private boolean extractAttachmentsEnabled = true; + + private NodeRef imapRootNodeRef; + + private final static Map qNameToFlag; + private final static Map flagToQname; + + static + { + qNameToFlag = new HashMap(); + qNameToFlag.put(ImapModel.PROP_FLAG_ANSWERED, Flags.Flag.ANSWERED); + qNameToFlag.put(ImapModel.PROP_FLAG_DELETED, Flags.Flag.DELETED); + qNameToFlag.put(ImapModel.PROP_FLAG_DRAFT, Flags.Flag.DRAFT); + qNameToFlag.put(ImapModel.PROP_FLAG_SEEN, Flags.Flag.SEEN); + qNameToFlag.put(ImapModel.PROP_FLAG_RECENT, Flags.Flag.RECENT); + qNameToFlag.put(ImapModel.PROP_FLAG_FLAGGED, Flags.Flag.FLAGGED); + + flagToQname = new HashMap(); + flagToQname.put(Flags.Flag.ANSWERED, ImapModel.PROP_FLAG_ANSWERED); + flagToQname.put(Flags.Flag.DELETED, ImapModel.PROP_FLAG_DELETED); + flagToQname.put(Flags.Flag.DRAFT, ImapModel.PROP_FLAG_DRAFT); + flagToQname.put(Flags.Flag.SEEN, ImapModel.PROP_FLAG_SEEN); + flagToQname.put(Flags.Flag.RECENT, ImapModel.PROP_FLAG_RECENT); + flagToQname.put(Flags.Flag.FLAGGED, ImapModel.PROP_FLAG_FLAGGED); + } + + + public FileFolderService getFileFolderService() + { + return fileFolderService; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public String getImapRoot() + { + return imapRoot; + } + + public void setImapRoot(String imapRoot) + { + this.imapRoot = imapRoot; + } + + public String getDefaultFromAddress() + { + return defaultFromAddress; + } + + public void setDefaultFromAddress(String defaultFromAddress) + { + this.defaultFromAddress = defaultFromAddress; + } + + public String getWebApplicationContextUrl() + { + return webApplicationContextUrl; + } + + public void setWebApplicationContextUrl(String webApplicationContextUrl) + { + this.webApplicationContextUrl = webApplicationContextUrl; + } + + public String getRepositoryTemplatePath() + { + return repositoryTemplatePath; + } + + public void setRepositoryTemplatePath(String repositoryTemplatePath) + { + this.repositoryTemplatePath = repositoryTemplatePath; + } + + public void setImapConfigMountPointsBeans(ImapConfigMountPointsBean[] imapConfigMountPointsBeans) + { + this.imapConfigMountPointsBeans = new LinkedHashMap(imapConfigMountPointsBeans.length * 2); + for (ImapConfigMountPointsBean bean : imapConfigMountPointsBeans) + { + this.imapConfigMountPointsBeans.put(bean.getName(), bean); + } + } + + /** + * Return map of imap configs. Name of config == key in the map + * + * @return map of imap configs. + */ + public Map getImapConfigMountPoints() + { + return this.imapConfigMountPointsBeans; + } + + public void setIgnoreExtractionFolders(final ImapConfigBean[] ignoreExtractionFolders) + { + this.ignoreExtractionFolders = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + { + public Set doWork() throws Exception + { + Set result = new HashSet(ignoreExtractionFolders.length * 2); + + for (ImapConfigBean bean : ignoreExtractionFolders) + { + StoreRef storeRef = new StoreRef(bean.getStore()); + + if (nodeService.exists(storeRef) == false) + { + throw new RuntimeException("No store for path: " + storeRef); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + NamespaceService namespaceService = serviceRegistry.getNamespaceService(); + String rootPathInStore = bean.getRootPath(); + + List nodeRefs = serviceRegistry.getSearchService().selectNodes(storeRootNodeRef, rootPathInStore, null, namespaceService, false); + + if (nodeRefs.size() > 1) + { + throw new RuntimeException("Multiple possible roots for : \n" + " root path: " + rootPathInStore + "\n" + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + throw new RuntimeException("No root found for : \n" + " root path: " + rootPathInStore); + } + + result.add(nodeRefs.get(0)); + } + + return result; + } + }, AuthenticationUtil.getSystemUserName()); + } + + public boolean getExtractAttachmentsEnabled() + { + return extractAttachmentsEnabled; + } + + public void setExtractAttachmentsEnabled(boolean extractAttachmentsEnabled) + { + this.extractAttachmentsEnabled = extractAttachmentsEnabled; + } + + // ---------------------- Service Methods -------------------------------- + + public void init() + { + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Void doWork() throws Exception + { + int indexOfStoreDelim = imapRoot.indexOf(StoreRef.URI_FILLER); + + if (indexOfStoreDelim == -1) + { + throw new RuntimeException("Bad path format, " + StoreRef.URI_FILLER + " not found"); + } + + indexOfStoreDelim += StoreRef.URI_FILLER.length(); + + int indexOfPathDelim = imapRoot.indexOf("/", indexOfStoreDelim); + + if (indexOfPathDelim == -1) + { + throw new RuntimeException("Bad path format, / not found"); + } + + String storePath = imapRoot.substring(0, indexOfPathDelim); + String rootPathInStore = imapRoot.substring(indexOfPathDelim); + + StoreRef storeRef = new StoreRef(storePath); + + if (nodeService.exists(storeRef) == false) + { + throw new RuntimeException("No store for path: " + storeRef); + } + + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + SearchService searchService = serviceRegistry.getSearchService(); + NamespaceService namespaceService = serviceRegistry.getNamespaceService(); + + List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPathInStore, null, namespaceService, false); + + if (nodeRefs.size() > 1) + { + throw new RuntimeException("Multiple possible roots for : \n" + " root path: " + rootPathInStore + "\n" + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + throw new RuntimeException("No root found for : \n" + " root path: " + rootPathInStore); + } + + imapRootNodeRef = nodeRefs.get(0); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + + public List listSubscribedMailboxes(AlfrescoImapUser user, String mailboxPattern) + { + mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED); + + if (logger.isDebugEnabled()) + { + logger.debug("Listing subscribed mailboxes: mailboxPattern=" + mailboxPattern); + } + mailboxPattern = getMailPathInRepo(mailboxPattern); + if (logger.isDebugEnabled()) + { + logger.debug("Listing subscribed mailboxes: mailboxPattern in alfresco=" + mailboxPattern); + } + return listMailboxes(user, mailboxPattern, true); + } + + public List listMailboxes(AlfrescoImapUser user, String mailboxPattern) + { + mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED); + + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); + } + mailboxPattern = getMailPathInRepo(mailboxPattern); + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: mailboxPattern in alfresco=" + mailboxPattern); + } + + return listMailboxes(user, mailboxPattern, false); + } + + public AlfrescoImapFolder createMailbox(AlfrescoImapUser user, String mailboxName) + { + if (mailboxName == null) + { + throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY)); + } + mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED); + if (logger.isDebugEnabled()) + { + logger.debug("Creating folder: " + mailboxName); + } + NodeRef root = getMailboxRootRef(mailboxName, user.getLogin()); + NodeRef parentNodeRef = root; // it is used for hierarhy deep search. + for (String folderName : getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER))) + { + List folders = searchFolders(parentNodeRef, folderName, false, AlfrescoImapConst.MODE_MIXED); + if (logger.isDebugEnabled()) + { + logger.debug("Trying to create folder '" + folderName + "'"); + } + if (folders.size() == 0) + { + // folder doesn't exist + AccessStatus status = serviceRegistry.getPermissionService().hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN); + if (status == AccessStatus.DENIED) + { + throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED); + } + FileInfo mailFolder = serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER); + return new AlfrescoImapFolder(user.getQualifiedMailboxName(), + mailFolder, + folderName, + getViewMode(mailboxName), + root, + getMountPointName(mailboxName), + isExtractionEnabled(mailFolder.getNodeRef()), + serviceRegistry); + } + else + { + // folder already exists + if (logger.isDebugEnabled()) + { + logger.debug("Folder '" + folderName + "' already exists"); + } + // next search from new parent + parentNodeRef = folders.get(0).getNodeRef(); + } + } + throw new AlfrescoRuntimeException(ERROR_FOLDER_ALREADY_EXISTS); + } + + public void deleteMailbox(AlfrescoImapUser user, String mailboxName) + { + if (mailboxName == null) + { + throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY)); + } + if (logger.isDebugEnabled()) + { + logger.debug("Deleting folder: mailboxName=" + mailboxName); + } + + AlfrescoImapFolder folder = getFolder(user, mailboxName); + NodeRef nodeRef = folder.getFolderInfo().getNodeRef(); + + List childFolders = searchFolders(nodeRef, "*", false, folder.getViewMode()); + + if (childFolders.isEmpty()) + { + folder.signalDeletion(); + // Delete child folders and messages + fileFolderService.delete(nodeRef); + } + else + { + if (folder.isSelectable()) + { + // Delete all messages for this folder + // Don't delete subfolders and their messages + List messages = searchFiles(nodeRef, "*", false); + for (FileInfo message : messages) + { + fileFolderService.delete(message.getNodeRef()); + } + nodeService.addAspect(nodeRef, ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE, null); + } + else + { + throw new AlfrescoRuntimeException(mailboxName + " - Can't delete a non-selectable store with children."); + } + } + } + + public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName) + { + if (oldMailboxName == null || newMailboxName == null) + { + throw new IllegalArgumentException(ERROR_MAILBOX_NAME_IS_MANDATORY); + } + oldMailboxName = Utf7.decode(oldMailboxName, Utf7.UTF7_MODIFIED); + newMailboxName = Utf7.decode(newMailboxName, Utf7.UTF7_MODIFIED); + if (logger.isDebugEnabled()) + { + logger.debug("Renaming folder oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName); + } + + AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName); + + NodeRef root = getMailboxRootRef(oldMailboxName, user.getLogin()); + String[] folderNames = getMailPathInRepo(newMailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); + String folderName = null; + NodeRef parentNodeRef = root; // initial root for search + try + { + for (int i=0; i < folderNames.length; i++) + { + folderName = folderNames[i]; + if (i == (folderNames.length - 1)) // is it the last element + { + if (oldMailboxName.equalsIgnoreCase(AlfrescoImapConst.INBOX_NAME)) + { + // If you trying to rename INBOX + // - just copy it to another folder with new name + // and leave INBOX (with children) intact. + fileFolderService.copy(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName); + } + else + { + fileFolderService.move(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName); + } + } + else // not last element than checks if it exists and creates if doesn't + { + List folders = searchFolders(parentNodeRef, folderName, false, sourceNode.getViewMode()); + if (folders.size() == 0) + { + // check creation permission + AccessStatus status = serviceRegistry.getPermissionService().hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN); + if (status == AccessStatus.DENIED) + { + throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Creating folder '" + folderName + "'"); + } + serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER); + } + else + { + parentNodeRef = folders.get(0).getNodeRef(); + if (logger.isDebugEnabled()) + { + logger.debug("Folder '" + folderName + "' already exists"); + } + } + } + } + } + catch (Exception e) + { + if (e instanceof AlfrescoRuntimeException) + { + throw (AlfrescoRuntimeException)e; + } + else + { + throw new AlfrescoRuntimeException(e.getMessage(), e); + } + } + } + + public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName) + { + mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED); + if (logger.isDebugEnabled()) + { + logger.debug("Getting folder '" + mailboxName + "'"); + } + // If MailFolder object is used to obtain hierarchy delimiter by LIST command: + // Example: + // C: 2 list "" "" + // S: * LIST () "." "" + // S: 2 OK LIST completed. + if ("".equals(mailboxName)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Request for the hierarchy delimiter"); + } + return new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry); + } + + NodeRef root = getMailboxRootRef(mailboxName, user.getLogin()); + String mountPointName = getMountPointName(mailboxName); + NodeRef nodeRef = root; // initial top folder + String viewMode = getViewMode(mailboxName); + + String[] folderNames = getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); + + for (int i = 0; i < folderNames.length; i++) + { + if (logger.isDebugEnabled()) + { + logger.debug("Processing of " + folderNames[i]); + } + List folderList = searchFolders(nodeRef, folderNames[i], false, viewMode); + if (folderList.isEmpty()) + { + return new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry); + } + FileInfo folderFileInfo = folderList.get(0); // we need the only one + if (i == (folderNames.length - 1)) // is last + { + return new AlfrescoImapFolder(user.getQualifiedMailboxName(), + folderFileInfo, + folderFileInfo.getName(), + viewMode, + root, + mountPointName, + isExtractionEnabled(folderFileInfo.getNodeRef()), + serviceRegistry); + } + else + { + nodeRef = folderFileInfo.getNodeRef(); // next parent + } + } + + throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[]{mailboxName}); + } + + /** + * Search for mailboxes in specified context + * + * @param contextNodeRef context folder for search + * @param namePattern name pattern for search + * @param includeSubFolders include SubFolders + * @param isVirtualView is folder in "Virtual" View + * @return list of mailboxes + */ + public List searchFolders(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders, String viewMode) + { + List result = fileFolderService.search(contextNodeRef, namePattern, false, true, includeSubFolders); + if (viewMode.equals(AlfrescoImapConst.MODE_VIRTUAL) || viewMode.equals(AlfrescoImapConst.MODE_MIXED)) + { + List nonFavSites = getNonFavouriteSites(getCurrentUser()); + for (SiteInfo siteInfo : nonFavSites) + { + FileInfo nonFavSite = fileFolderService.getFileInfo(siteInfo.getNodeRef()); + List siteChilds = fileFolderService.search(nonFavSite.getNodeRef(), namePattern, false, true, true); + result.removeAll(siteChilds); + result.remove(nonFavSite); + } + + } + else + { + // Remove folders from Sites + List sites = serviceRegistry.getSiteService().listSites(getCurrentUser()); + for (SiteInfo siteInfo : sites) + { + List siteChilds = fileFolderService.search(siteInfo.getNodeRef(), namePattern, false, true, true); + result.removeAll(siteChilds); + //remove site + result.remove(fileFolderService.getFileInfo(siteInfo.getNodeRef())); + } + + } + return result; + } + + /** + * Search for files in specified context + * + * @param contextNodeRef context folder for search + * @param namePattern name pattern for search + * @param searchType type for search + * @param includeSubFolders include SubFolders + * @return list of files with specifed type + */ + public List searchFiles(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders) + { + return fileFolderService.search(contextNodeRef, namePattern, true, false, includeSubFolders); + } + + /** + * Search for emails in specified folder depend on view mode. + * + * @param contextNodeRef context folder for search + * @param namePattern name pattern for search + * @param viewMode context folder view mode + * @param includeSubFolders includeSubFolders + * @return list of emails that context folder contains. + */ + public List searchMails(NodeRef contextNodeRef, String namePattern, String viewMode, boolean includeSubFolders) + { + + List result = new LinkedList(); + List searchResult = fileFolderService.search(contextNodeRef, namePattern, true, false, includeSubFolders); + if (viewMode.equals(AlfrescoImapConst.MODE_MIXED)) + { + return searchResult; + } + else if (viewMode.equals(AlfrescoImapConst.MODE_ARCHIVE)) + { + for (FileInfo fileInfo : searchResult) + { + if (nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) + { + result.add(fileInfo); + } + + } + } + else if (viewMode.equals(AlfrescoImapConst.MODE_VIRTUAL)) + { + for (FileInfo fileInfo : searchResult) + { + if (!nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) + { + result.add(fileInfo); + } + + } + + } + + return result; + } + + public void subscribe(AlfrescoImapUser user, String mailbox) + { + if (logger.isDebugEnabled()) + { + logger.debug("Subscribing: " + mailbox); + } + AlfrescoImapFolder mailFolder = getFolder(user, mailbox); + nodeService.removeAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); + } + + public void unsubscribe(AlfrescoImapUser user, String mailbox) + { + if (logger.isDebugEnabled()) + { + logger.debug("Unsubscribing: " + mailbox); + } + AlfrescoImapFolder mailFolder = getFolder(user, mailbox); + nodeService.addAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED, null); + } + + /** + * Return flags that belong to the specified imap folder. + * + * @param messageInfo imap folder info. + * @return flags. + */ + public synchronized Flags getFlags(FileInfo messageInfo) + { + Flags flags = new Flags(); + checkForFlaggableAspect(messageInfo.getNodeRef()); + Map props = nodeService.getProperties(messageInfo.getNodeRef()); + + for (QName key : qNameToFlag.keySet()) + { + Boolean value = (Boolean) props.get(key); + if (value != null && value) + { + flags.add(qNameToFlag.get(key)); + } + } + return flags; + } + + /** + * Set flags to the specified imapFolder. + * + * @param messageInfo FileInfo of imap Folder. + * @param flags flags to set. + * @param value value to set. + */ + public synchronized void setFlags(FileInfo messageInfo, Flags flags, boolean value) + { + checkForFlaggableAspect(messageInfo.getNodeRef()); + for (Flags.Flag flag : flags.getSystemFlags()) + { + setFlag(messageInfo, flag, value); + } + } + + /** + * Set flags to the specified imapFolder. + * + * @param messageInfo FileInfo of imap Folder + * @param flag flag to set. + * @param value value value to set. + */ + public void setFlag(FileInfo messageInfo, Flag flag, boolean value) + { + checkForFlaggableAspect(messageInfo.getNodeRef()); + nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), value); + } + + /** + * Depend on listSubscribed param, list Mailboxes or list subscribed Mailboxes + */ + private List listMailboxes(AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed) + { + List result = new LinkedList(); + + Map mountPoints = getMountPoints(); + Map imapConfigs = getImapConfigMountPoints(); + + NodeRef mountPoint; + + // List mailboxes that are in mount points + for (String mountPointName : mountPoints.keySet()) + { + + mountPoint = mountPoints.get(mountPointName); + FileInfo mountPointFileInfo = fileFolderService.getFileInfo(mountPoint); + NodeRef mountParent = nodeService.getParentAssocs(mountPoint).get(0).getParentRef(); + String viewMode = imapConfigs.get(mountPointName).getMode(); + + if (!mailboxPattern.equals("*")) + { + mountPoint = mountParent; + } + + List folders = listFolder(mountPoint, mountPoint, user, mailboxPattern, listSubscribed, viewMode); + if (folders != null) + { + for (AlfrescoImapFolder mailFolder : folders) + { + AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder; + folder.setMountPointName(mountPointName); + folder.setViewMode(viewMode); + folder.setMountParent(mountParent); + } + result.addAll(folders); + } + + // Add mount point to the result list + if (mailboxPattern.equals("*")) + { + if ((listSubscribed && isSubscribed(mountPointFileInfo, user.getLogin())) || (!listSubscribed)) + { + result.add(new AlfrescoImapFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, viewMode, mountParent, mountPointName, isExtractionEnabled(mountPointFileInfo.getNodeRef()), serviceRegistry)); + } + // \NoSelect + else if (listSubscribed && hasSubscribedChild(mountPointFileInfo, user.getLogin(), viewMode)) + { + result.add(new AlfrescoImapFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, viewMode, mountParent, mountPointName, serviceRegistry, false, isExtractionEnabled(mountPointFileInfo.getNodeRef()))); + } + } + + } + + // List mailboxes that are in user IMAP Home + NodeRef root = getUserImapHomeRef(user.getLogin()); + List imapFolders = listFolder(root, root, user, mailboxPattern, listSubscribed, AlfrescoImapConst.MODE_ARCHIVE); + + if (imapFolders != null) + { + for (AlfrescoImapFolder mailFolder : imapFolders) + { + AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder; + folder.setViewMode(AlfrescoImapConst.MODE_ARCHIVE); + folder.setMountParent(root); + } + result.addAll(imapFolders); + } + + return result; + + } + + private List listFolder(NodeRef mailboxRoot, NodeRef root, AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed, String viewMode) + { + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); + } + + int index = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + + String name = null; + String remainName = null; + + if (index < 0) + { + name = mailboxPattern; + } + else + { + name = mailboxPattern.substring(0, index); + remainName = mailboxPattern.substring(index + 1); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: name=" + name); + } + + if (index < 0) + { + if ("*".equals(name)) + { + Collection list = searchFolders(root, name, true, viewMode); + if (listSubscribed) + { + list = getSubscribed(list, user.getLogin()); + } + + if (list.size() > 0) + { + return createMailFolderList(user, list, mailboxRoot); + } + return null; + } + else if (name.endsWith("*")) + { + List fullList = new LinkedList(); + List list = searchFolders(root, name.replace('%', '*'), false, viewMode); + Collection subscribedList = list; + if (listSubscribed) + { + subscribedList = getSubscribed(list, user.getLogin()); + } + + if (list.size() > 0) + { + fullList.addAll(subscribedList); + for (FileInfo fileInfo : list) + { + List childList = searchFolders(fileInfo.getNodeRef(), "*", true, viewMode); + if (listSubscribed) + { + fullList.addAll(getSubscribed(childList, user.getLogin())); + } + else + { + fullList.addAll(childList); + } + } + return createMailFolderList(user, fullList, mailboxRoot); + } + return null; + } + else if ("%".equals(name)) + { + List list = searchFolders(root, "*", false, viewMode); + LinkedList subscribedList = new LinkedList(); + + if (listSubscribed) + { + for (FileInfo fileInfo : list) + { + if (isSubscribed(fileInfo, user.getLogin())) + { + // folderName, viewMode, mountPointName will be setted in listMailboxes() method + subscribedList.add(new AlfrescoImapFolder(user.getQualifiedMailboxName(), fileInfo, null, null, mailboxRoot, null, isExtractionEnabled(fileInfo.getNodeRef()), serviceRegistry)); + } + // \NoSelect + else if (hasSubscribedChild(fileInfo, user.getLogin(), viewMode)) + { + // folderName, viewMode, mountPointName will be setted in listMailboxes() method + subscribedList.add(new AlfrescoImapFolder(user.getQualifiedMailboxName(), fileInfo, null, null, mailboxRoot, null, serviceRegistry, false, isExtractionEnabled(fileInfo.getNodeRef()))); + } + } + } + else + { + return createMailFolderList(user, list, mailboxRoot); + } + + return subscribedList; + } + else if (name.contains("%") || name.contains("*")) + { + List list = searchFolders(root, name.replace('%', '*'), false, viewMode); + Collection subscribedList = list; + if (listSubscribed) + { + subscribedList = getSubscribed(list, user.getLogin()); + } + + if (subscribedList.size() > 0) + { + return createMailFolderList(user, subscribedList, mailboxRoot); + } + return null; + } + else + { + List list = searchFolders(root, name, false, viewMode); + Collection subscribedList = list; + if (listSubscribed) + { + subscribedList = getSubscribed(list, user.getLogin()); + } + + if (subscribedList.size() > 0) + { + return createMailFolderList(user, subscribedList, mailboxRoot); + } + return null; + } + } + + // If (index != -1) this is not the last level + List result = new LinkedList(); + + List list = searchFolders(root, name.replace('%', '*'), false, viewMode); + for (FileInfo folder : list) + { + Collection childFolders = listFolder(mailboxRoot, folder.getNodeRef(), user, remainName, listSubscribed, viewMode); + + if (childFolders != null) + { + result.addAll(childFolders); + } + } + + if (result.isEmpty()) + { + return null; + } + + return result; + } + + /** + * Convert mailpath from IMAP client representation to the alfresco representation view. (e.g. with default settings "getMailPathInRepo(Repository_virtual.Imap Home)" will + * return "Company Home.Imap Home") + * + * @param mailPath mailbox path in IMAP client + * @return mailbox path in alfresco + */ + private String getMailPathInRepo(String mailPath) + { + String rootFolder; + String remain = ""; + int index = mailPath.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (index > 0) + { + rootFolder = mailPath.substring(0, index); + remain = mailPath.substring(index); + } + else + { + rootFolder = mailPath; + } + Map imapConfigs = getImapConfigMountPoints(); + if (imapConfigs.keySet().contains(rootFolder)) + { + Map mountPoints = getMountPoints(); + NodeRef rootRef = mountPoints.get(rootFolder); + String rootName = nodeService.getProperty(rootRef, ContentModel.PROP_NAME).toString(); + + return rootName + remain; + } + else + { + return mailPath; + } + } + + /** + * Return mount point name, which was specified in imap-config.xml for the current mailbox. + * + * @param mailboxName mailbox name in IMAP client. + * @return mount point name or null. + */ + private String getMountPointName(String mailboxName) + { + String rootFolder; + int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (index > 0) + { + rootFolder = mailboxName.substring(0, index); + } + else + { + rootFolder = mailboxName; + } + Map imapConfigs = getImapConfigMountPoints(); + if (imapConfigs.keySet().contains(rootFolder)) + { + return rootFolder; + } + else + { + return null; + } + + } + + /** + * Map of mount points. Name of mount point == key in the map. + * + * @return Map of mount points. + */ + private Map getMountPoints() + { + Map imapConfigs = getImapConfigMountPoints(); + Map mountPoints = new HashMap(); + SearchService searchService = serviceRegistry.getSearchService(); + for (ImapConfigMountPointsBean config : imapConfigs.values()) + { + // Get node reference + StoreRef store = new StoreRef(config.getStore()); + ResultSet rs = searchService.query(store, SearchService.LANGUAGE_XPATH, config.getRootPath()); + if (rs.length() == 0) + { + logger.warn("Didn't find " + config.getName()); + } + else + { + NodeRef nodeRef = rs.getNodeRef(0); + mountPoints.put(config.getName(), nodeRef); + } + rs.close(); + } + return mountPoints; + } + + /** + * Get root reference for the specified mailbox + * + * @param mailboxName mailbox name in IMAP client. + * @param userName + * @return + */ + private NodeRef getMailboxRootRef(String mailboxName, String userName) + { + String rootFolder; + int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (index > 0) + { + rootFolder = mailboxName.substring(0, index); + } + else + { + rootFolder = mailboxName; + } + + Map imapConfigs = getImapConfigMountPoints(); + if (imapConfigs.keySet().contains(rootFolder)) + { + Map mountPoints = getMountPoints(); + NodeRef mountRef = mountPoints.get(rootFolder); + return nodeService.getParentAssocs(mountRef).get(0).getParentRef(); + } + else + { + return getUserImapHomeRef(userName); + } + } + + /** + * @param userName user name + * @return user IMAP home reference and create it if it doesn't exist. + */ + private NodeRef getUserImapHomeRef(final String userName) + { + NodeRef userHome = fileFolderService.searchSimple(imapRootNodeRef, userName); + if (userHome == null) + { + // create user home + userHome = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public NodeRef doWork() throws Exception + { + NodeRef result = fileFolderService.create(imapRootNodeRef, userName, ContentModel.TYPE_FOLDER).getNodeRef(); + nodeService.setProperty(result, ContentModel.PROP_DESCRIPTION, userName); + // create inbox + fileFolderService.create(result, AlfrescoImapConst.INBOX_NAME, ContentModel.TYPE_FOLDER); + return result; + } + }, AuthenticationUtil.getSystemUserName()); + } + return userHome; + } + + private boolean isSubscribed(FileInfo fileInfo, String userName) + { + return !nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); + // This is a multiuser support. Commented due new requirements + + // Map properties = fileInfo.getProperties(); + // String subscribedList = (String) properties.get(ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED); + // if (subscribedList == null) + // { + // return false; + // } + // else + // { + // return subscribedList.contains(imapHelper.formatUserEntry(userName)); + // } + + } + + private Collection getSubscribed(Collection list, String userName) + { + Collection result = new LinkedList(); + + for (FileInfo folderInfo : list) + { + if (isSubscribed(folderInfo, userName)) + { + result.add(folderInfo); + } + } + + return result; + } + + private boolean hasSubscribedChild(FileInfo parent, String userName, String viewMode) + { + List list = searchFolders(parent.getNodeRef(), "*", true, viewMode); + + for (FileInfo fileInfo : list) + { + if (isSubscribed(fileInfo, userName)) + { + return true; + } + } + + return false; + } + + private List createMailFolderList(AlfrescoImapUser user, Collection list, NodeRef imapUserHomeRef) + { + List result = new LinkedList(); + + for (FileInfo folderInfo : list) + { + // folderName, viewMode, mountPointName will be setted in listSubscribedMailboxes() method + result.add(new AlfrescoImapFolder(user.getQualifiedMailboxName(), folderInfo, null, null, imapUserHomeRef, null, isExtractionEnabled(folderInfo.getNodeRef()), serviceRegistry)); + } + + return result; + + } + + /** + * Return view mode ("virtual", "archive" or "mixed") for specified mailbox. + * + * @param mailboxName name of the mailbox in IMAP client. + * @return view mode of the specified mailbox. + */ + private String getViewMode(String mailboxName) + { + String rootFolder; + int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (index > 0) + { + rootFolder = mailboxName.substring(0, index); + } + else + { + rootFolder = mailboxName; + } + Map imapConfigs = getImapConfigMountPoints(); + if (imapConfigs.keySet().contains(rootFolder)) + { + return imapConfigs.get(rootFolder).getMode(); + } + else + { + return AlfrescoImapConst.MODE_ARCHIVE; + } + } + + private String getCurrentUser() + { + return AuthenticationUtil.getFullyAuthenticatedUser(); + } + + /** + * Return list of sites, that belong to the specified user and not marked as "Imap favourite" + * + * @param userName name of user + * @return List of nonFavourite sites. + */ + private List getNonFavouriteSites(String userName) + { + List nonFavSites = new LinkedList(); + PreferenceService preferenceService = (PreferenceService) serviceRegistry.getService(QName.createQName(NamespaceService.ALFRESCO_URI, "PreferenceService")); + Map prefs = preferenceService.getPreferences(userName, AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES); + List sites = serviceRegistry.getSiteService().listSites(userName); + for (SiteInfo siteInfo : sites) + { + String key = AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES + "." + siteInfo.getShortName(); + Boolean isImapFavourite = (Boolean) prefs.get(key); + if (isImapFavourite == null || !isImapFavourite) + { + nonFavSites.add(siteInfo); + } + } + + return nonFavSites; + } + + private void checkForFlaggableAspect(NodeRef nodeRef) + { + if (!nodeService.hasAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE)) + { + Map aspectProperties = new HashMap(); + nodeService.addAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE, aspectProperties); + } + } + + private boolean isExtractionEnabled(NodeRef nodeRef) + { + return extractAttachmentsEnabled && !ignoreExtractionFolders.contains(nodeRef); + } + + /** + * This method should returns a unique identifier of Alfresco server. The possible UID may be calculated based on IP address, Server port, MAC address, Web Application context. + * This UID should be parseable into initial components. This necessary for the implementation of the following case: If the message being copied (e.g. drag-and-drop) between + * two different Alfresco accounts in the IMAP client, we must unambiguously identify from which Alfresco server this message being copied. The message itself does not contain + * content data, so we must download it from the initial server (e.g. using download content servlet) and save it into destination repository. + * + * @return String representation of unique identifier of Alfresco server + */ + public String getAlfrescoServerUID() + { + // TODO Implement as javadoc says. + return "Not-Implemented"; + } + +} diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java b/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java new file mode 100755 index 0000000000..06a9dfe96e --- /dev/null +++ b/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java @@ -0,0 +1,417 @@ +package org.alfresco.repo.imap; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.model.ImapModel; +import org.alfresco.repo.importer.ACPImportPackageHandler; +import org.alfresco.repo.node.integrity.IntegrityChecker; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +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.SearchService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; + +public class ImapServiceImplTest extends TestCase +{ + + private static final String USER_NAME = "admin"; + private static final String USER_PASSWORD = "admin"; + + private static final String MAILBOX_NAME_A = "mailbox_a"; + private static final String MAILBOX_NAME_B = "mailbox_b"; + private static final String MAILBOX_PATTERN = "mailbox*"; + private static final String FOLDER_PATTERN = "___-___folder*"; + private static final String FILE_PATTERN = "___-___file*"; + + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + private TransactionService transactionService; + private NodeService nodeService; + private ImporterService importerService; + private PersonService personService; + private AuthenticationService authenticationService; + private PermissionService permissionService; + private SearchService searchService; + private NamespaceService namespaceService; + + private AlfrescoImapUser user; + private ImapService imapService; + private UserTransaction txn; + + private NodeRef companyHomeNodeRef; + + private Flags flags; + + String anotherUserName; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + transactionService = serviceRegistry.getTransactionService(); + nodeService = serviceRegistry.getNodeService(); + importerService = serviceRegistry.getImporterService(); + personService = serviceRegistry.getPersonService(); + authenticationService = serviceRegistry.getAuthenticationService(); + permissionService = serviceRegistry.getPermissionService(); + imapService = serviceRegistry.getImapService(); + searchService = serviceRegistry.getSearchService(); + namespaceService = serviceRegistry.getNamespaceService(); + + flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.FLAGGED); + flags.add(Flags.Flag.ANSWERED); + flags.add(Flags.Flag.DELETED); + + // start the transaction + txn = transactionService.getUserTransaction(); + txn.begin(); + authenticationService.authenticate(USER_NAME, USER_PASSWORD.toCharArray()); + + // downgrade integrity + IntegrityChecker.setWarnInTransaction(); + + anotherUserName = "user" + System.currentTimeMillis(); + + PropertyMap testUser = new PropertyMap(); + testUser.put(ContentModel.PROP_USERNAME, anotherUserName); + testUser.put(ContentModel.PROP_FIRSTNAME, anotherUserName); + testUser.put(ContentModel.PROP_LASTNAME, anotherUserName); + testUser.put(ContentModel.PROP_EMAIL, anotherUserName + "@alfresco.com"); + testUser.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + personService.createPerson(testUser); + + // create the ACEGI Authentication instance for the new user + authenticationService.createAuthentication(anotherUserName, anotherUserName.toCharArray()); + + user = new AlfrescoImapUser(anotherUserName + "@alfresco.com", anotherUserName, anotherUserName); + + String storePath = "workspace://SpacesStore"; + String companyHomePathInStore = "/app:company_home"; + + StoreRef storeRef = new StoreRef(storePath); + + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(storeRootNodeRef, companyHomePathInStore, null, namespaceService, false); + companyHomeNodeRef = nodeRefs.get(0); + + /* + * Importing test folders: + * + * "Company Home" contains: "___-___folder_a" + * + * "___-___folder_a" contains: "___-___folder_a_a", + * "___-___file_a", + * "Message_485.eml" (this is IMAP Message) + * + * "___-___folder_a_a" contains: "____-____file_a_a" + * + */ + importInternal("test-resources/imapservice_test_folder_a.acp", companyHomeNodeRef); + + reauthenticate(anotherUserName, anotherUserName); + } + + public void tearDown() throws Exception + { + try + { + txn.rollback(); + } + catch (Throwable e) + { + e.printStackTrace(); + } + } + + private void importInternal(String acpName, NodeRef space) + throws IOException + { + ClassPathResource acpResource = new ClassPathResource(acpName); + ACPImportPackageHandler acpHandler = new ACPImportPackageHandler(acpResource.getFile(), null); + Location importLocation = new Location(space); + importerService.importView(acpHandler, importLocation, null, null); + } + + private boolean checkMailbox(AlfrescoImapUser user, String mailboxName) + { + AlfrescoImapFolder mailFolder = (AlfrescoImapFolder)imapService.getFolder(user, mailboxName); + + if (mailFolder.getFolderInfo() == null) + { + return false; + } + return true; + } + + private boolean checkSubscribedMailbox(AlfrescoImapUser user, String mailboxName) + { + List aifs = imapService.listSubscribedMailboxes(user, mailboxName); + boolean present = false; + for (AlfrescoImapFolder aif : aifs) + { + if (aif.getName().equals(mailboxName)) + { + present = true; + break; + } + } + return present; + } + + private void reauthenticate(String name, String password) + { + authenticationService.invalidateTicket(authenticationService.getCurrentTicket()); + authenticationService.clearCurrentSecurityContext(); + authenticationService.authenticate(name, password.toCharArray()); + } + + public void testGetFolder() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + assertTrue(checkMailbox(user, MAILBOX_NAME_A)); + } + + public void testListMailbox() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.createMailbox(user, MAILBOX_NAME_B); + List mf = imapService.listMailboxes(user, MAILBOX_PATTERN); + assertEquals(mf.size(), 2); + } + + public void testListSubscribedMailbox() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.createMailbox(user, MAILBOX_NAME_B); + imapService.subscribe(user, MAILBOX_NAME_A); + imapService.subscribe(user, MAILBOX_NAME_B); + List aif = imapService.listSubscribedMailboxes(user, MAILBOX_PATTERN); + assertEquals(aif.size(), 2); + } + + public void testCreateMailbox() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + assertTrue("Mailbox isn't created", checkMailbox(user, MAILBOX_NAME_A)); + } + + public void testDuplicateMailboxes() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + try + { + imapService.createMailbox(user, MAILBOX_NAME_A); + fail("Duplicate Mailbox was created"); + } + catch (AlfrescoRuntimeException e) + { + // expected + } + + } + + public void testRenameMailbox() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); + assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A)); + assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B)); + } + + public void testRenameMailboxDuplicate() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.createMailbox(user, MAILBOX_NAME_B); + try + { + imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); + fail("Mailbox was renamed to existing one but shouldn't"); + } + catch (AlfrescoRuntimeException e) + { + // expected + } + } + + public void testDeleteMailbox() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_B); + imapService.deleteMailbox(user, MAILBOX_NAME_B); + assertFalse("Can't delete mailbox", checkMailbox(user, MAILBOX_NAME_B)); + } + + public void testSearchFoldersInArchive() throws Exception + { + List fi = imapService.searchFolders(companyHomeNodeRef, FOLDER_PATTERN, true, AlfrescoImapConst.MODE_ARCHIVE); + assertNotNull("Can't find folders in Archive Mode", fi); + assertEquals("Can't find folders in Archive Mode", fi.size(), 2); + + fi = imapService.searchFolders(companyHomeNodeRef, FOLDER_PATTERN, false, AlfrescoImapConst.MODE_ARCHIVE); + assertNotNull("Can't find folders in Archive Mode", fi); + assertEquals("Can't find folders in Archive Mode", fi.size(), 1); + } + + public void testSearchFoldersInVirtual() throws Exception + { + List fi = imapService.searchFolders(companyHomeNodeRef, FOLDER_PATTERN, true, AlfrescoImapConst.MODE_VIRTUAL); + assertNotNull("Can't find folders in Virtual Mode", fi); + assertEquals("Can't find folders in Virtual Mode", fi.size(), 2); + + fi = imapService.searchFolders(companyHomeNodeRef, FOLDER_PATTERN, false, AlfrescoImapConst.MODE_VIRTUAL); + assertNotNull("Can't find folders in Virtual Mode", fi); + assertEquals("Can't find folders in Virtual Mode", fi.size(), 1); + } + + public void testSearchFoldersInMixed() throws Exception + { + List fi = imapService.searchFolders(companyHomeNodeRef, FOLDER_PATTERN, true, AlfrescoImapConst.MODE_MIXED); + assertNotNull("Can't find folders in Mixed Mode", fi); + assertEquals("Can't find folders in Mixed Mode", fi.size(), 2); + + fi = imapService.searchFolders(companyHomeNodeRef, FOLDER_PATTERN, false, AlfrescoImapConst.MODE_MIXED); + assertNotNull("Can't find folders in Mixed Mode", fi); + assertEquals("Can't find folders in Mixed Mode", fi.size(), 1); + } + + public void testSearchFiles() throws Exception + { + List fi = imapService.searchFiles(companyHomeNodeRef, FILE_PATTERN, true); + assertNotNull(fi); + assertTrue(fi.size() > 0); + } + + public void testSearchMails() throws Exception + { + List fi = imapService.searchMails(companyHomeNodeRef, "*", AlfrescoImapConst.MODE_MIXED, true); + assertNotNull(fi); + assertTrue(fi.size() > 0); + } + + public void testSubscribe() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + + imapService.subscribe(user, MAILBOX_NAME_A); + assertTrue("Can't subscribe mailbox", checkSubscribedMailbox(user, MAILBOX_NAME_A)); + } + + public void testUnsubscribe() throws Exception + { + imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.subscribe(user, MAILBOX_NAME_A); + imapService.unsubscribe(user, MAILBOX_NAME_A); + assertFalse("Can't unsubscribe mailbox", checkSubscribedMailbox(user, MAILBOX_NAME_A)); + } + + private void setFlags(FileInfo messageFileInfo) throws Exception + { + imapService.setFlags(messageFileInfo, flags, true); + NodeRef messageNodeRef = messageFileInfo.getNodeRef(); + Map props = nodeService.getProperties(messageNodeRef); + + assertTrue("Can't set SEEN flag", props.containsKey(ImapModel.PROP_FLAG_SEEN)); + assertTrue("Can't set FLAGGED flag", props.containsKey(ImapModel.PROP_FLAG_FLAGGED)); + assertTrue("Can't set ANSWERED flag", props.containsKey(ImapModel.PROP_FLAG_ANSWERED)); + assertTrue("Can't set DELETED flag", props.containsKey(ImapModel.PROP_FLAG_DELETED)); + } + + public void testSetFlags() throws Exception + { + List fis = imapService.searchMails(companyHomeNodeRef, "*", AlfrescoImapConst.MODE_ARCHIVE, true); + if (fis != null && fis.size() > 0) + { + FileInfo messageFileInfo = fis.get(0); + try + { + setFlags(messageFileInfo); + fail("Can't set flags"); + } + catch (Exception e) + { + if (e instanceof AccessDeniedException) + { + // expected + } + else + { + throw e; + } + } + + reauthenticate(USER_NAME, USER_PASSWORD); + + permissionService.setPermission(companyHomeNodeRef, anotherUserName, PermissionService.WRITE, true); + + reauthenticate(anotherUserName, anotherUserName); + + setFlags(messageFileInfo); + } + } + + public void testSetFlag() throws Exception + { + List fis = imapService.searchMails(companyHomeNodeRef, "*", AlfrescoImapConst.MODE_ARCHIVE, true); + if (fis != null && fis.size() > 0) + { + FileInfo messageFileInfo = fis.get(0); + + reauthenticate(USER_NAME, USER_PASSWORD); + + permissionService.setPermission(companyHomeNodeRef, anotherUserName, PermissionService.WRITE, true); + + reauthenticate(anotherUserName, anotherUserName); + + imapService.setFlag(messageFileInfo, Flags.Flag.RECENT, true); + + Serializable prop = nodeService.getProperty(messageFileInfo.getNodeRef(), ImapModel.PROP_FLAG_RECENT); + assertNotNull("Can't set RECENT flag", prop); + } + } + + public void testGetFlags() throws Exception + { + List fis = imapService.searchMails(companyHomeNodeRef, "*", AlfrescoImapConst.MODE_ARCHIVE, true); + if (fis != null && fis.size() > 0) + { + FileInfo messageFileInfo = fis.get(0); + + reauthenticate(USER_NAME, USER_PASSWORD); + + permissionService.setPermission(companyHomeNodeRef, anotherUserName, PermissionService.WRITE, true); + + imapService.setFlags(messageFileInfo, flags, true); + + reauthenticate(anotherUserName, anotherUserName); + + Flags fl = imapService.getFlags(messageFileInfo); + assertTrue(fl.contains(flags)); + } + } +} diff --git a/source/java/org/alfresco/repo/imap/IncomingImapMessage.java b/source/java/org/alfresco/repo/imap/IncomingImapMessage.java new file mode 100755 index 0000000000..aa15c53ef9 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/IncomingImapMessage.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeUtility; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ImapModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +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.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.FileCopyUtils; + +/** + * This class is used to serve incoming IMAP message. E.g. when message is copied /moved into some IMAP older. + * + * @author Arseny Kovalchuk + */ +public class IncomingImapMessage extends AbstractMimeMessage +{ + private Log logger = LogFactory.getLog(IncomingImapMessage.class); + private ContentReader contentReader; + /** + * Constructs {@link IncomingImapMessage} object based on {@link MimeMessage} + * + * @param fileInfo - reference to the {@link FileInfo} object representing the message. + * @param imapHelper - reference to the {@link ImapHelper} object. + * @param message - {@link MimeMessage} + * @throws MessagingException + */ + public IncomingImapMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry, MimeMessage message) throws MessagingException + { + super(Session.getDefaultInstance(new Properties())); + this.wrappedMessage = message; // temporary save it and then destroyed in writeContent() (to avoid memory leak with byte[] MimeMessage.content field) + this.buildMessage(fileInfo, serviceRegistry); + } + + @Override + public void buildMessageInternal() throws MessagingException + { + setMessageHeaders(); + // Add Imap Content Aspect with properties + NodeService nodeService = serviceRegistry.getNodeService(); + nodeService.addAspect(this.messageFileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT, null); + // Write content + writeContent(); + imapService.setFlags(messageFileInfo, flags, true); + + final NodeRef nodeRef = messageFileInfo.getNodeRef(); + Map newProperties = new HashMap(); + newProperties.put(ImapModel.PROP_MESSAGE_FROM, InternetAddress.toString(this.getFrom())); + newProperties.put(ImapModel.PROP_MESSAGE_TO, InternetAddress.toString(this.getRecipients(RecipientType.TO))); + newProperties.put(ImapModel.PROP_MESSAGE_CC, InternetAddress.toString(this.getRecipients(RecipientType.CC))); + newProperties.put(ImapModel.PROP_MESSAGE_ID, this.getMessageID()); + String[] threadIndexes = this.getHeader("Thread-Index"); + String threadIndex = (threadIndexes == null || threadIndexes.length == 0) ? null : threadIndexes[0]; + newProperties.put(ImapModel.PROP_THREAD_INDEX, threadIndex); + + String[] subj = this.getHeader("Subject"); + if (subj != null && subj.length > 0) + { + String decodedSubject = subj[0]; + try + { + decodedSubject = MimeUtility.decodeText(decodedSubject); + } + catch (UnsupportedEncodingException e) + { + logger.warn(e.toString()); + } + newProperties.put(ImapModel.PROP_MESSAGE_SUBJECT, decodedSubject); + newProperties.put(ContentModel.PROP_TITLE, decodedSubject); + newProperties.put(ContentModel.PROP_DESCRIPTION, decodedSubject); + } + + serviceRegistry.getNodeService().addProperties(nodeRef, newProperties); + } + + /** + * Writes the content of incoming message into Alfresco repository. + * + * @throws MessagingException + */ + private void writeContent() throws MessagingException + { + ContentWriter writer = serviceRegistry.getContentService().getWriter(messageFileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_RFC822); + try + { + OutputStream outputStream = writer.getContentOutputStream(); + wrappedMessage.writeTo(outputStream); + outputStream.close(); + wrappedMessage = null; // it is not used any more and it is available to GC (to avoid memory leak with byte[] MimeMessage.content field) + this.contentReader = serviceRegistry.getContentService().getReader(messageFileInfo.getNodeRef(), ContentModel.PROP_CONTENT); + } + catch (ContentIOException e) + { + throw new MessagingException(e.getMessage(), e); + } + catch (IOException e) + { + throw new MessagingException(e.getMessage(), e); + } + } + + @Override + protected InputStream getContentStream() throws MessagingException + { + try + { + if (this.contentStream == null) + { + this.contentStream = this.contentReader.getContentInputStream(); + } + return this.contentStream; + } + catch (Exception e) + { + throw new MessagingException(e.getMessage(),e); + } + } + +} diff --git a/source/java/org/alfresco/repo/imap/LoadTest.java b/source/java/org/alfresco/repo/imap/LoadTest.java new file mode 100755 index 0000000000..a392e716c8 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/LoadTest.java @@ -0,0 +1,154 @@ +package org.alfresco.repo.imap; + +import java.io.IOException; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; + +import org.alfresco.repo.importer.ACPImportPackageHandler; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +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.SearchService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; + +import com.icegreen.greenmail.imap.ImapConstants; +import com.icegreen.greenmail.store.SimpleStoredMessage; + +import junit.framework.TestCase; + +public class LoadTest extends TestCase +{ + private Log logger = LogFactory.getLog(LoadTest.class); + + + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private ImapService imapService; + private ImporterService importerService; + + private AlfrescoImapUser user; + private static final String USER_NAME = "admin"; + private static final String USER_PASSWORD = "admin"; + private static final String TEST_DATA_FOLDER_NAME = "test_data"; + private static final String TEST_FOLDER_NAME = "test_imap3"; + private static final long MESSAGE_QUANTITY = 3; + + + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + AuthenticationService authenticationService = serviceRegistry.getAuthenticationService(); + imapService = serviceRegistry.getImapService(); + importerService = serviceRegistry.getImporterService(); + NodeService nodeService = serviceRegistry.getNodeService(); + SearchService searchService = serviceRegistry.getSearchService(); + NamespaceService namespaceService = serviceRegistry.getNamespaceService(); + + user = new AlfrescoImapUser(USER_NAME + "@alfresco.com", USER_NAME, USER_PASSWORD); + + authenticationService.authenticate(USER_NAME, USER_PASSWORD.toCharArray()); + + StoreRef storeRef = new StoreRef("workspace://SpacesStore"); + NodeRef rootRef = nodeService.getRootNode(storeRef); + + // Delete test folder + List nodeRefs = searchService.selectNodes(rootRef, "/app:company_home/imap:imap_home/cm:admin/cm:" + TEST_FOLDER_NAME, null, namespaceService, false); + if (nodeRefs.size() == 1) + { + NodeRef ch = nodeRefs.get(0); + nodeService.deleteNode(ch); + } + // Delete test data folder + nodeRefs = searchService.selectNodes(rootRef, "/app:company_home/imap:imap_home/cm:admin/cm:" + TEST_DATA_FOLDER_NAME, null, namespaceService, false); + if (nodeRefs.size() == 1) + { + NodeRef ch = nodeRefs.get(0); + nodeService.deleteNode(ch); + } + + NodeRef adminNodeRef = searchService.selectNodes(rootRef, "/app:company_home/imap:imap_home/cm:admin", null, namespaceService, false).get(0); + importTestData("test-resources/load_test_data.acp", adminNodeRef); + + + AlfrescoImapFolder testDataFolder = imapService.getFolder(user, TEST_DATA_FOLDER_NAME); +// FileInfo fileInfo = imapService.searchMails(drafts.getFolderInfo().getNodeRef(), "*", AlfrescoImapConst.MODE_ARCHIVE, false).get(0); +// SimpleStoredMessage m = drafts.createImapMessage(fileInfo, (long)0, true); + +// imapService.setExtractAttachmentsEnabled(false); + SimpleStoredMessage m = testDataFolder.getMessages().get(0); + m = testDataFolder.getMessage(m.getUid()); + + + + + AlfrescoImapFolder folder = imapService.createMailbox(user, TEST_FOLDER_NAME); + + logger.info("Creating folders..."); + long t = System.currentTimeMillis(); + + try + { + for (int i = 0; i < MESSAGE_QUANTITY; i++) + { + System.out.println("i = " + i); +// folder.appendMessageInternal(message, new Flags(), new Date()); + folder.appendMessage(m.getMimeMessage(), new Flags(), new Date()); + } + + } + catch (Exception e) + { + logger.error(e, e); + } + + t = System.currentTimeMillis() - t; + logger.info("Create time: " + t + " ms (" + t/1000 + " s (" + t/60000 + " min))"); + + } + + + + public void tearDown() throws Exception + { + } + + + public void testList() + { + logger.info("Listing folders..."); + + long t = System.currentTimeMillis(); + List list = imapService.listMailboxes(user, TEST_FOLDER_NAME + "*"); + t = System.currentTimeMillis() - t; + + logger.info("List time: " + t + " ms (" + t/1000 + " s)"); + logger.info("List size: " + list.size()); + + } + + + private void importTestData(String acpName, NodeRef space) throws IOException + { + ClassPathResource acpResource = new ClassPathResource(acpName); + ACPImportPackageHandler acpHandler = new ACPImportPackageHandler(acpResource.getFile(), null); + Location importLocation = new Location(space); + importerService.importView(acpHandler, importLocation, null, null); + } + + + +} diff --git a/source/java/org/alfresco/repo/imap/RemoteLoadTest.java b/source/java/org/alfresco/repo/imap/RemoteLoadTest.java new file mode 100755 index 0000000000..7a6c85afe6 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/RemoteLoadTest.java @@ -0,0 +1,161 @@ +package org.alfresco.repo.imap; + +import java.io.BufferedInputStream; +import java.io.FilterInputStream; +import java.util.Properties; + +import javax.mail.Address; +import javax.mail.BodyPart; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Part; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.internet.MimeMultipart; + +import junit.framework.TestCase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.sun.mail.util.BASE64DecoderStream; + +public class RemoteLoadTest extends TestCase +{ + + private Log logger = LogFactory.getLog(LoadTest.class); + + private static final String USER_NAME = "admin"; + private static final String USER_PASSWORD = "admin"; + private static final String TEST_FOLDER_NAME = "test_imap1000"; + + @Override + public void setUp() throws Exception + { + } + + public void tearDown() throws Exception + { + + } + + + public void testMailbox() + { + logger.info("Getting folder..."); + long t = System.currentTimeMillis(); + + String host = "localhost"; + + // Create empty properties + Properties props = new Properties(); + + // Get session + Session session = Session.getDefaultInstance(props, null); + + Store store = null; + Folder folder = null; + try + { + // Get the store + store = session.getStore("imap"); + store.connect(host, USER_NAME, USER_PASSWORD); + + // Get folder + folder = store.getFolder(TEST_FOLDER_NAME); + folder.open(Folder.READ_ONLY); + + // Get directory + Message message[] = folder.getMessages(); + + for (int i = 0, n = message.length; i < n; i++) + { + message[i].getAllHeaders(); + + Address[] from = message[i].getFrom(); + System.out.print(i + ": "); + if (from != null) + { + System.out.print(message[i].getFrom()[0] + "\t"); + } + System.out.println(message[i].getSubject()); + + Object content = message[i].getContent(); + if (content instanceof MimeMultipart) + { + for (int j = 0, m = ((MimeMultipart)content).getCount(); j < m; j++) + { + BodyPart part = ((MimeMultipart)content).getBodyPart(j); + Object partContent = part.getContent(); + + if (partContent instanceof String) + { + String body = (String)partContent; + } + else if (partContent instanceof FilterInputStream) + { + FilterInputStream fis = (FilterInputStream)partContent; + BufferedInputStream bis = new BufferedInputStream(fis); + + /* while (bis.available() > 0) + { + bis.read(); + }*/ + byte[] bytes = new byte[524288]; + while (bis.read(bytes) != -1) + { + } + bis.close(); + } + } + } + + int nn = 0; + + } + + + + t = System.currentTimeMillis() - t; + logger.info("Time: " + t + " ms (" + t/1000 + " s)"); + logger.info("Length: " + message.length); + + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + fail(e.getMessage()); + } + finally + { + // Close connection + try + { + if (folder != null) + { + folder.close(false); + } + } + catch (MessagingException e) + { + logger.error(e.getMessage(), e); + fail(e.getMessage()); + } + try + { + if (store != null) + { + store.close(); + } + } + catch (MessagingException e) + { + logger.error(e.getMessage(), e); + fail(e.getMessage()); + } + } + + } + + +} diff --git a/source/java/org/alfresco/repo/imap/SimpleImapMessage.java b/source/java/org/alfresco/repo/imap/SimpleImapMessage.java new file mode 100755 index 0000000000..52e6c9b7f7 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/SimpleImapMessage.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2005-2009 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.repo.imap; + +import static org.alfresco.repo.imap.AlfrescoImapConst.BASE_64_ENCODING; +import static org.alfresco.repo.imap.AlfrescoImapConst.CONTENT_TRANSFER_ENCODING; +import static org.alfresco.repo.imap.AlfrescoImapConst.UTF_8; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimeUtility; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ImapModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Extended MimeMessage to represent a content stored in the Alfresco repository. + * + * @author Arseny Kovalchuk + */ +public class SimpleImapMessage extends AbstractMimeMessage +{ + private static Log logger = LogFactory.getLog(SimpleImapMessage.class); + + /** + * Constructs {@link SimpleImapMessage} object. + * + * @param fileInfo - reference to the {@link FileInfo} object representing the message. + * @param imapHelper - reference to the {@link ImapHelper} object. + * @param generateBody - if {@code true} message body will be generated. + * + * @throws MessagingException if generation of the body fails. + */ + public SimpleImapMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry, boolean generateBody) throws MessagingException + { + super(fileInfo, serviceRegistry, generateBody); + } + + @Override + public void buildMessageInternal() throws MessagingException + { + if (generateBody != false) + { + setMessageHeaders(); + buildImapMessage(); + } + } + + /** + * This method builds MimeMessage based on either ImapModel or ContentModel type. + * + * @param fileInfo - Source file information {@link FileInfo} + * @throws MessagingException + */ + private void buildImapMessage() throws MessagingException + { + final NodeRef nodeRef = messageFileInfo.getNodeRef(); + if (serviceRegistry.getNodeService().hasAspect(nodeRef, ImapModel.ASPECT_IMAP_CONTENT)) + { + buildRFC822Message(); + } + else + { + buildContentModelMessage(); + } + } + + private void buildRFC822Message() throws MessagingException + { + ContentService contentService = serviceRegistry.getContentService(); + ContentReader reader = contentService.getReader(messageFileInfo.getNodeRef(), ContentModel.PROP_CONTENT); + try + { + InputStream inputStream = reader.getContentInputStream(); + this.parse(inputStream); + inputStream.close(); + } + catch (ContentIOException e) + { + //logger.error(e); + throw new MessagingException("The error occured during message creation from content stream.", e); + } + catch (IOException e) + { + //logger.error(e); + throw new MessagingException("The error occured during message creation from content stream.", e); + } + } + + /** + * This method builds {@link MimeMessage} based on {@link ContentModel} + * + * @param fileInfo - Source file information {@link FileInfo} + * @throws MessagingException + */ + private void buildContentModelMessage() throws MessagingException + { + Map properties = messageFileInfo.getProperties(); + String prop = null; + setSentDate(messageFileInfo.getModifiedDate()); + // Add FROM address + Address[] addressList = buildSenderFromAddress(); + addFrom(addressList); + // Add TO address + addressList = buildRecipientToAddress(); + addRecipients(RecipientType.TO, addressList); + prop = (String) properties.get(ContentModel.PROP_TITLE); + try + { + prop = (prop == null) ? MimeUtility.encodeText(messageFileInfo.getName(), KOI8R_CHARSET, null) : MimeUtility.encodeText(prop, KOI8R_CHARSET, null); + } + catch (UnsupportedEncodingException e) + { + // ignore + } + setSubject(prop); + setContent(buildContentModelMultipart()); + } + + /** + * This method builds {@link Multipart} based on {@link ContentModel} + * + * @param fileInfo - Source file information {@link FileInfo} + * @throws MessagingException + */ + private Multipart buildContentModelMultipart() throws MessagingException + { + MimeMultipart rootMultipart = new MimeMultipart("alternative"); + // Cite MOB-395: "email agent will be used to select an appropriate template" - we are not able to + // detect an email agent so we use a default template for all messages. + // See AlfrescoImapConst to see the possible templates to use. + String bodyTxt = getEmailBodyText(EmailBodyType.TEXT_PLAIN); + rootMultipart.addBodyPart(getTextBodyPart(bodyTxt, EmailBodyType.TEXT_PLAIN.getSubtype())); + String bodyHtml = getEmailBodyText(EmailBodyType.TEXT_HTML); + rootMultipart.addBodyPart(getTextBodyPart(bodyHtml, EmailBodyType.TEXT_HTML.getSubtype())); + return rootMultipart; + } + + + private MimeBodyPart getTextBodyPart(String bodyText, String subtype) throws MessagingException + { + MimeBodyPart result = new MimeBodyPart(); + result.setText(bodyText, UTF_8, subtype); + result.addHeader(CONTENT_TRANSFER_ENCODING, BASE_64_ENCODING); + return result; + } + +} diff --git a/source/java/org/alfresco/repo/imap/config/ImapConfigBean.java b/source/java/org/alfresco/repo/imap/config/ImapConfigBean.java index 4301e247da..8fd3b43938 100755 --- a/source/java/org/alfresco/repo/imap/config/ImapConfigBean.java +++ b/source/java/org/alfresco/repo/imap/config/ImapConfigBean.java @@ -27,7 +27,7 @@ package org.alfresco.repo.imap.config; import org.springframework.beans.factory.BeanNameAware; /** - * Provides the parameters for an IMAP mount point (a mapping from an Alfresco node path to an IMAP folder name). + * Standard ImapConfig bean. */ public class ImapConfigBean implements BeanNameAware { @@ -35,9 +35,6 @@ public class ImapConfigBean implements BeanNameAware /** The IMAP folder name. */ private String name; - /** The mode (virtual or archive). */ - private String mode; - /** The Alfresco store name. */ private String store; @@ -63,27 +60,6 @@ public class ImapConfigBean implements BeanNameAware this.name = name; } - /** - * Gets the mode. - * - * @return the mode (virtual or archive) - */ - public String getMode() - { - return this.mode; - } - - /** - * Sets the mode. - * - * @param mode - * the new mode (virtual or archive) - */ - public void setMode(String mode) - { - this.mode = mode; - } - /** * Gets the Alfresco store name. * diff --git a/source/java/org/alfresco/repo/imap/config/ImapConfigMountPointsBean.java b/source/java/org/alfresco/repo/imap/config/ImapConfigMountPointsBean.java new file mode 100755 index 0000000000..25bc5eec4f --- /dev/null +++ b/source/java/org/alfresco/repo/imap/config/ImapConfigMountPointsBean.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2009 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 received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.imap.config; + + +/** + * Provides the parameters for an IMAP mount point (a mapping from an Alfresco node path to an IMAP folder name). + */ +public class ImapConfigMountPointsBean extends ImapConfigBean +{ + + + /** The mode (virtual, archive or mixed). */ + private String mode; + + /** + * Gets the mode. + * + * @return the mode (virtual or archive) + */ + public String getMode() + { + return this.mode; + } + + /** + * Sets the mode. + * + * @param mode + * the new mode (virtual or archive) + */ + public void setMode(String mode) + { + this.mode = mode; + } + +} diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index b6d62dd6c9..73c9d9d31c 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -31,6 +31,7 @@ import org.alfresco.cmis.CMISQueryService; import org.alfresco.cmis.CMISServices; import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.repo.forms.FormService; +import org.alfresco.repo.imap.ImapService; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; @@ -544,4 +545,13 @@ public class ServiceDescriptorRegistry { return (CMISQueryService)getService(CMIS_QUERY_SERVICE); } + + /* + * (non-Javadoc) + * @see org.alfresco.service.ServiceRegistry#getCMISQueryService() + */ + public ImapService getImapService() + { + return (ImapService)getService(IMAP_SERVICE); + } } diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index 73a87f4d25..323de1f22d 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -31,6 +31,7 @@ import org.alfresco.cmis.CMISQueryService; import org.alfresco.cmis.CMISServices; import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.repo.forms.FormService; +import org.alfresco.repo.imap.ImapService; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.attributes.AttributeService; @@ -149,6 +150,7 @@ public interface ServiceRegistry static final QName CMIS_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CMISService"); static final QName CMIS_DICTIONARY_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CMISDictionaryService"); static final QName CMIS_QUERY_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CMISQueryService"); + static final QName IMAP_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "ImapService"); /** @@ -506,4 +508,11 @@ public interface ServiceRegistry */ @NotAuditable CMISQueryService getCMISQueryService(); + + /** + * Get the IMAP service (or null if one is not provided) + * @return the IMAP service + */ + @NotAuditable + ImapService getImapService(); } diff --git a/source/java/org/alfresco/util/Utf7.java b/source/java/org/alfresco/util/Utf7.java new file mode 100755 index 0000000000..2b6ff0fc73 --- /dev/null +++ b/source/java/org/alfresco/util/Utf7.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2009 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; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + +import com.beetstra.jutf7.CharsetProvider; + +/** + * + * @author Mike Shavnev + * + */ +public class Utf7 +{ + public static final String UTF7 = "UTF-7"; + public static final String UTF7_OPTIONAL = "X-UTF-7-OPTIONAL"; + public static final String UTF7_MODIFIED = "X-MODIFIED-UTF-7"; + + + + /** + * Convert string from UTF-7 characters + * + * @param string Input string for decoding + * @return Decoded string + */ + public static String decode(String string, String charsetName) + { + if (string.length() <= 1) + { + return string; + } + CharsetProvider provider = new CharsetProvider(); + Charset charset = provider.charsetForName(charsetName); + CharBuffer charBuffer = charset.decode(ByteBuffer.wrap(string.getBytes())); + return charBuffer.toString(); + } + + /** + * Convert string to UTF-7 characters + * + * @param string Input string for decoding + * @return Encoded string + */ + public static String encode(String string, String charsetName) + { + if (string.length() <= 1) + { + return string; + } + CharsetProvider provider = new CharsetProvider(); + Charset charset = provider.charsetForName(charsetName); + ByteBuffer byteBuffer = charset.encode(string); + return new String(byteBuffer.array()).substring(0, byteBuffer.limit()); + } +} diff --git a/source/test-resources/imapservice_test_folder_a.acp b/source/test-resources/imapservice_test_folder_a.acp new file mode 100755 index 0000000000..11ba385da2 Binary files /dev/null and b/source/test-resources/imapservice_test_folder_a.acp differ diff --git a/source/test-resources/load_test_data.acp b/source/test-resources/load_test_data.acp new file mode 100755 index 0000000000..1e34899c40 Binary files /dev/null and b/source/test-resources/load_test_data.acp differ