From 3cc38f4289d59bbac4f70592d9af6a0bd6064a84 Mon Sep 17 00:00:00 2001 From: Gavin Cornwell Date: Tue, 12 May 2009 13:41:08 +0000 Subject: [PATCH] Initial cut of IMAP support (disabled by default, to enable move imap sample files into extension folder) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14279 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/action-services-context.xml | 3 + config/alfresco/application-context.xml | 1 + config/alfresco/bootstrap/spaces.xml | 188 +-- config/alfresco/bootstrap/system.xml | 2 +- config/alfresco/core-services-context.xml | 1 + .../custom-imap-server-context.xml.sample | 19 + .../custom-imap-server.properties.sample | 2 + .../imap-bootsrap-context.xml.sample | 40 + config/alfresco/imap-config.xml | 14 + config/alfresco/imap-server-context.xml | 91 ++ config/alfresco/imap-server.properties | 4 + .../imap/scripts/command-processor.js | 77 ++ .../alfresco/imap/scripts/command-search.js | 243 ++++ config/alfresco/imap/scripts/command-utils.js | 56 + config/alfresco/import-export-context.xml | 4 + .../messages/bootstrap-imapScripts.properties | 12 + .../messages/bootstrap-spaces.properties | 9 + .../alfresco/messages/imap-service.properties | 10 + .../messages/patch-service.properties | 10 +- config/alfresco/model/imapModel.xml | 100 ++ config/alfresco/repository.properties | 5 + .../imap/command_processor_scripts.acp | Bin 0 -> 5164 bytes .../templates/imap/email_actions_space.acp | Bin 0 -> 3521 bytes .../templates/imap/imap_config_space.acp | Bin 0 -> 4600 bytes .../templates/imap/imap_message_text_html.ftl | 173 +++ .../imap/imap_message_text_plain.ftl | 35 + .../templates/imap/template_test.html | 157 +++ .../alfresco/imap/imap-enabled.get.desc.xml | 11 + .../alfresco/imap/start-workflow.get.desc.xml | 12 + .../alfresco/imap/start-workflow.get.html.ftl | 71 ++ .../org/alfresco/imap/start-workflow.get.js | 42 + .../handler/FolderEmailMessageHandler.java | 52 +- .../impl/subetha/SubethaEmailMessage.java | 25 +- source/java/org/alfresco/model/ImapModel.java | 60 + .../action/executer/ScriptActionExecuter.java | 19 +- .../admin/patch/impl/ImapFoldersPatch.java | 264 ++++ .../repo/admin/patch/impl/ImapUsersPatch.java | 257 ++++ .../alfresco/repo/imap/AlfrescoImapConst.java | 72 ++ .../repo/imap/AlfrescoImapHostManager.java | 958 +++++++++++++++ .../repo/imap/AlfrescoImapMailFolder.java | 1084 +++++++++++++++++ .../repo/imap/AlfrescoImapMessage.java | 508 ++++++++ .../repo/imap/AlfrescoImapServer.java | 123 ++ .../alfresco/repo/imap/AlfrescoImapUser.java | 107 ++ .../repo/imap/AlfrescoImapUserManager.java | 168 +++ .../org/alfresco/repo/imap/ImapHelper.java | 980 +++++++++++++++ .../repo/imap/config/ImapConfigElement.java | 143 +++ .../repo/imap/config/ImapElementReader.java | 81 ++ .../AlfrescoImapFolderException.java | 46 + .../org/alfresco/repo/jscript/Search.java | 22 + 49 files changed, 6250 insertions(+), 111 deletions(-) create mode 100755 config/alfresco/extension/custom-imap-server-context.xml.sample create mode 100755 config/alfresco/extension/custom-imap-server.properties.sample create mode 100755 config/alfresco/extension/imap-bootsrap-context.xml.sample create mode 100755 config/alfresco/imap-config.xml create mode 100755 config/alfresco/imap-server-context.xml create mode 100755 config/alfresco/imap-server.properties create mode 100755 config/alfresco/imap/scripts/command-processor.js create mode 100755 config/alfresco/imap/scripts/command-search.js create mode 100755 config/alfresco/imap/scripts/command-utils.js create mode 100755 config/alfresco/messages/bootstrap-imapScripts.properties create mode 100755 config/alfresco/messages/imap-service.properties create mode 100755 config/alfresco/model/imapModel.xml create mode 100755 config/alfresco/templates/imap/command_processor_scripts.acp create mode 100755 config/alfresco/templates/imap/email_actions_space.acp create mode 100755 config/alfresco/templates/imap/imap_config_space.acp create mode 100755 config/alfresco/templates/imap/imap_message_text_html.ftl create mode 100755 config/alfresco/templates/imap/imap_message_text_plain.ftl create mode 100755 config/alfresco/templates/imap/template_test.html create mode 100755 config/alfresco/templates/webscripts/org/alfresco/imap/imap-enabled.get.desc.xml create mode 100755 config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.desc.xml create mode 100755 config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.html.ftl create mode 100755 config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.js create mode 100755 source/java/org/alfresco/model/ImapModel.java create mode 100755 source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java create mode 100755 source/java/org/alfresco/repo/admin/patch/impl/ImapUsersPatch.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapConst.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapServer.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapUser.java create mode 100755 source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java create mode 100755 source/java/org/alfresco/repo/imap/ImapHelper.java create mode 100755 source/java/org/alfresco/repo/imap/config/ImapConfigElement.java create mode 100755 source/java/org/alfresco/repo/imap/config/ImapElementReader.java create mode 100755 source/java/org/alfresco/repo/imap/exception/AlfrescoImapFolderException.java diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 2a62c14ef5..2e40e81e60 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -473,6 +473,9 @@ /${spaces.company_home.childname} + + ${web.application.context.url} + diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 684b0494a1..e6d122e352 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -52,6 +52,7 @@ + diff --git a/config/alfresco/bootstrap/spaces.xml b/config/alfresco/bootstrap/spaces.xml index b7c16ab94a..13adb72acd 100644 --- a/config/alfresco/bootstrap/spaces.xml +++ b/config/alfresco/bootstrap/spaces.xml @@ -1,6 +1,7 @@ + xmlns:app="http://www.alfresco.org/model/application/1.0" + xmlns:emailserver="http://www.alfresco.org/model/emailserver/1.0"> @@ -24,34 +25,34 @@ Consumer - - ${spaces.dictionary.name} - space-icon-default - ${spaces.dictionary.name} - ${spaces.dictionary.description} - - - - ${spaces.templates.name} - space-icon-default - ${spaces.templates.name} - ${spaces.templates.description} - - - - ${spaces.templates.content.name} - space-icon-default - ${spaces.templates.content.name} - ${spaces.templates.content.description} - - - - ${spaces.templates.email.name} - space-icon-default - ${spaces.templates.email.name} - ${spaces.templates.email.description} + + ${spaces.dictionary.name} + space-icon-default + ${spaces.dictionary.name} + ${spaces.dictionary.description} + + + + ${spaces.templates.name} + space-icon-default + ${spaces.templates.name} + ${spaces.templates.description} + + + + ${spaces.templates.content.name} + space-icon-default + ${spaces.templates.content.name} + ${spaces.templates.content.description} + + + + ${spaces.templates.email.name} + space-icon-default + ${spaces.templates.email.name} + ${spaces.templates.email.description} @@ -80,69 +81,68 @@ - - - - - ${alfresco_user_store.guestusername} - Consumer - - - - ${spaces.templates.rss.name} - space-icon-default - ${spaces.templates.rss.name} - ${spaces.templates.rss.description} - - - - - GROUP_EVERYONE - Contributor - - - - ${spaces.savedsearches.name} - space-icon-default - ${spaces.savedsearches.name} - ${spaces.savedsearches.description} - - - - ${spaces.scripts.name} - space-icon-default - ${spaces.scripts.name} - ${spaces.scripts.description} - - - - - - - ${alfresco_user_store.guestusername} - Consumer - - - GROUP_EVERYONE - Consumer - - - - ${spaces.guest_home.name} - space-icon-default - ${spaces.guest_home.name} - ${spaces.guest_home.description} - - - - ${spaces.user_homes.name} - space-icon-default - ${spaces.user_homes.name} - ${spaces.user_homes.description} - - - - + + + + + ${alfresco_user_store.guestusername} + Consumer + + + + ${spaces.templates.rss.name} + space-icon-default + ${spaces.templates.rss.name} + ${spaces.templates.rss.description} + + + + + GROUP_EVERYONE + Contributor + + + + ${spaces.savedsearches.name} + space-icon-default + ${spaces.savedsearches.name} + ${spaces.savedsearches.description} + + + + ${spaces.scripts.name} + space-icon-default + ${spaces.scripts.name} + ${spaces.scripts.description} + + + + + + + ${alfresco_user_store.guestusername} + Consumer + + + GROUP_EVERYONE + Consumer + + + + ${spaces.guest_home.name} + space-icon-default + ${spaces.guest_home.name} + ${spaces.guest_home.description} + + + + ${spaces.user_homes.name} + space-icon-default + ${spaces.user_homes.name} + ${spaces.user_homes.description} + + + \ No newline at end of file diff --git a/config/alfresco/bootstrap/system.xml b/config/alfresco/bootstrap/system.xml index 2e8e23bb79..95a9200224 100644 --- a/config/alfresco/bootstrap/system.xml +++ b/config/alfresco/bootstrap/system.xml @@ -49,7 +49,7 @@ ${alfresco_user_store.adminusername} Administrator - + admin@alfresco.com /${spaces.company_home.childname} bootstrapHomeFolderProvider diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index d16fe3cf04..38d4842c04 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -928,6 +928,7 @@ alfresco/model/bpmModel.xml alfresco/model/wcmModel.xml alfresco/model/forumModel.xml + alfresco/model/imapModel.xml alfresco/model/applicationModel.xml diff --git a/config/alfresco/extension/custom-imap-server-context.xml.sample b/config/alfresco/extension/custom-imap-server-context.xml.sample new file mode 100755 index 0000000000..a706326c12 --- /dev/null +++ b/config/alfresco/extension/custom-imap-server-context.xml.sample @@ -0,0 +1,19 @@ + + + + + + + + true + + + + classpath:alfresco/imap-server.properties + + classpath:alfresco/extension/custom-imap-server.properties + + + + + diff --git a/config/alfresco/extension/custom-imap-server.properties.sample b/config/alfresco/extension/custom-imap-server.properties.sample new file mode 100755 index 0000000000..c87cdff75c --- /dev/null +++ b/config/alfresco/extension/custom-imap-server.properties.sample @@ -0,0 +1,2 @@ +imap.server.enabled=true +imap.server.port=143 diff --git a/config/alfresco/extension/imap-bootsrap-context.xml.sample b/config/alfresco/extension/imap-bootsrap-context.xml.sample new file mode 100755 index 0000000000..307e8f2996 --- /dev/null +++ b/config/alfresco/extension/imap-bootsrap-context.xml.sample @@ -0,0 +1,40 @@ + + + + + + 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-config.xml b/config/alfresco/imap-config.xml new file mode 100755 index 0000000000..6ae9ec65fd --- /dev/null +++ b/config/alfresco/imap-config.xml @@ -0,0 +1,14 @@ + + + + + workspace://SpacesStore + /app:company_home + + + workspace://SpacesStore + /app:company_home + + + + \ No newline at end of file diff --git a/config/alfresco/imap-server-context.xml b/config/alfresco/imap-server-context.xml new file mode 100755 index 0000000000..88259243d2 --- /dev/null +++ b/config/alfresco/imap-server-context.xml @@ -0,0 +1,91 @@ + + + + + + + + + alfresco.messages.imap-service + + + + + + + true + + + + classpath:alfresco/imap-server.properties + + + + + + + + + + + + + + + + + + + + + + ${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.port} + + + ${imap.server.enabled} + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/imap-server.properties b/config/alfresco/imap-server.properties new file mode 100755 index 0000000000..42d301e12e --- /dev/null +++ b/config/alfresco/imap-server.properties @@ -0,0 +1,4 @@ +imap.server.enabled=false +imap.server.port=143 + +#imap.server.web.application.context.url=http://localhost:8080/alfresco diff --git a/config/alfresco/imap/scripts/command-processor.js b/config/alfresco/imap/scripts/command-processor.js new file mode 100755 index 0000000000..963f147c10 --- /dev/null +++ b/config/alfresco/imap/scripts/command-processor.js @@ -0,0 +1,77 @@ + + +function processCommand() +{ + var isEmailed = document.hasAspect("emailserver:emailed"); + + logger.log("Command Processor: isEmailed=" + isEmailed); + + if (isEmailed) + { + // Delete email attachments + var attachments = document.assocs["attachments"]; + if (attachments != null) + { + for (var i = 0; i < attachments.length; i++) + { + attachments[i].remove(); + } + } + + var command = document.properties["cm:title"]; + logger.log("Command Processor: command=" + command); + + var parts = new Array(); + var str = command; + var i = 0; + while (true) + { + var index = str.indexOf("-"); + if (index == -1) + { + parts[i] = str; + break; + } + parts[i] = str.substring(0, index); + str = str.substr(index + 1); + i++; + } + + + + // do---...- + if (parts.length < 3 || parts[0].toLowerCase() != "do") + { + var message = "Unknown command: " + command; + logger.log(message); + createEmail(message, message, message, false); + return; + } + + var commandName = parts[1].toLowerCase(); + var commandFolder = space.childByNamePath(commandName); + logger.log("Found '" + commandName + "' command folder: '" + commandFolder + "'"); + if (commandFolder == null) + { + var message = "Command Processor: wrong command=" + command; + createEmail(message, message, message, false); + logger.log(message); + return; + } + + document.move(commandFolder); + + } + +} + +processCommand(); + + + + + + + + + diff --git a/config/alfresco/imap/scripts/command-search.js b/config/alfresco/imap/scripts/command-search.js new file mode 100755 index 0000000000..5ffecaa983 --- /dev/null +++ b/config/alfresco/imap/scripts/command-search.js @@ -0,0 +1,243 @@ + + +/** + * Resources + */ + +/* var templatePath = "/Data Dictionary/Imap Configs/Templates/imap_search_response_text_html.ftl";*/ +var errorParameter = "Error: The query parameter is not set!"; +var errorXPathNotValid = "Error: The Xpath query is not valid."; +var unknownCommand = "Unknown command"; + +/** + * Globals + */ +var title; +var command; + +/** + * Create content for e-mail in text format + * + * @nodes (Array) ScriptNodes array + * @return content for e-mail in text format + */ +function createContentTextPlain(nodes) +{ + var content = "Command: " + title + "\n\n"; + for (var i = 0; i < nodes.length; i++) + { + content = content + "Name: " + nodes[i].getName() + "\nUrl: " + + webApplicationContextUrl + nodes[i].getUrl(); + + if (nodes[i].isDocument) + { + content = content + "\nDownload Url: " + + webApplicationContextUrl + nodes[i].getDownloadUrl(); + } + + content = content + "\n\n"; + } + return content; +} + +/** + * This for possible processing. It need to be investigated. + * The possible solution is to send a search request into FreeMarker template and let the template do search! + * @param nodes + * @return + */ +function createResponseTextHtml(nodes) +{ + var template = companyhome.childByNamePath(templatePath); + var result; + if (template != null) + { + var args = new Array(); + args["title"] = title; + args["nodes"] = nodes; /*it does not work; need to investigate how to send this to freemarker processing*/ + args["webApplicationContextUrl"] = webApplicationContextUrl; + result = document.processTemplate(template, args); + logger.log("Response template is found. Response body is created using template."); + } + else + { + result = createContentTextHtml(nodes); + logger.log("Response template is NOT found. Response is created using default function."); + } + return result; +} + +/** + * Create content for e-mail in html format + * + * @nodes (Array) ScriptNodes array + * @return content for e-mail in html format + */ +function createContentTextHtml(nodes) +{ + var content = "Command: " + title + "\n

\n"; + content += "\n"; + content += ""; + content += ""; + content += ""; + content += ""; + content += ""; + content += ""; + content += "\n" + + + for (var i = 0; i < nodes.length; i++) + { + content += "\n"; + content += ""; + content += ""; + content += "\n"; + content += "\n"; + } + content += "
NameUrlDownload Url
" + nodes[i].getName() + "" + + webApplicationContextUrl + nodes[i].getUrl() + " "; + if (nodes[i].isDocument) + { + content += "" + + webApplicationContextUrl + nodes[i].getDownloadUrl() + ""; + } + content += "
"; + return content; +} + +/** + * Execute search command + * + * @params (string) command parameters + */ +function commandSearch(params) +{ + var store = "workspace://SpacesStore"; + var query; + var subject = "Search result"; + var type = "lucene"; + var paramArray = params.split(";"); + for (var i = 0; i < paramArray.length; i++) + { + var param = paramArray[i].split("="); + param[0] = param[0].toLowerCase(); + + switch (param[0]) + { + case "store": store = param[1]; break; + case "query": query = param[1]; break; + case "subject": subject = param[1]; break; + case "type": type = param[1].toLowerCase(); break; + } + } + + if (query == null) + { + createEmail(errorParameter, errorParameter, errorParameter, false); + return; + } + + var nodes; + + try + { + switch (type) + { + case "lucene": + nodes = search.luceneSearch(store, query); + break; + case "xpath": + var isValid = search.isValidXpathQuery(query); + if (isValid == true) + { + nodes = search.xpathSearch(store, query); + } + else + { + createEmail(errorXPathNotValid, errorXPathNotValid, errorXPathNotValid, false); + return; + } + break; + case "node": + var node = search.findNode(query); + if (node == null) break; + nodes = new Array(node); + break; + case "tag": + nodes = search.tagSearch(store, query); + break; + } + } + catch (exception) + { + createEmail(exception.message, exception.message, "Search Error"); + return; + } + + if (nodes == null || nodes.length == 0) + { + var message = "Nothing was found using query: '" + subject + "'."; + createEmail(message, message, subject); + return; + } + /*createEmail(createResponseTextHtml(nodes), createContentTextPlain(nodes), subject, true);*/ + createEmail(createContentTextHtml(nodes), createContentTextPlain(nodes), subject, false); +} +/** + * Decode subject + * + * @subject (string) subject + */ +function decodeSubject(subject) +{ + var s = new Array(); + s[0] = new Array("\\", "%5c"); + s[1] = new Array("/", "%2f"); + s[2] = new Array("*", "%2a"); + s[3] = new Array("|", "%7c"); + s[4] = new Array(":", "%3a"); + s[5] = new Array("\"", "%22"); + s[6] = new Array("<", "%3c"); + s[7] = new Array(">", "%3e"); + s[8] = new Array("?", "%3f"); + + for (var i = 0; i < s.length; i++) + { + var re = new RegExp(s[i][1], 'g'); + subject = subject.replace(re, s[i][0]); + } + + return subject; +} + + +function main() +{ + title = decodeSubject(document.properties["cm:title"]); + command = title.split("-"); + if (command[0].toLowerCase() == "do") + { + if (command[1].toLowerCase() == "search") + { + commandSearch(title.substring(4 + command[1].length)); + } + else + { + var message = unknownCommand + ": '" + title + "'"; + createEmail(message, message, message, false); + } + } + else + { + var message = unknownCommand + ": '" + title + "'"; + createEmail(message, message, message, false); + } + + document.remove(); +} + + +logger.log("Start search command."); +main(); +logger.log("End search command."); + diff --git a/config/alfresco/imap/scripts/command-utils.js b/config/alfresco/imap/scripts/command-utils.js new file mode 100755 index 0000000000..64039946f5 --- /dev/null +++ b/config/alfresco/imap/scripts/command-utils.js @@ -0,0 +1,56 @@ +/** +* Create e-mail +* contentTextHtml (string) html content +* contentTextPlain (string) text content +*/ +function createEmail(contentTextHtml, contentTextPlain, subject, templateUsed) +{ + var command = document.properties["cm:title"]; + var userName = person.properties["cm:userName"]; + + var inboxFolder = companyhome.childByNamePath("IMAP Home/" + userName + "/INBOX"); + if (inboxFolder == null) + { + logger.log("Command Processor: INBOX folder does't exists."); + return; + } + + var nextMessageUID = inboxFolder.properties["imap:nextMessageUID"]; + inboxFolder.properties["imap:nextMessageUID"] = nextMessageUID + 1; + inboxFolder.save(); + + var response = inboxFolder.createNode("response" + Date.now(), "imap:imapContent"); + 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.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(); + +} \ No newline at end of file diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 666cdb7638..e98746534b 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -326,6 +326,9 @@ ${spaces.user_homes.childname} ${spaces.sites.childname} ${spaces.templates.email.invite.childname} + ${spaces.imap_home.childname} + ${spaces.imapConfig.childname} + ${spaces.imap_templates.childname}
@@ -516,6 +519,7 @@ alfresco/bootstrap/sitesSpace.xml alfresco/messages/bootstrap-spaces + diff --git a/config/alfresco/messages/bootstrap-imapScripts.properties b/config/alfresco/messages/bootstrap-imapScripts.properties new file mode 100755 index 0000000000..39e505cdfd --- /dev/null +++ b/config/alfresco/messages/bootstrap-imapScripts.properties @@ -0,0 +1,12 @@ +imap.command_processor.name=command-processor.js +imap.command_processor.title=Command Processor +imap.command_processor.description=Email Command Processor Script + +imap.command_search.name=command-search.js +imap.command_search.title=Search Command +imap.command_search.description=Email Search Command Script + +imap.command_utils.name=command-utils.js +imap.command_utils.title=Command Utils +imap.command_utils.description=Email Command Utils + \ No newline at end of file diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index ca6aa301e9..dabd16aa08 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -6,6 +6,12 @@ spaces.company_home.description=The company root space spaces.dictionary.name=Data Dictionary spaces.dictionary.description=User managed definitions +spaces.imapConfig.name=Imap Configs +spaces.imapConfig.description=Imap Configs + +spaces.imap_templates.name=Templates +spaces.imap_templates.description=Templates for IMAP generated messages + spaces.templates.name=Space Templates spaces.templates.description=Space folder templates @@ -44,3 +50,6 @@ spaces.sites.description=Site Collaboration Spaces spaces.templates.email.invite.name=invite spaces.templates.email.invite.description=Invite email templates + +spaces.imap_home.name=IMAP Home +spaces.imap_home.description=IMAP Home \ No newline at end of file diff --git a/config/alfresco/messages/imap-service.properties b/config/alfresco/messages/imap-service.properties new file mode 100755 index 0000000000..fdfd12856a --- /dev/null +++ b/config/alfresco/messages/imap-service.properties @@ -0,0 +1,10 @@ +# +# Imap I18N messages +# + +# Information messages. prefix 'imap.server.info' +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." + diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 2a3253d9f1..28de79ac2a 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -1,4 +1,4 @@ -# PatchService messages +# PatchService messages patch.service.preceeded_by_alternative=Preceeded by alternative patch ''{0}''. patch.service.not_relevant=Not relevant to schema {0} patch.executer.checking=Checking for patches to apply ... @@ -261,3 +261,11 @@ patch.mtShareExistingTenants.result=Update existing tenants for MT Share. patch.mtShareExistingTenants.result.not_applicable=Patch applied, although no changes made since MT is not enabled. patch.redeployInvitationProcess.description=Re-deploy Invitation Process Definitions. + +patch.imapFolders.description=Creates folders tree necessary for IMAP functionality +patch.imapFolders.result.exists=The 'Imap Configs' folder already exists +patch.imapFolders.result.created=The 'Imap Configs' folder was successfully created + +patch.imapUserFolders.description=Creates folders tree necessary for IMAP functionality +patch.imapUserFolders.result.exists=The 'IMAP Home' folder already exists +patch.imapUserFolders.result.created=The 'IMAP Home' folder was successfully created diff --git a/config/alfresco/model/imapModel.xml b/config/alfresco/model/imapModel.xml new file mode 100755 index 0000000000..ee93e810b2 --- /dev/null +++ b/config/alfresco/model/imapModel.xml @@ -0,0 +1,100 @@ + + + IMAP Content Model + Alfresco + 2009-01-20 + 1.0 + + + + + + + + + + + + + IMAP Folder + cm:folder + + + d:boolean + + + d:boolean + true + true + + + + + + IMAP File + cm:folder + + + + d:text + + + d:text + + + d:text + + + d:text + + + + imap:flaggable + + + + + Attachment to the IMAP message + cm:content + + + d:text + + + + + + Body of the IMAP message + cm:content + + + + + + + + d:boolean + + + d:boolean + + + d:boolean + + + d:boolean + + + d:boolean + + + d:boolean + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 285650cd52..80d75769da 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -6,6 +6,8 @@ repository.name=Main Repository dir.root=./alf_data +web.application.context.url=http://localhost:8080/alfresco + dir.contentstore=${dir.root}/contentstore dir.contentstore.deleted=${dir.root}/contentstore.deleted @@ -226,6 +228,8 @@ spaces.company_home.childname=app:company_home spaces.guest_home.childname=app:guest_home spaces.dictionary.childname=app:dictionary spaces.templates.childname=app:space_templates +spaces.imapConfig.childname=app:imap_configs +spaces.imap_templates.childname=app:imap_templates spaces.templates.content.childname=app:content_templates spaces.templates.email.childname=app:email_templates spaces.templates.rss.childname=app:rss_templates @@ -235,6 +239,7 @@ spaces.wcm.childname=app:wcm spaces.wcm_content_forms.childname=app:wcm_forms spaces.content_forms.childname=app:forms spaces.user_homes.childname=app:user_homes +spaces.imap_home.childname=imap:imap_home spaces.sites.childname=st:sites spaces.templates.email.invite.childname=cm:invite diff --git a/config/alfresco/templates/imap/command_processor_scripts.acp b/config/alfresco/templates/imap/command_processor_scripts.acp new file mode 100755 index 0000000000000000000000000000000000000000..dda4f430b3c434a200bfee2422fb5b9a9f5dd575 GIT binary patch literal 5164 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 MNC3bT(VyGD0LP1GvH$=8 literal 0 HcmV?d00001 diff --git a/config/alfresco/templates/imap/email_actions_space.acp b/config/alfresco/templates/imap/email_actions_space.acp new file mode 100755 index 0000000000000000000000000000000000000000..f54d9069c0880675d31bc9621fd71022b1932870 GIT binary patch literal 3521 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 literal 0 HcmV?d00001 diff --git a/config/alfresco/templates/imap/imap_config_space.acp b/config/alfresco/templates/imap/imap_config_space.acp new file mode 100755 index 0000000000000000000000000000000000000000..d2dee2323c7e86381fb624509a3789f475ef6318 GIT binary patch literal 4600 zcmbW5XHXN|wuVEKqVy_)5PI*1j&uk$2ndKsjSyPsMWln$yP*atp-68*>Ai#w0YQ2d z0fi3)ktPSvk9+*inRDmPUF*-@f8LqB=bd@pwIJGf_%r~bn?4IHl>z)wOgCq+i?y2y z-1Rxk-q6k37W&i{?&<|~^%4>C^7E=R)Q8WD&~#iFw!&l)5xTNQ4vu|VB|wK#RzZ92 zAe?eNrcc6==pouec+k#?f@f`VLM)AVSX_w|AOv1*V{%mX2%|7l zJ&^JJ=ZP`b#QdiEwu5HDiDpW-)1sF4kC@1reIBgn2j^pm*;k4z+L5q@^EuxjlLN-v z4YkSAU?loSp~|LCtQAKJ|_WhQ(H2BeODa}LvL z#%q8aQa12+Bne;0?z>f#hmo&bJPbmJL^Ie**0Z*fDrY2wW4c|u)n{@zpAav4-#0-E z$}<@IpduQY*01-aD~Az3MBX+Qm%t(yn%$S}v^?EA-+F%nmW!F&NH8k+=!{K)7>Iv8 zO`T(78dEVH`<(g^`zfV%Ry4FB2^RL$oS!{Xkui%Nm(1N7Tyj*TFz4_+ji(JK6UU`I zK$N#c_zq(-H7-I5!;m(T>uh{~h4jF>DgA6%JbxnO_MFZ#LHG)R#q_yT34`aJG7o{Y zDl%pFZK76(O5m-gFq5kxOZnQpb z^5BxF9aN5|SS)vH;iJ+PIKE60ZOp7zMKE_f_}C<0GSSR1mzV#QWqcXKR_eB+x2*v3cOVet3qP$Erb-Q+uf zrQOL6Uz}&E(P_N0F4FAD_&($J1?XZ4mg>)>6b-pJ61x*-oSDp`B7;=@rM{Z_ww?Y+ zq?7snW08JoML58+Y9&7zH*&8fHjzar8ESjk6d3LPepW5`aicR!V?~SF*Z0q(`9heRGpz5>yul zfs+F_6EfK}V;8>eMd-_ZgwpWQ@wL2<%8b0+thD6?nV6QpWeZk~uq#WAo5xF|eWcl6 zVSCl9%oP=*inA)mK*V)Lv{WO7yPs?dpf2Q{VYAWD2^_TS*eCNC}g9Lua6Mc19y1}g+0vayZsAqDXj#MYp zq$aQWSGv;ulXIGC${VxrfaRYA|qM-DrOIYF%1sS zk-|asfqcuW$Xm6-w3NH!W*qy3N)jq^aooE#(*kClmoYxfbE+aK1;(%6#o<1Sp;YPY zllrb*-*ejv_~pTDRo)BY+~N=~5-461jdmor zP|_>JKgx>57R#F?E7FNXbdz_vDwjSG1_UZ79*{QhG)`)|)AA#Wy=GV9Q z)|x~N4C_~c^y;-+Ck?q9Hfr8)O&Geleu`0chop#DhuW=J?Lz0$4|i_yygHOumaNV%~33~%_0UQJxC(xO=3K(=))^EFA? zT~j=C>%hB3!%(NplFsqGpz_l$KCd<f%JiU^hiYo3?y6JF@wiK9=dL^L0P}CrY|8Mv z^^h2wqnbIgG&;a7lG(ZEbF2CNvWH|=q>}0vYF(_+w|xT=wRbQ0-kNm;{Mw0DeO{rD zId*5{&m^Ie)mW%p=9=7g&K8JRV5PF{g$+De0atoKIo|ksv!s14B$9Zo1J&&{`OHxd zV!T6ekaltByJ&Q6SDj?->U65nE=L01J~e6hHi+qg^XSB@5D`Llb?l54Nq9pui@TPY zLy;X=1v^9Vv-Do9xyog2$6haTYmbq@^dorYOazBJBdA);`UH=!=(oc z@Qo&8tuWU#8sD8&VrI4WE#%(4T}uChb=5pQgBo6Y@!=Lx-(xD|KoX*);t7kRuy|5I zB8d=x@m^i|IHz;7rrOCfA-xtd$gmVyiXh;)K<-tY@1=muY?ww^xtcuE>*)9u?)LSl z^srk6`qQZS9#$B#UO&jnJSU{YL1wl_pVz&%myv@B3fsWBB;~dCkNh`?suNR z5O18@scRve=O>m$tL)mx&YR9~7`*6#O*6S{)%+F#Nz-SZ-UlbMdR zaMpDkN4N89P8V7%IYjU?XC51+TuCB7IbS7?y_|+yGEnLnD}d$vp6+l~HuRy3_xyK? zk22Pt+pmr0tc2yjKhWxD@|K{(xfP2jmdBnRn~2>_o!}s!ea)PzB#4y47~M_%l~EoW z{b=4{y%8ly{tERNb2}m~=$!POcUq8&R`9AAy}eJFDV-BXDF?`rbIuDrAxuAO(y5?d^EYq6ZT?KcM93l zpe=%#&_0Lg7MNEtG|yZ;I^AL&FjwTEKTjPbgM2Zb9c&*HHCG!6we`8AnkoYfp0r!e zZmiJ=yrGFdBQF-7b*^&aDpCe})_crh`x0ZZiDI97YEQ|3vx=cxxY_0w0H8|$|74Yz zke`e5n`9-D9##sq)6hkwI^lZqkGjH;EWC)WEOkcl0ubkWIESfookPpi^#CFH>x~KN zEly^Rcw~EYm}q}}K-_$ue4x=gC_AHfh0svSZ0O>m-h3n>8LhaN1sCu0RjrFHG?l)G zFWjJvF)z9VZZmGp3DW)jUnnKvNpar*STn^|iHe{!*4N|=4G^c0=V@(*x?!4}S zx95NGt!CNKl%_&pB|Ztp6}3Y&sZbeQ7fb6s7c0}F>*&+8myiY4ZhBnOA4wt}fnfTLq8g7J_gIml%T_d0i*ex?!gtqXw4K|^)mLV;L^ zkY+?kDeo|emo#g{V6lXBq+w%8S|CZ`F=}#CQ+hIm27;Fz2h|V_bUr&{u#1dM#KBtW zJSLdTD?|B^tYZelKDeouChk1@LG5V*pI)a98)LOYX}4{ zvZcF7v~r>oSiY6yTo^Q$5OW87W&TrWe#L6yOyzolzd{H+*!e#5+_LQtn=@Q041uulPuZWpRkY#lOjF~xk`#6kheGVA|ZxpA@c3$P%W}k(a+*@QSFX@w}K|AZyU%3 z{60x*Z#_*#Ie?$9FXdThl!L%heSRDIq-am(F636lwZ7c7zC9pPu2bzh`djr2OY{@34U4r75pf-A}wQ6dOo$QI~^g>a#=L}fWW1? z7JlAd&PWHEc~khBPi)Qp8)M^sMDI+Xm?Ex4Fez;XVQC&%)$DAW=J=B$?g}fnS2?z*-I*xUGbn7KIN!72#;BnA0tCOjptx8jUMsrh|x9K zr7P&P$9e4c;<;wzLkcJ*-uUDl(W?9?Y6Re~1Umx{SYKWYAmPXZm{&+UXU5aLKI4(| zq{zZ0YFq+WRREs`yqeQ6Z)NE%xJpF-j5M3!u~n5irfv_NuVbGkmd#}By>1-*u*QL# z?Dm#fKF@YXIWQ2za^|UH+x=Fo&WY-;9BlC5n25{9*-I1Cr?d(h?|ur+CvX1hX}L(L zFWc#N#N6YV06v0`Y%BUU6F2al*-$$$jlv1!vxn=mn?*lS$n0fI(sAT1E;D7pVuM5) zb+2Ac#;$&BG>~R=y77(3VwEEWxm-f~DWUYx!Iky%V2X)%^F5?XT`N+}P5t~cY6%1@ z*g1&guOWjIR-!vgwa!>INuSrF_6KDBe!evp0at3xDi%n~a_J1j84HMTc;Y&JG#{K-IlZ&{;2JkZqppr zQx6B12Jo*X#-B6m?wjBm{HgSN#{Dx<{9TD3_rESj{yFjfyV9SO{=3pF(SKF? pXCC=?r9WT(o9h3nQR4ql`ptNVHo?tD0RZ?n&2keGdMJM1{sE<|N9X_m literal 0 HcmV?d00001 diff --git a/config/alfresco/templates/imap/imap_message_text_html.ftl b/config/alfresco/templates/imap/imap_message_text_html.ftl new file mode 100755 index 0000000000..5af0c44148 --- /dev/null +++ b/config/alfresco/templates/imap/imap_message_text_html.ftl @@ -0,0 +1,173 @@ + + + + + + + + + + +


+

Document (name): ${document.name}

+
+
+ Metadata + + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
Title:${document.properties.title}
Title: 
Description:${document.properties.description}
Description: 
Creator:${document.properties.creator}
Created:${document.properties.created?datetime}
Modifier:${document.properties.modifier}
Modified:${document.properties.modified?datetime}
Size:${document.size / 1024} Kb
+
+
+ Content links + + + + + + + + + + + + + + +
+
+ Start Workflow +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Workflow type: + + +
Asign to: + +
Priority: + +
Due date: + Day: +   + Month:   + Year: +
Description:
+
+
+ + + \ No newline at end of file diff --git a/config/alfresco/templates/imap/imap_message_text_plain.ftl b/config/alfresco/templates/imap/imap_message_text_plain.ftl new file mode 100755 index 0000000000..b8edc2195e --- /dev/null +++ b/config/alfresco/templates/imap/imap_message_text_plain.ftl @@ -0,0 +1,35 @@ +------------------------------------------------------------------------------ +Document name: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +Title: ${document.properties.title} + <#else> +Title: NONE + + <#if document.properties.description?exists> +Description: ${document.properties.description} + <#else> +Description: NONE + +Creator: ${document.properties.creator} +Created: ${document.properties.created?datetime} +Modifier: ${document.properties.modifier} +Modified: ${document.properties.modified?datetime} +Size: ${document.size / 1024} Kb + + +CONTENT LINKS + +Content folder: ${contextUrl}${document.displayPath} +Content URL: ${contextUrl}${document.url} +Download URL: ${contextUrl}${document.downloadUrl} +WebDAV URL: ${contextUrl}${document.webdavUrl} + +START WORKFLOW + +{It is not possible to create a customizible workflow in txt format! + It is possible to create static links to IMAP Workflow Handler webscript, + But, in this case all parameters must be hardcoded in the link. + See http://localhost:8080/alfresco/service/description/org/alfresco/imap/start-workflow.get + for usage information.} \ No newline at end of file diff --git a/config/alfresco/templates/imap/template_test.html b/config/alfresco/templates/imap/template_test.html new file mode 100755 index 0000000000..21a5a5d5e4 --- /dev/null +++ b/config/alfresco/templates/imap/template_test.html @@ -0,0 +1,157 @@ + + + + + + + + + + +
+

Document (name): ${document.name}

+
+
+ Metadata + + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
Title:${document.properties.title}
Title: 
Description:${document.properties.description}
Description: 
Creator:${document.properties.creator}
Created:${document.properties.created?datetime}
Modifier:${document.properties.modifier}
Modified:${document.properties.modified?datetime}
Size:${document.size / 1024} Kb
+
+
+ Content links + + + + + + + + + + + + + + +
+
+ Start Workflow +
+ + + + + + + + + + + + + + + + + + + + + + + +
Workflow type: + + +
Asign to: + +
Priority: + +
Due date:
Description:
+
+
+ + + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/imap/imap-enabled.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/imap/imap-enabled.get.desc.xml new file mode 100755 index 0000000000..c405507342 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/imap/imap-enabled.get.desc.xml @@ -0,0 +1,11 @@ + + IMAP Server Status + + This script return status of IMAP server (enabled/diabled) + + /imap/servstatus + extension + user + required + IMAP + diff --git a/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.desc.xml new file mode 100755 index 0000000000..d8849e0ff6 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.desc.xml @@ -0,0 +1,12 @@ + + IMAP Workflow Handler + + This webscript starts a different workflows. It have used in the IMAP email body links. + (The optional feature is the reply email with a report.) + + /imap/start-workflow?alfTicket={ticket}&nodeRefId={id}&workflowType={wt}&assignTo={at}&workflowDueDateDay={ddd}&workflowDueDateMonth={ddm}&workflowDueDateYear={ddy}&description={desc} + extension + user + required + IMAP + diff --git a/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.html.ftl b/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.html.ftl new file mode 100755 index 0000000000..3b2b45b169 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.html.ftl @@ -0,0 +1,71 @@ + + + + + + + + + + +

Workflow has started successfully.

+ + + + + + + + + + + + + + + + + + + + + + +
Ticket:${args.alfTicket}
NodeRef id:${args.nodeRefId}
Workflow type:${args.workflowType}
Asign to:${args.assignTo}
Due date:${args.workflowDueDateDay}/${args.workflowDueDateMonth}/${args.workflowDueDateYear}
Description:${args.description}
Result: ---
+ + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.js b/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.js new file mode 100755 index 0000000000..c55922ffaf --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/imap/start-workflow.get.js @@ -0,0 +1,42 @@ +function main() +{ + logger.log("Start workflow form 'Start workflow' webscript"); + var docNode = search.findNode("workspace://SpacesStore/" + args.nodeRefId); + if (docNode == undefined) + { + status.code = 404; + status.message = "Content with NodeRef id '" + args.nodeRefId + "' not found."; + status.redirect = true; + return; + } + var workflowType = "jbpm$wf:" + args.workflowType; + var assignTo = people.getPerson(args.assignTo); + if (assignTo == undefined) + { + status.code = 404; + status.message = "Person with username '" + args.assignTo + "' not found."; + status.redirect = true; + return; + } + var day = args.workflowDueDateDay; + var month = args.workflowDueDateMonth; + var year = args.workflowDueDateYear; + if (year != null && year.length == 2) + { + year = "20" + year; + } + var dueDate = new Date(year, month - 1, day); + var description = args.description; + + var workflow = actions.create("start-workflow"); + workflow.parameters.workflowName = workflowType; + workflow.parameters["bpm:workflowDescription"] = description; + workflow.parameters["bpm:assignee"] = assignTo; + workflow.parameters["bpm:workflowPriority"] = args.workflowPriority; + if (dueDate != null) + { + workflow.parameters["bpm:workflowDueDate"] = dueDate; + } + workflow.execute(docNode); +} +main(); \ No newline at end of file diff --git a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java index af0b951ec8..152d302634 100644 --- a/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/FolderEmailMessageHandler.java @@ -40,6 +40,8 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.email.EmailMessage; import org.alfresco.service.cmr.email.EmailMessageException; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -120,24 +122,54 @@ public class FolderEmailMessageHandler extends AbstractEmailMessageHandler // Write the message content if (message.getBody() != null) { - InputStream contentIs = message.getBody().getContent(); - // The message body is plain text, unless an extension has been provided - MimetypeService mimetypeService = getMimetypeService(); - String mimetype = mimetypeService.guessMimetype(messageSubject); - if (mimetype.equals(MimetypeMap.MIMETYPE_BINARY)) + if (message.getBody().getSize() == -1) { - mimetype= MimetypeMap.MIMETYPE_TEXT_PLAIN; + // If message body is empty we write space as a content + // to make possible rule processing + // (Rules don't work on empty documents) + writeSpace(contentNodeRef); + } + else + { + InputStream contentIs = message.getBody().getContent(); + // The message body is plain text, unless an extension has been provided + MimetypeService mimetypeService = getMimetypeService(); + String mimetype = mimetypeService.guessMimetype(messageSubject); + if (mimetype.equals(MimetypeMap.MIMETYPE_BINARY)) + { + mimetype = MimetypeMap.MIMETYPE_TEXT_PLAIN; + } + // Use the default encoding. It will get overridden if the body is text. + String encoding = message.getBody().getEncoding(); + + writeContent(contentNodeRef, contentIs, mimetype, encoding); } - // Use the default encoding. It will get overridden if the body is text. - String encoding = message.getBody().getEncoding(); - - writeContent(contentNodeRef, contentIs, mimetype, encoding); } // Add attachments addAttachments(spaceNodeRef, contentNodeRef, message); } + /** + * This method writes space as a content. We need this space because rules doesn't proceed documents with empty content. We need rule processing for command email messages with + * empty body. + * + * @param nodeRef Reference to the parent node + */ + private void writeSpace(NodeRef nodeRef) + { + if (log.isDebugEnabled()) + { + log.debug("Write space string"); + } + + ContentService contentService = getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + writer.putContent(" "); + } + /** * Adds titled aspect to the specified node. * 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 4bc026ac23..91c344c1a5 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java @@ -153,7 +153,7 @@ public class SubethaEmailMessage implements EmailMessage try { - subject = mimeMessage.getSubject(); + subject = encodeSubject(mimeMessage.getSubject()); } catch (MessagingException e) { @@ -373,7 +373,8 @@ public class SubethaEmailMessage implements EmailMessage for (EmailMessagePart attachment : attachments) { - if (attachment instanceof SubethaEmailMessagePart) { + if (attachment instanceof SubethaEmailMessagePart) + { ((SubethaEmailMessagePart) attachment).setRmiRegistry(rmiRegistryHost, rmiRegistryPort); } } @@ -410,4 +411,24 @@ public class SubethaEmailMessage implements EmailMessage return attachments; } + /** + * Replaces characters \/*|:"<>? on their hex values. Subject field is used as name of the content, so we need to replace characters that are forbidden in content names. + * + * @param subject String representing subject + * @return Encoded string + */ + static private String encodeSubject(String subject) + { + String result = subject.trim(); + String[][] s = new String[][] { { "\\", "%5c" }, { "/", "%2f" }, { "*", "%2a" }, { "|", "%7c" }, { ":", "%3a" }, { "\"", "%22" }, { "<", "%3c" }, { ">", "%3e" }, + { "?", "%3f" } }; + + for (int i = 0; i < s.length; i++) + { + result = result.replace(s[i][0], s[i][1]); + } + + return result; + } + } diff --git a/source/java/org/alfresco/model/ImapModel.java b/source/java/org/alfresco/model/ImapModel.java new file mode 100755 index 0000000000..f918e53452 --- /dev/null +++ b/source/java/org/alfresco/model/ImapModel.java @@ -0,0 +1,60 @@ +/* + * 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.model; + +import org.alfresco.service.namespace.QName; + +/** + * IMAP Model Constants + * + * @author Mike Shavnev + */ +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_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 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 ASPECT_FLAGGABLE = QName.createQName(IMAP_MODEL_1_0_URI, "flaggable"); + static final QName PROP_FLAG_ANSWERED = QName.createQName(IMAP_MODEL_1_0_URI, "flagAnswered"); + static final QName PROP_FLAG_DELETED = QName.createQName(IMAP_MODEL_1_0_URI, "flagDeleted"); + static final QName PROP_FLAG_DRAFT = QName.createQName(IMAP_MODEL_1_0_URI, "flagDraft"); + static final QName PROP_FLAG_SEEN = QName.createQName(IMAP_MODEL_1_0_URI, "flagSeen"); + static final QName PROP_FLAG_RECENT = QName.createQName(IMAP_MODEL_1_0_URI, "flagRecent"); + static final QName PROP_FLAG_FLAGGED = QName.createQName(IMAP_MODEL_1_0_URI, "flagFlagged"); + + static final QName TYPE_IMAP_BODY = QName.createQName(IMAP_MODEL_1_0_URI, "imapBody"); + + static final QName TYPE_IMAP_ATTACH = QName.createQName(IMAP_MODEL_1_0_URI, "imapAttach"); + static final QName PROP_ATTACH_ID = QName.createQName(IMAP_MODEL_1_0_URI, "attachID"); + +} diff --git a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java index 29fef29d57..e82dd044a2 100644 --- a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java @@ -58,7 +58,8 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase private String companyHomePath; private StoreRef storeRef; private ScriptLocation scriptLocation; - + private String webApplicationContextUrl; + /** * @param serviceRegistry The serviceRegistry to set. */ @@ -94,7 +95,17 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase { this.scriptLocation = scriptLocation; } - + + /** + * Set the web application context url + * + * @param webApplicationContextUrl web application context url + */ + public void setWebApplicationContextUrl(String webApplicationContextUrl) + { + this.webApplicationContextUrl = webApplicationContextUrl; + } + /** * Allow adhoc properties to be passed to this action * @@ -149,7 +160,9 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase // Add the action to the default model ScriptAction scriptAction = new ScriptAction(this.serviceRegistry, action, this.actionDefinition); model.put("action", scriptAction); - + + model.put("webApplicationContextUrl", webApplicationContextUrl); + Object result = null; if (this.scriptLocation == null) { diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java new file mode 100755 index 0000000000..787d4c7a7f --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java @@ -0,0 +1,264 @@ +/* + * 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.admin.patch.impl; + +import java.io.IOException; +import java.util.List; +import java.util.Properties; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.ACPImportPackageHandler; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +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. + * + * 1. Company Home > Data Dictionary > Imap Config > Templates + * 2. Company Home > Data Dictionary > Email Actions > search + * 3. Company Home > Data Dictionary > Scripts > command-processor.js, command-search.js + * + * @author Arseny Kovalchuk + */ +public class ImapFoldersPatch extends AbstractPatch +{ + // messages' ids + private static final String MSG_EXISTS = "patch.imapFolders.result.exists"; + private static final String MSG_CREATED = "patch.imapFolders.result.created"; + + // folders' names for path building + private static final String PROPERTY_COMPANY_HOME_CHILDNAME = "spaces.company_home.childname"; + private static final String PROPERTY_DICTIONARY_CHILDNAME = "spaces.dictionary.childname"; + private static final String PROPERTY_SCRIPTS_CHILDNAME = "spaces.scripts.childname"; + private static final String PROPERTY_IMAP_CONFIG_CHILDNAME = "spaces.imapConfig.childname"; + + private ImporterBootstrap importerBootstrap; + private MessageSource messageSource; + protected Properties configuration; + private ImporterService importerService; + + private NodeRef companyHomeNodeRef; + private NodeRef dictionaryNodeRef; + private NodeRef scriptsNodeRef; + private NodeRef imapConfigFolderNodeRef; + private String configFoldersACP; + private String emailActionsACP; + private String scriptsACP; + + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + public void setMessageSource(MessageSource messageSource) + { + this.messageSource = messageSource; + } + + public void setImporterService(ImporterService importerService) + { + this.importerService = importerService; + } + + public void setConfigFoldersACP(String configFoldersACP) + { + this.configFoldersACP = configFoldersACP; + } + + public void setEmailActionsACP(String emailActionsACP) + { + this.emailActionsACP = emailActionsACP; + } + + public void setScriptsACP(String scriptsACP) + { + this.scriptsACP = scriptsACP; + } + + protected void checkCommonProperties() throws Exception + { + checkPropertyNotNull(importerBootstrap, "importerBootstrap"); + checkPropertyNotNull(messageSource, "messageSource"); + checkPropertyNotNull(importerService, "importerService"); + } + + protected void setUp() throws Exception + { + // get the node store that we must work against + StoreRef storeRef = importerBootstrap.getStoreRef(); + if (storeRef == null) + { + throw new PatchException("Bootstrap store has not been set"); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + this.configuration = importerBootstrap.getConfiguration(); + // get the association names that form the path + String companyHomeChildName = configuration.getProperty(PROPERTY_COMPANY_HOME_CHILDNAME); + if (companyHomeChildName == null || companyHomeChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_COMPANY_HOME_CHILDNAME + "' is not present"); + } + String dictionaryChildName = configuration.getProperty(PROPERTY_DICTIONARY_CHILDNAME); + if (dictionaryChildName == null || dictionaryChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_DICTIONARY_CHILDNAME + "' is not present"); + } + String scriptsChildName = configuration.getProperty(PROPERTY_SCRIPTS_CHILDNAME); + if (scriptsChildName == null || scriptsChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_CHILDNAME + "' is not present"); + } + + String imapConfigChildName = configuration.getProperty(PROPERTY_IMAP_CONFIG_CHILDNAME); + if (imapConfigChildName == null || imapConfigChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_IMAP_CONFIG_CHILDNAME + "' is not present"); + } + + // build the search string to get the company home node + StringBuilder sb = new StringBuilder(256); + sb.append("/").append(companyHomeChildName); + String xpath = sb.toString(); + // get the company home + List nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() == 0) + { + throw new PatchException("XPath didn't return any results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath); + } + else if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath + "\n" + " results: " + nodeRefs); + } + this.companyHomeNodeRef = nodeRefs.get(0); + + // build the search string to get the dictionary node + sb.append("/").append(dictionaryChildName); + xpath = sb.toString(); + // get the dictionary node + nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() == 0) + { + throw new PatchException("XPath didn't return any results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath); + } + else if (nodeRefs.size() > 1) + { + 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); + if (nodeRefs.size() == 0) + { + throw new PatchException("XPath didn't return any results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath); + } + else if (nodeRefs.size() > 1) + { + 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); + xpath = sb.toString(); + nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath + "\n" + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + this.imapConfigFolderNodeRef = null; + } + else + { + this.imapConfigFolderNodeRef = nodeRefs.get(0); + } + + } + + @Override + protected String applyInternal() throws Exception + { + checkCommonProperties(); + setUp(); + String msg = null; + if (imapConfigFolderNodeRef == null) + { + // import the content + RunAsWork importRunAs = new RunAsWork() + { + public Object doWork() throws Exception + { + importImapConfig(); + importScripts(); + importEmailActions(); + return null; + } + }; + AuthenticationUtil.runAs(importRunAs, authenticationContext.getSystemUserName()); + msg = I18NUtil.getMessage(MSG_CREATED); + } + else + { + msg = I18NUtil.getMessage(MSG_EXISTS, imapConfigFolderNodeRef); + } + return msg; + + } + + private void importImapConfig() throws IOException + { + importInternal(this.configFoldersACP, this.dictionaryNodeRef); + } + + private void importEmailActions() throws IOException + { + importInternal(this.emailActionsACP, this.dictionaryNodeRef); + } + + private void importScripts() throws IOException + { + importInternal(this.scriptsACP, this.scriptsNodeRef); + } + + 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); + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ImapUsersPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ImapUsersPatch.java new file mode 100755 index 0000000000..699ffa3e8d --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/ImapUsersPatch.java @@ -0,0 +1,257 @@ +/* + * 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.admin.patch.impl; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.springframework.context.MessageSource; + +/** + * @author Dmitry Vaserin + */ +public class ImapUsersPatch extends AbstractPatch +{ + // messages' ids + private static final String MSG_EXISTS = "patch.imapUserFolders.result.exists"; + private static final String MSG_CREATED = "patch.imapUserFolders.result.created"; + + // folders' names for path building + private static final String PROPERTY_COMPANY_HOME_CHILDNAME = "spaces.company_home.childname"; + private static final String PROPERTY_IMAP_HOME_FOLDER_CHILDNAME = "spaces.imap_home.childname"; + + private static final String PROPERTY_ICON = "space-icon-default"; + private static final String MSG_IMAP_HOME_FOLDER_NAME = "spaces.imap_home.name"; + private static final String MSG_IMAP_HOME_FOLDER_DESCRIPTION = "spaces.imap_home.description"; + + private static final String INBOX_NAME = "INBOX"; + private static final String INBOX_DECSRIPTION = "INBOX mail box"; + + private ImporterBootstrap importerBootstrap; + private MessageSource messageSource; + + protected NodeRef companyHomeNodeRef; + protected Properties configuration; + protected NodeRef imapHomeNodeRef; + + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + public void setMessageSource(MessageSource messageSource) + { + this.messageSource = messageSource; + } + + /** + * Ensure that required common properties have been set + */ + protected void checkCommonProperties() throws Exception + { + checkPropertyNotNull(importerBootstrap, "importerBootstrap"); + checkPropertyNotNull(namespaceService, "namespaceService"); + checkPropertyNotNull(searchService, "searchService"); + checkPropertyNotNull(nodeService, "nodeService"); + checkPropertyNotNull(messageSource, "messageSource"); + } + + /** + * Extracts pertinent references and properties that are common to execution of this and derived patches. + */ + protected void setUp() throws Exception + { + // get the node store that we must work against + StoreRef storeRef = importerBootstrap.getStoreRef(); + if (storeRef == null) + { + throw new PatchException("Bootstrap store has not been set"); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + this.configuration = importerBootstrap.getConfiguration(); + // get the association names that form the path + String companyHomeChildName = configuration.getProperty(PROPERTY_COMPANY_HOME_CHILDNAME); + if (companyHomeChildName == null || companyHomeChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_COMPANY_HOME_CHILDNAME + "' is not present"); + } + String imapHomeChildName = configuration.getProperty(PROPERTY_IMAP_HOME_FOLDER_CHILDNAME); + if (imapHomeChildName == null || imapHomeChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_IMAP_HOME_FOLDER_CHILDNAME + "' is not present"); + } + + // build the search string to get the company home node + StringBuilder sb = new StringBuilder(256); + sb.append("/").append(companyHomeChildName); + String xpath = sb.toString(); + // get the company home + List nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() == 0) + { + throw new PatchException("XPath didn't return any results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath); + } + else if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + " root: " + storeRootNodeRef + "\n" + " xpath: " + xpath + "\n" + " results: " + nodeRefs); + } + this.companyHomeNodeRef = nodeRefs.get(0); + + xpath = imapHomeChildName; + nodeRefs = searchService.selectNodes(companyHomeNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + " dictionary node: " + companyHomeNodeRef + "\n" + " xpath: " + xpath + "\n" + " results: " + + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + // the node does not exist + this.imapHomeNodeRef = null; + } + else + { + this.imapHomeNodeRef = nodeRefs.get(0); + } + } + + @Override + protected String applyInternal() throws Exception + { + // properties must be set + checkCommonProperties(); + // get useful values + setUp(); + + String msg = null; + + if (imapHomeNodeRef == null) + { + // create it + createImapHomeFolders(); + msg = I18NUtil.getMessage(MSG_CREATED, imapHomeNodeRef); + } + else + { + // it already exists + msg = I18NUtil.getMessage(MSG_EXISTS, imapHomeNodeRef); + } + + // done + return msg; + } + + private void createImapHomeFolders() + { + // get required properties + String imapHomeChildName = configuration.getProperty(PROPERTY_IMAP_HOME_FOLDER_CHILDNAME); + if (imapHomeChildName == null) + { + throw new PatchException("Bootstrap property '" + PROPERTY_IMAP_HOME_FOLDER_CHILDNAME + "' is not present"); + } + + String name = messageSource.getMessage(MSG_IMAP_HOME_FOLDER_NAME, null, I18NUtil.getLocale()); + if (name == null || name.length() == 0) + { + throw new PatchException("Bootstrap property '" + MSG_IMAP_HOME_FOLDER_NAME + "' is not present"); + } + + String description = messageSource.getMessage(MSG_IMAP_HOME_FOLDER_DESCRIPTION, null, I18NUtil.getLocale()); + if (description == null || description.length() == 0) + { + throw new PatchException("Bootstrap property '" + MSG_IMAP_HOME_FOLDER_DESCRIPTION + "' is not present"); + } + + imapHomeNodeRef = createSpace(companyHomeNodeRef, name, description, imapHomeChildName); + + // Create IMAP Home and "INBOX" for each user + createImapUserHomes(); + + } + + private void createImapUserHomes() + { + StringBuilder query = new StringBuilder(128); + query.append("@").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:userName:*"); + + SearchParameters params = new SearchParameters(); + params.setLanguage(SearchService.LANGUAGE_LUCENE); + params.addStore(importerBootstrap.getStoreRef()); + params.setQuery(query.toString()); + + ResultSet results = searchService.query(params); + List people; + try + { + people = results.getNodeRefs(); + } + finally + { + results.close(); + } + + for (NodeRef nodeRef : people) + { + String userName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); + String desc = userName + " " + messageSource.getMessage(MSG_IMAP_HOME_FOLDER_NAME, null, I18NUtil.getLocale()); + NodeRef userHome = createSpace(imapHomeNodeRef, userName, desc, userName); + // Create Inbox + createSpace(userHome, INBOX_NAME, INBOX_DECSRIPTION, INBOX_NAME); + + } + + } + + private NodeRef createSpace(NodeRef parent, String name, String desc, String childName) + { + Map properties = new HashMap(7); + properties.put(ContentModel.PROP_NAME, name); + properties.put(ContentModel.PROP_TITLE, name); + properties.put(ContentModel.PROP_DESCRIPTION, desc); + properties.put(ApplicationModel.PROP_ICON, PROPERTY_ICON); + // create the node + ChildAssociationRef childAssocRef = nodeService.createNode(parent, ContentModel.ASSOC_CONTAINS, QName.resolveToQName(namespaceService, childName), + ContentModel.TYPE_FOLDER, properties); + NodeRef result = childAssocRef.getChildRef(); + + // add the required aspects + nodeService.addAspect(result, ApplicationModel.ASPECT_UIFACETS, null); + return result; + } +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java b/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java new file mode 100755 index 0000000000..6fa28f2150 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapConst.java @@ -0,0 +1,72 @@ +/* + * 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; + +/** + * @author Mike Shavnev + */ +public interface AlfrescoImapConst +{ + + 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"; + + 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_"; + // 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. + */ + public static final String MODE_ARCHIVE = "archive"; + /** + * Defines {@link AlfrescoImapMailFolder} view mode as virtual mode. Used for IMAP Virtualised View. + */ + public static final String MODE_VIRTUAL = "virtual"; + + // Default content model email message templates + public static final String CLASSPATH_TEXT_PLAIN_TEMPLATE = "/alfresco/templates/imap/imap_message_text_plain.ftl"; + public static final String CLASSPATH_TEXT_HTML_TEMPLATE = "/alfresco/templates/imap/imap_message_text_html.ftl"; + + public static final String DICTIONARY_TEMPLATE_PREFIX = "emailbody"; + public static final String PREF_IMAP_FAVOURITE_SITES = "org.alfresco.share.sites.imap.favourites"; + + // AlfrescoImapMessage constants + public static final String MIME_VERSION = "MIME-Version"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + public static final String MULTIPART_MIXED = "mixed"; + public static final String CONTENT_ID = "Content-ID"; + public static final String X_ALF_NODEREF_ID = "X-Alfresco-NodeRef-ID"; // The NodeRef id header + public static final String X_ALF_SERVER_UID = "X-Alfresco-Server-UID"; // The unique identifier of Alfresco server + public static final String EIGHT_BIT_ENCODING = "8bit"; + public static final String BASE_64_ENCODING = "base64"; + public static final String UTF_8 = "UTF-8"; + +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java b/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java new file mode 100755 index 0000000000..34bd71f0c9 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java @@ -0,0 +1,958 @@ +/* + * 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.Arrays; +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.ImapConfigElement.ImapConfig; +import org.alfresco.repo.imap.exception.AlfrescoImapFolderException; +import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +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.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.icegreen.greenmail.imap.AuthorizationException; +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 + */ +public class AlfrescoImapHostManager implements ImapHostManager +{ + + private Log logger = LogFactory.getLog(AlfrescoImapHostManager.class); + + private ServiceRegistry serviceRegistry; + + private NodeService nodeService; + private FileFolderService fileFolderService; + private ImapHelper imapHelper; + + /** + * Returns the hierarchy delimiter for mailboxes on this host. + * + * @return The hierarchy delimiter character. + */ + public char getHierarchyDelimiter() + { + return AlfrescoImapConst.HIERARCHY_DELIMITER; + } + + /** + * Returns an collection of mailboxes. Method searches mailboxes under mount points defined for a specific user. Mount points include user's IMAP Virtualised Views and Email + * Archive Views. This method serves LIST command of the IMAP protocol. + * + * @param user User making the request + * @param mailboxPattern String name of a mailbox possible including a wildcard. + * @return Collection of mailboxes matching the pattern. + * @throws com.icegreen.greenmail.store.FolderException + */ + public Collection listMailboxes(GreenMailUser user, String mailboxPattern) throws FolderException + { + mailboxPattern = GreenMailUtil.convertFromUtf7(mailboxPattern); + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); + } + mailboxPattern = imapHelper.getMailPathInRepo(mailboxPattern); + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: mailboxPattern in alfresco=" + mailboxPattern); + } + + Collection result = new LinkedList(); + + Map mountPoints = imapHelper.getMountPoints(); + Map imapConfigs = imapHelper.getImapConfigs(); + + NodeRef mountPoint; + 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 mode = imapConfigs.get(mountPointName).getMode(); + + if (!mailboxPattern.equals("*")) + { + mountPoint = mountParent; + } + + boolean isVirtualView = imapConfigs.get(mountPointName).getMode().equals(AlfrescoImapConst.MODE_VIRTUAL); + Collection folders = listFolder(mountPoint, user, mailboxPattern, isVirtualView); + if (folders != null) + { + for (FileInfo folder : folders) + { + result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), folder, folder.getName(), mode, mountParent, mountPointName, imapHelper)); + } + } + if (mailboxPattern.equals("*")) + { + result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, mode, mountParent, mountPointName, imapHelper)); + } + } + mountPoint = imapHelper.getUserImapHomeRef(user.getLogin()); + Collection imapFolders = listFolder(mountPoint, user, mailboxPattern, false); + if (imapFolders != null) + { + for (FileInfo imapFolder : imapFolders) + { + result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), imapFolder, imapFolder.getName(), AlfrescoImapConst.MODE_ARCHIVE, mountPoint, null, + imapHelper)); + } + } + + return result; + } + + private Collection listFolder(NodeRef root, GreenMailUser user, String mailboxPattern, boolean isVirtualView) throws FolderException + { + if (logger.isDebugEnabled()) + { + logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); + } + + Collection result = new LinkedList(); + + 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)) + { + List list = imapHelper.searchFolders(root, name, true, isVirtualView); + if (list.size() > 0) + { + return list; + } + return null; + } + else if (name.endsWith("*")) + { + List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); + if (list.size() > 0) + { + result.addAll(list); + for (FileInfo fileInfo : list) + { + List childList = imapHelper.searchFolders(fileInfo.getNodeRef(), "*", true, isVirtualView); + result.addAll(childList); + } + return result; + } + return null; + } + else if (name.contains("%") || name.contains("*")) + { + List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); + if (list.size() > 0) + { + return list; + } + return null; + } + else + { + List list = imapHelper.searchFolders(root, name, false, isVirtualView); + if (list.size() > 0) + { + return list; + } + return null; + } + } + + List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); + for (FileInfo folder : list) + { + Collection childFolders = listFolder(folder.getNodeRef(), user, remainName, isVirtualView); + if (childFolders != null) + { + result.addAll(childFolders); + } + } + + if (result.isEmpty()) + { + return null; + } + + return result; + } + + /** + * Returns an collection of subscribed mailboxes. To appear in search result mailboxes should have {http://www.alfresco.org/model/imap/1.0}subscribed property specified for + * user. Method searches subscribed mailboxes under mount points defined for a specific user. Mount points include user's IMAP Virtualised Views and Email Archive Views. This + * method serves LSUB command of the IMAP protocol. + * + * @param user User making the request + * @param mailboxPattern String name of a mailbox possible including a wildcard. + * @return Collection of mailboxes matching the pattern. + * @throws com.icegreen.greenmail.store.FolderException + */ + public Collection listSubscribedMailboxes(GreenMailUser user, String mailboxPattern) throws FolderException + { + if (logger.isDebugEnabled()) + { + logger.debug("Listing subscribed mailboxes: mailboxPattern=" + mailboxPattern); + } + mailboxPattern = imapHelper.getMailPathInRepo(mailboxPattern); + if (logger.isDebugEnabled()) + { + logger.debug("Listing subscribed mailboxes: mailboxPattern in alfresco=" + mailboxPattern); + } + + Collection result = new LinkedList(); + + Map mountPoints = imapHelper.getMountPoints(); + Map imapConfigs = imapHelper.getImapConfigs(); + + NodeRef mountPoint; + + 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 = listSubscribedFolder(mountPoint, mountPoint, user, mailboxPattern, isVirtualView); + if (folders != null) + { + for (MailFolder mailFolder : folders) + { + AlfrescoImapMailFolder folder = (AlfrescoImapMailFolder) mailFolder; + folder.setMountPointName(mountPointName); + folder.setViewMode(viewMode); + folder.setMountParent(mountParent); + } + result.addAll(folders); + } + + if (mailboxPattern.equals("*")) + { + if (isSubscribed(mountPointFileInfo, user.getLogin())) + { + result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, viewMode, mountParent, mountPointName, imapHelper)); + } + // \NoSelect + else if (hasSubscribedChild(mountPointFileInfo, user.getLogin(), isVirtualView)) + { + result.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), mountPointFileInfo, mountPointName, viewMode, mountParent, mountPointName, imapHelper, + false)); + } + } + + } + + NodeRef root = imapHelper.getSpacesStoreNodeRef(); + + root = imapHelper.getUserImapHomeRef(user.getLogin()); + Collection imapFolders = listSubscribedFolder(root, root, user, mailboxPattern, false); + + if (imapFolders != null) + { + result.addAll(imapFolders); + } + + return result; + } + + private Collection listSubscribedFolder(NodeRef mailboxRoot, NodeRef root, GreenMailUser user, String mailboxPattern, 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)) + { + List list = imapHelper.searchFolders(root, name, true, isVirtualView); + Collection subscribedList = getSubscribed(list, user.getLogin()); + + if (subscribedList.size() > 0) + { + return createMailFolderList(user, subscribedList, mailboxRoot); + } + return null; + } + else if (name.endsWith("*")) + { + List fullList = new LinkedList(); + List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); + Collection subscribedList = getSubscribed(list, user.getLogin()); + + if (list.size() > 0) + { + fullList.addAll(subscribedList); + for (FileInfo fileInfo : list) + { + List childList = imapHelper.searchFolders(fileInfo.getNodeRef(), "*", true, isVirtualView); + fullList.addAll(getSubscribed(childList, user.getLogin())); + } + return createMailFolderList(user, fullList, mailboxRoot); + } + return null; + } + else if ("%".equals(name)) + { + List list = imapHelper.searchFolders(root, "*", false, isVirtualView); + LinkedList subscribedList = new LinkedList(); + + for (FileInfo fileInfo : list) + { + if (isSubscribed(fileInfo, user.getLogin())) + { + // folderName, viewMode, mountPointName will be setted in listSubscribedMailboxes() 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 listSubscribedMailboxes() method + subscribedList.add(new AlfrescoImapMailFolder(user.getQualifiedMailboxName(), fileInfo, null, null, mailboxRoot, null, imapHelper, false)); + } + } + + return subscribedList; + } + else if (name.contains("%") || name.contains("*")) + { + List list = imapHelper.searchFolders(root, name.replace('%', '*'), false, isVirtualView); + Collection 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 = 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 = listSubscribedFolder(mailboxRoot, folder.getNodeRef(), user, remainName, isVirtualView); + + if (childFolders != null) + { + result.addAll(childFolders); + } + } + + if (result.isEmpty()) + { + return null; + } + + return result; + } + + /** + * Renames an existing mailbox. The specified mailbox must already exist, the requested name must not exist already but must be able to be created and the user must have rights + * to delete the existing mailbox and create a mailbox with the new name. Any inferior hierarchical names must also be renamed. If INBOX is renamed, the contents of INBOX are + * transferred to a new mailbox with the new name, but INBOX is not deleted. If INBOX has inferior mailbox these are not renamed. This method serves RENAME command of the IMAP + * protocol.

Method searches mailbox under mount points defined for a specific user. Mount points include user's IMAP Virtualised Views and Email Archive Views. + * + * @param user User making the request. + * @param oldMailboxName String name of the existing folder + * @param newMailboxName String target new name + * @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()) + { + logger.debug("Renaming folder: oldMailboxName=" + oldMailboxName + " newMailboxName=" + 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()) + { + 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"); + } + } + } + } + + } + + /** + * Returns a reference to a newly created mailbox. The request should specify a mailbox that does not already exist on this server, that could exist on this server and that the + * user has rights to create. This method serves CREATE command of the IMAP protocol. + * + * @param user User making the request. + * @param mailboxName String name of the target + * @return an Mailbox reference. + * @throws com.icegreen.greenmail.store.FolderException if mailbox already exists + * @throws AlfrescoImapFolderException if user does not have rights to create the new mailbox. + */ + public MailFolder createMailbox(GreenMailUser user, String mailboxName) throws AuthorizationException, FolderException + { + mailboxName = GreenMailUtil.convertFromUtf7(mailboxName); + if (logger.isDebugEnabled()) + { + logger.debug("Creating folder: " + 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()) + { + 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(FolderException.ALREADY_EXISTS_LOCALLY); + } + + /** + * Deletes an existing MailBox. Specified mailbox must already exist on this server, and the user must have rights to delete it.

This method serves DELETE command of the + * IMAP protocol. + * + * @param user User making the request. + * @param mailboxName String name of the target + * @throws com.icegreen.greenmail.store.FolderException if mailbox has a non-selectable store with children + */ + 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()) + { + 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 = 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."); + } + } + } + + /** + * Returns a reference to an existing Mailbox. The requested mailbox must already exists on this server and the requesting user must have at least lookup rights.

It is + * also can be used by to obtain hierarchy delimiter by the LIST command:

C: 2 list "" ""

S: * LIST () "." ""

S: 2 OK LIST completed.

Method searches + * mailbox under mount points defined for a specific user. Mount points include user's IMAP Virtualised Views and Email Archive Views. + * + * @param user User making the request. + * @param mailboxName String name of the target. + * @return an Mailbox reference. + */ + 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"); + } + + /** + * Simply calls {@link #getFolder(GreenMailUser, String)}.

Added to implement {@link ImapHostManager}. + */ + public MailFolder getFolder(GreenMailUser user, String mailboxName, boolean mustExist) throws FolderException + { + return getFolder(user, mailboxName); + } + + /** + * Returns a reference to the user's INBOX. + * + * @param user The user making the request. + * @return The user's Inbox. + */ + public MailFolder getInbox(GreenMailUser user) throws FolderException + { + return getFolder(user, AlfrescoImapConst.INBOX_NAME); + } + + /** + * Not supported. May be used by GreenMailUser.create() method.

Added to implement {@link ImapHostManager}. + */ + public void createPrivateMailAccount(GreenMailUser user) throws FolderException + { + throw new UnsupportedOperationException(); + } + + /** + * Subscribes a user to a mailbox. The mailbox must exist locally and the user must have rights to modify it.

This method serves SUBSCRIBE command of the IMAP protocol. + * + * @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 + { + if (logger.isDebugEnabled()) + { + logger.debug("Subscribing: " + mailbox); + } + 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()); + } + + /** + * Unsubscribes from a given mailbox.

This method serves UNSUBSCRIBE command of the IMAP protocol. + * + * @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 + { + if (logger.isDebugEnabled()) + { + logger.debug("Unsubscribing: " + mailbox); + } + 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() + { + 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(List 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) + { + this.serviceRegistry = serviceRegistry; + } + + public ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setImapHelper(ImapHelper imapHelper) + { + this.imapHelper = imapHelper; + } + +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java new file mode 100755 index 0000000000..3537733ec6 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapMailFolder.java @@ -0,0 +1,1084 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +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.cmr.model.FileExistsException; +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.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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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 + * requests. + * + * @author Mike Shavnev + */ +public class AlfrescoImapMailFolder implements MailFolder +{ + + private static Log logger = LogFactory.getLog(AlfrescoImapMailFolder.class); + + /** + * Reference to the {@link FileInfo} object representing the folder. + */ + private FileInfo folderInfo; + + /** + * Reference to the root node of the store where folder is placed. + */ + private NodeRef rootNodeRef; + + /** + * Name of the mailbox (e.g. "admin" for admin user). + */ + private String qualifiedMailboxName; + + /** + * Name of the folder. + */ + private String folderName; + + /** + * Defines view mode. Can be one of the following: {@link AlfrescoImapConst#MODE_ARCHIVE} or {@link AlfrescoImapConst#MODE_VIRTUAL}. + */ + private String viewMode; + + /** + * Name of the mount point. + */ + private String mountPointName; + + /** + * Reference to the {@link ImapHelper} object. + */ + private ImapHelper imapHelper; + + /** + * Defines whether the folder is selectable or not. + */ + private Boolean selectable; + + /** + * Defines whether the folder is read-only for user or not. + */ + private Boolean readOnly; + + + private Map messages = new TreeMap(); + private boolean isBodyGenerated = false; + + private static final Flags PERMANENT_FLAGS = new Flags(); + + private List listeners = new LinkedList(); + + static + { + PERMANENT_FLAGS.add(Flags.Flag.ANSWERED); + PERMANENT_FLAGS.add(Flags.Flag.DELETED); + PERMANENT_FLAGS.add(Flags.Flag.DRAFT); + PERMANENT_FLAGS.add(Flags.Flag.FLAGGED); + PERMANENT_FLAGS.add(Flags.Flag.SEEN); + } + + /** + * Constructs {@link AlfrescoImapMailFolder} object. + * + * @param qualifiedMailboxName - name of the mailbox (e.g. "admin" for admin user). + * @param folderInfo - reference to the {@link FileInfo} object representing the folder. + * @param folderName - name of the folder. + * @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. + */ + public AlfrescoImapMailFolder(String qualifiedMailboxName, FileInfo folderInfo, String folderName, String viewMode, NodeRef rootNodeRef, String mountPointName, + ImapHelper imapHelper) + { + this(qualifiedMailboxName, folderInfo, folderName, viewMode, rootNodeRef, mountPointName, imapHelper, null); + } + + /** + * Constructs {@link AlfrescoImapMailFolder} object. + * + * @param qualifiedMailboxName - name of the mailbox (e.g. "admin" for admin user). + * @param folderInfo - reference to the {@link FileInfo} object representing the folder. + * @param folderName - name of the folder. + * @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 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) + { + 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; + + // MailFolder object can be null if it is used to obtain hierarchy delimiter by LIST command: + // Example: + // C: 2 list "" "" + // S: * LIST () "." "" + // S: 2 OK LIST completed. + if (folderInfo != null) + { + if (selectable == null) + { + // isSelectable(); + Boolean storedSelectable = !imapHelper.getNodeService().hasAspect(folderInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE); + if (storedSelectable == null) + { + setSelectable(true); + } + else + { + setSelectable(storedSelectable); + } + } + else + { + setSelectable(selectable); + } + + AccessStatus status = imapHelper.hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE); + if (status == AccessStatus.DENIED) + { + readOnly = true; + } + else + { + readOnly = false; + } + + } + else + { + setSelectable(false); + } + + } + + /** + * 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. + * + * @param message - message. + * @param flags - message flags. + * @param internalDate - not used. Current date used instead. + * @return + */ + public long appendMessage(MimeMessage message, Flags flags, Date internalDate) throws FolderException + { + 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); + } + + 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); + } + + } + + } + + /** + * 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(long uid, MailFolder toFolder) throws FolderException + { + AlfrescoImapMailFolder toImapMailFolder = (AlfrescoImapMailFolder) toFolder; + + if (toImapMailFolder.isReadOnly()) + { + throw new FolderException("Can't create folder - Permission denied"); + } + + NodeRef toNodeRef = toImapMailFolder.getFolderInfo().getNodeRef(); + + SimpleStoredMessage message = messages.get(uid); + FileInfo copyMess = ((AlfrescoImapMessage) message.getMimeMessage()).getMessageInfo(); + + List fis = new LinkedList(); + + if (imapHelper.getNodeService().getType(copyMess.getNodeRef()).equals(ImapModel.TYPE_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; + } + 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); + } + } + } + + /** + * Marks all messages in the folder as deleted using {@link Flags.Flag#DELETED} flag. + */ + public void deleteAllMessages() throws FolderException + { + if (this.readOnly) + { + throw new FolderException("Can't delete all - Permission denied"); + } + + for (SimpleStoredMessage mess : messages.values()) + { + AlfrescoImapMessage message = (AlfrescoImapMessage) mess.getMimeMessage(); + FileInfo fileInfo = message.getMessageInfo(); + imapHelper.setFlag(fileInfo, Flags.Flag.DELETED, true); + // comment out to physically remove content. + // fileFolderService.delete(fileInfo.getNodeRef()); + messages.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 + { + if (this.readOnly) + { + throw new FolderException("Can't expunge - Permission denied"); + } + + Collection listMess = messages.values(); + for (SimpleStoredMessage mess : listMess) + { + + Flags flags = getFlags(mess); + if (flags.contains(Flags.Flag.DELETED)) + { + NodeRef nodeRef = ((AlfrescoImapMessage) mess.getMimeMessage()).getMessageInfo().getNodeRef(); + imapHelper.getFileFolderService().delete(nodeRef); + } + } + } + + /** + * Returns the number of the first unseen message. + * + * @return Number of the first unseen message. + */ + public int getFirstUnseen() + { + return 0; + } + + /** + * 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() + { + + // If MailFolder object is used to obtain hierarchy delimiter by LIST command: + // Example: + // C: 2 list "" "" + // S: * LIST () "." "" + // S: 2 OK LIST completed. + + if (rootNodeRef == null) + { + return ""; + } + + 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); + + boolean isFirst = true; + for (FileInfo path : pathList) + { + fullName.append(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (isFirst) + { + fullName.append("\""); + isFirst = false; + if (mountPointName != null) + { + fullName.append(mountPointName); + } + else + { + fullName.append(path.getName()); + } + } + else + { + fullName.append(path.getName()); + } + } + fullName.append("\""); + } + catch (FileNotFoundException e) + { + logger.error(e); + } + if (logger.isDebugEnabled()) + { + logger.debug("fullName: " + fullName); + } + return GreenMailUtil.convertInUtf7(fullName.toString()); + } + + /** + * Returns message by its UID. + * + * @param uid - UID of the message. + * @return message. + */ + public SimpleStoredMessage getMessage(long uid) + { + if (!isBodyGenerated) + { + // regenerate messages list and include message body into result + getMessages(); + } + return messages.get(uid); + } + + /** + * Returns count of the messages in the folder. + * + * @return Count of the messages. + */ + public int getMessageCount() + { + if (messages.size() == 0) + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos, false); + } + if (logger.isDebugEnabled()) + { + logger.debug(folderInfo.getName() + " - Messages count:" + messages.size()); + } + return messages.size(); + } + + /** + * Returns UIDs of all messages in the folder. + * + * @return UIDS of the messages. + */ + public long[] getMessageUids() + { + if (messages == null || messages.size() == 0) + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos, false); + } + int len = messages.size(); + long[] uids = new long[len]; + Set keys = messages.keySet(); + int i = 0; + for (Long key : keys) + { + uids[i++] = key; + } + return uids; + } + + /** + * Returns list of all messages in the folder. + * + * @return list of {@link SimpleStoredMessage} objects. + */ + public List getMessages() + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + return getMessages(fileInfos, true); + } + + private List getMessages(List fileInfos, boolean generateBody) + { + isBodyGenerated = generateBody; + if (fileInfos == null || fileInfos.size() == 0) + { + messages = Collections.emptyMap(); + } + if (fileInfos.size() != messages.size() || generateBody) + { + for (FileInfo fileInfo : fileInfos) + { + try + { + Long key = getMessageUid(fileInfo); + SimpleStoredMessage message = new SimpleStoredMessage(new AlfrescoImapMessage(fileInfo, imapHelper, generateBody), new Date(), key); + messages.put(key, message); + if (logger.isDebugEnabled()) + { + logger.debug("Message added: " + fileInfo.getName()); + } + } + catch (MessagingException e) + { + logger.warn("Invalid message! File name:" + fileInfo.getName(), e); + } + } + } + return new LinkedList(messages.values()); + } + + /** + * Returns list of messages by filter. + * + * @param msgRangeFilter - {@link MsgRangeFilter} object representing filter. + * @return list of filtered messages. + */ + public List getMessages(MsgRangeFilter msgRangeFilter) + { + if (messages == null || messages.size() == 0 || !isBodyGenerated) + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos, true); + } + List ret = new ArrayList(); + for (int i = 0; i < messages.size(); i++) + { + if (msgRangeFilter.includes(i + 1)) + { + ret.add(messages.get(i)); + } + } + + return ret; + } + + /** + * 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(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) + { + // "==" is legal with primitives and autoboxing + if (key == uid) + { + return msn + 1; + } + msn++; + } + throw new FolderException("No such message."); + } + + /** + * Returns folder name. + * + * @return folder name. + */ + public String getName() + { + return folderName; + } + + /** + * 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() + { + List result = new ArrayList(); + + if (messages.size() == 0 || !isBodyGenerated) + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos, true); + } + + Collection values = messages.values(); + for (SimpleStoredMessage message : values) + { + if (!getFlags(message).contains(Flags.Flag.DELETED)) + { + result.add(message); + } + + } + if (logger.isDebugEnabled()) + { + logger.debug(folderInfo.getName() + " - Non deleted messages count:" + result.size()); + } + return result; + } + + /** + * Returns permanent flags. + * + * @return {@link Flags} object containing flags. + */ + public Flags getPermanentFlags() + { + return PERMANENT_FLAGS; + } + + /** + * 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(boolean reset) + { + if (messages.size() == 0) + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos, false); + } + + int count = 0; + Collection values = messages.values(); + for (SimpleStoredMessage message : values) + { + if (getFlags(message).contains(Flags.Flag.RECENT)) + { + count++; + if (reset) + { + imapHelper.setFlag(((AlfrescoImapMessage) message.getMimeMessage()).getMessageInfo(), Flags.Flag.RECENT, false); + } + } + + } + + if (logger.isDebugEnabled()) + { + logger.debug(folderInfo.getName() + " - Recent count: " + count + " reset: " + reset); + } + return count; + } + + /** + * Returns UIDNEXT value of the folder. + * + * @return UIDNEXT value. + */ + public long getUidNext() + { + return getUidValidity(); + } + + /** + * Returns UIDVALIDITY value of the folder. + * + * @return UIDVALIDITY value. + */ + public long getUidValidity() + { + return ((Date) imapHelper.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); + } + + /** + * 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() + { + if (messages.size() == 0) + { + List fileInfos = imapHelper.searchMails(folderInfo.getNodeRef(), "*", viewMode, false); + getMessages(fileInfos, false); + } + + int count = 0; + Collection values = messages.values(); + for (SimpleStoredMessage message : values) + { + if (!getFlags(message).contains(Flags.Flag.SEEN)) + { + count++; + } + + } + if (logger.isDebugEnabled()) + { + logger.debug(folderInfo.getName() + " - Unseen count: " + count); + } + 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. + * + * @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(Flags flags, long uid, FolderListener silentListener, boolean addUid) throws FolderException + { + 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); + } + + 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. + * + * @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(Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) throws 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); + } + + Long uidNotification = null; + if (addUid) + { + uidNotification = new Long(uid); + } + notifyFlagUpdate(msn, message.getFlags(), uidNotification, silentListener); + + } + + /** + * 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)) + { + return ((Date) imapHelper.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); + } + + return (Long) imapHelper.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_NODE_DBID); + } + + private Flags getFlags(SimpleStoredMessage mess) + { + return ((AlfrescoImapMessage) mess.getMimeMessage()).getFlags(); + } + + // ----------------------Getters and Setters---------------------------- + + public FileInfo getFolderInfo() + { + return folderInfo; + } + + public void setFolderName(String folderName) + { + this.folderName = folderName; + } + + public void setViewMode(String viewMode) + { + this.viewMode = viewMode; + } + + public void setMountPointName(String mountPointName) + { + this.mountPointName = mountPointName; + } + + public void setMountParent(NodeRef mountParent) + { + this.rootNodeRef = mountParent; + } + + /** + * Whether the folder is selectable. + * + * @return {@code boolean}. + */ + public boolean isSelectable() + { + + return this.selectable; + } + + /** + * Sets {@link #selectable} property. + * + * @param selectable - {@code boolean}. + */ + public void setSelectable(boolean selectable) + { + this.selectable = selectable; + // Map properties = folderInfo.getProperties(); + // properties.put(ImapModel.PROP_IMAP_FOLDER_SELECTABLE, this.selectable); + // imapHelper.setProperties(folderInfo, properties); + } + + + /** + * Whether the folder is read-only for user. + * @return {@code boolean} + */ + public boolean isReadOnly() + { + return readOnly; + } + +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java b/source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java new file mode 100755 index 0000000000..1d2155586a --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapMessage.java @@ -0,0 +1,508 @@ +/* + * 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 new file mode 100755 index 0000000000..1efdeb32fa --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java @@ -0,0 +1,123 @@ +/* + * 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 org.alfresco.util.AbstractLifecycleBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; + +import com.icegreen.greenmail.Managers; +import com.icegreen.greenmail.imap.ImapHostManager; +import com.icegreen.greenmail.imap.ImapServer; +import com.icegreen.greenmail.user.UserManager; +import com.icegreen.greenmail.util.ServerSetup; + +/** + * @author Mike Shavnev + */ +public class AlfrescoImapServer extends AbstractLifecycleBean +{ + + private static Log logger = LogFactory.getLog(AlfrescoImapServer.class); + + private ImapServer serverImpl; + + private int port = 143; + + private ImapHostManager imapHostManager; + + private UserManager imapUserManager; + + private boolean imapServerEnabled; + + private ImapHelper imapHelper; + + public void setImapServerEnabled(boolean imapServerEnabled) + { + this.imapServerEnabled = imapServerEnabled; + } + + public void setPort(int port) + { + this.port = port; + } + + public void setImapHostManager(ImapHostManager imapHostManager) + { + this.imapHostManager = imapHostManager; + } + + public void setImapUserManager(UserManager imapUserManager) + { + this.imapUserManager = imapUserManager; + } + + public void setImapHelper(ImapHelper imapHelper) + { + this.imapHelper = imapHelper; + } + + protected void onBootstrap(ApplicationEvent event) + { + if (imapServerEnabled && imapHelper.isPatchApplied()) + { + Managers imapManagers = new Managers() + { + public ImapHostManager getImapHostManager() + { + return imapHostManager; + } + + public UserManager getUserManager() + { + return imapUserManager; + } + }; + serverImpl = new ImapServer(new ServerSetup(port, null, ServerSetup.PROTOCOL_IMAP), imapManagers); + serverImpl.startService(null); + if (logger.isInfoEnabled()) + { + logger.info("IMAP service started on port " + this.port + "."); + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("IMAP service is disabled."); + } + } + } + + protected void onShutdown(ApplicationEvent event) + { + if (serverImpl != null) + { + serverImpl.stopService(null); + } + } + +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java b/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java new file mode 100755 index 0000000000..ed5d31aa71 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java @@ -0,0 +1,107 @@ +/* + * 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 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; + +/** + * Alfresco implementation of the GreenMailUser interface. + * + * @author Arseny Kovalchuk + */ +public class AlfrescoImapUser implements GreenMailUser +{ + private String userName; + private char[] password; + private String email; + + private ImapHostManager imapHostManager; + + public AlfrescoImapUser(String email, String login, String password, ImapHostManager imapHostManager) + { + this.email = email; + this.userName = login; + this.password = password.toCharArray(); + this.imapHostManager = imapHostManager; + } + + public void authenticate(String password) throws UserException + { + throw new UnsupportedOperationException(); + // This method is used in the POP3 greenmail implementation, so it is disabled for IMAP + // See AlfrescoImapUserManager.test() method. + } + + public void create() throws UserException + { + throw new UnsupportedOperationException(); + } + + public void delete() throws UserException + { + throw new UnsupportedOperationException(); + } + + public void deliver(MovingMessage msg) throws UserException + { + + } + + public void deliver(MimeMessage msg) throws UserException + { + + } + + public String getEmail() + { + return this.email; + } + + public String getLogin() + { + return this.userName; + } + + public String getPassword() + { + return new String(this.password); + } + + public String getQualifiedMailboxName() + { + return userName; + } + + public void setPassword(String password) + { + this.password = password.toCharArray(); + } + +} diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java b/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java new file mode 100755 index 0000000000..eb938cb728 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapUserManager.java @@ -0,0 +1,168 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthenticationService; +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; + +/** + * @author Arseny Kovalchuk + */ +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; + protected NodeService nodeService; + + public AlfrescoImapUserManager() + { + 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); + user.create(); + addUser(user); + return user; + } + + protected void addUser(GreenMailUser user) + { + userMap.put(user.getLogin(), user); + } + + public GreenMailUser getUser(String login) + { + return (GreenMailUser) userMap.get(login); + } + + public GreenMailUser getUserByEmail(String email) + { + GreenMailUser ret = getUser(email); + if (null == ret) + { + for (GreenMailUser user : userMap.values()) + { + // TODO: NPE! + if (user.getEmail().trim().equalsIgnoreCase(email.trim())) + { + return user; + } + } + } + return ret; + } + + public void deleteUser(GreenMailUser user) throws UserException + { + user = (GreenMailUser) userMap.remove(user.getLogin()); + if (user != null) + { + user.delete(); + } + } + + /** + * The login method. + * + * @see com.icegreen.greenmail.imap.commands.LoginCommand#doProcess() + */ + public boolean test(String userid, String password) + { + try + { + authenticationService.authenticate(userid, password.toCharArray()); + String email = null; + if (personService.personExists(userid)) + { + NodeRef personNodeRef = personService.getPerson(userid); + email = (String) nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL); + } + GreenMailUser user = new AlfrescoImapUser(email, userid, password, imapHostManager); + addUser(user); + } + catch (AuthenticationException ex) + { + logger.error("IMAP authentication failed for userid: " + userid); + return false; + } + return true; + } + + public ImapHostManager getImapHostManager() + { + return this.imapHostManager; + } + + public void setImapHostManager(ImapHostManager imapHostManager) + { + this.imapHostManager = imapHostManager; + } + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + +} diff --git a/source/java/org/alfresco/repo/imap/ImapHelper.java b/source/java/org/alfresco/repo/imap/ImapHelper.java new file mode 100755 index 0000000000..3326cd9be3 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/ImapHelper.java @@ -0,0 +1,980 @@ +/* + * 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.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigService; +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.ImapConfigElement; +import org.alfresco.repo.imap.config.ImapConfigElement.ImapConfig; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +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.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 + */ +public 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 ConfigService configService; + 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; + + 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 = getImapConfigs(); + 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 + */ + public NodeRef getUserImapHomeRef(String userName) + { + return fileFolderService.searchSimple(imapRootNodeRef, userName); + } + + 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 = getImapConfigs(); + Map mountPoints = new HashMap(); + + for (ImapConfig 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; + } + + /** + * Return map of imap configs. Name of config == key in the map + * + * @return map of imap configs. + */ + public Map getImapConfigs() + { + Config imapConfig = configService.getConfig("imapConfig"); + ImapConfigElement imapConfigElement = (ImapConfigElement) imapConfig.getConfigElement(ImapConfigElement.CONFIG_ELEMENT_ID); + return imapConfigElement.getImapConfigs(); + } + + /** + * 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 = getImapConfigs(); + 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 = getImapConfigs(); + 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 = getImapConfigs(); + 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 ConfigService getConfigService() + { + return configService; + } + + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + 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/config/ImapConfigElement.java b/source/java/org/alfresco/repo/imap/config/ImapConfigElement.java new file mode 100755 index 0000000000..5d7762c7d7 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/config/ImapConfigElement.java @@ -0,0 +1,143 @@ +/* + * 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.config; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.element.ConfigElementAdapter; + +public class ImapConfigElement extends ConfigElementAdapter +{ + private static final long serialVersionUID = -6911139959296875159L; + + public static final String CONFIG_ELEMENT_ID = "imapConfig"; + + private Map imapConfigs = new LinkedHashMap(8, 10f); + + public ImapConfigElement() + { + super(CONFIG_ELEMENT_ID); + } + + public ImapConfigElement(String name) + { + super(name); + } + + @Override + public ConfigElement combine(ConfigElement configElement) + { + ImapConfigElement combined = new ImapConfigElement(); + + // add all the imapConfigs from this element + for (ImapConfig imapConfig : getImapConfigs().values()) + { + combined.addImapConfig(imapConfig); + } + + // add all the imapConfigs from the given element + for (ImapConfig imapConfig : ((ImapConfigElement) configElement).getImapConfigs().values()) + { + combined.addImapConfig(imapConfig); + } + + return combined; + } + + public Map getImapConfigs() + { + return imapConfigs; + } + + public ImapConfig getImapConfig(String name) + { + return imapConfigs.get(name); + } + + void addImapConfig(ImapConfig imapConfig) + { + imapConfigs.put(imapConfig.getName(), imapConfig); + } + + public static class ImapConfig implements Serializable + { + private static final long serialVersionUID = 424330549937129149L; + + private String name; + private String mode; + private String store; + private String rootPath; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getMode() + { + return mode; + } + + public void setMode(String mode) + { + this.mode = mode; + } + + public String getStore() + { + return store; + } + + public void setStore(String store) + { + this.store = store; + } + + public String getRootPath() + { + return rootPath; + } + + public void setRootPath(String rootPath) + { + this.rootPath = rootPath; + } + + public static long getSerialVersionUID() + { + return serialVersionUID; + } + + } + +} diff --git a/source/java/org/alfresco/repo/imap/config/ImapElementReader.java b/source/java/org/alfresco/repo/imap/config/ImapElementReader.java new file mode 100755 index 0000000000..2e0e64b302 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/config/ImapElementReader.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.repo.imap.config; + +import java.util.Iterator; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.alfresco.repo.imap.config.ImapConfigElement.ImapConfig; +import org.dom4j.Element; + +public class ImapElementReader implements ConfigElementReader +{ + + private static final String ELEMENT_IMAP_CONFIG = "imapConfig"; + private static final String ELEMENT_IMAP = "imap"; + private static final String ELEMENT_STORE = "store"; + private static final String ELEMENT_ROOTPATH = "rootPath"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_MODE = "mode"; + + public ConfigElement parse(Element element) + { + ImapConfigElement configElement = null; + + if (element != null) + { + String elementName = element.getName(); + if (elementName.equals(ELEMENT_IMAP_CONFIG) == false) + { + throw new ConfigException("ImapElementReader can parse '" + ELEMENT_IMAP_CONFIG + "' elements only, the element passed is '" + elementName + "'"); + } + + configElement = new ImapConfigElement(); + + for (Iterator items = element.elementIterator(ELEMENT_IMAP); items.hasNext();) + { + Element item = items.next(); + + String name = item.attributeValue(ATTR_NAME); + String mode = item.attributeValue(ATTR_MODE); + String store = item.element(ELEMENT_STORE).getStringValue(); + String rootPath = item.element(ELEMENT_ROOTPATH).getStringValue(); + + ImapConfig imapConfig = new ImapConfig(); + imapConfig.setName(name); + imapConfig.setMode(mode); + imapConfig.setStore(store); + imapConfig.setRootPath(rootPath); + + configElement.addImapConfig(imapConfig); + } + } + return configElement; + } + +} diff --git a/source/java/org/alfresco/repo/imap/exception/AlfrescoImapFolderException.java b/source/java/org/alfresco/repo/imap/exception/AlfrescoImapFolderException.java new file mode 100755 index 0000000000..54bf098ed3 --- /dev/null +++ b/source/java/org/alfresco/repo/imap/exception/AlfrescoImapFolderException.java @@ -0,0 +1,46 @@ +/* + * 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.exception; + +import com.icegreen.greenmail.store.FolderException; + +/** + * Thrown on an inappropriate attempt to modify a folder. + * + * @author Ivan Rybnikov + */ +public class AlfrescoImapFolderException extends FolderException +{ + + private static final long serialVersionUID = -2721708848846740336L; + + public final static String PERMISSION_DENIED = "Can't create folder - Permission denied"; + + public AlfrescoImapFolderException(String message) + { + super(message); + } + +} diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 5f22d3e39a..8af5008ae5 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -48,6 +48,8 @@ import org.dom4j.io.SAXReader; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; +import com.werken.saxpath.XPathReader; + /** * Search component for use by the ScriptService. *

@@ -215,6 +217,26 @@ public final class Search extends BaseScopableProcessorExtension } } + /** + * Validation Xpath query + * + * @param query xpath query + * @return true if xpath query valid + */ + public boolean isValidXpathQuery(String query) + { + try + { + XPathReader reader = new XPathReader(); + reader.parse(query); + } + catch (Exception e) + { + return false; + } + return true; + } + /** * Execute a Lucene search *