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 += "";
+ 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 0000000000..dda4f430b3
Binary files /dev/null and b/config/alfresco/templates/imap/command_processor_scripts.acp differ
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 0000000000..f54d9069c0
Binary files /dev/null and b/config/alfresco/templates/imap/email_actions_space.acp differ
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 0000000000..d2dee2323c
Binary files /dev/null and b/config/alfresco/templates/imap/imap_config_space.acp differ
diff --git a/config/alfresco/templates/imap/imap_message_text_html.ftl b/config/alfresco/templates/imap/imap_message_text_html.ftl
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>
+ Title: ${document.properties.title}
+ <#else>
+ Title:
+ #if>
+ <#if document.properties.description?exists>
+ Description: ${document.properties.description}
+ <#else>
+ Description:
+ #if>
+ 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
+
+
+
+
+
\ 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>
+ <#if document.properties.description?exists>
+Description: ${document.properties.description}
+ <#else>
+Description: NONE
+ #if>
+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>
+ Title: ${document.properties.title}
+ <#else>
+ Title:
+ #if>
+ <#if document.properties.description?exists>
+ Description: ${document.properties.description}
+ <#else>
+ Description:
+ #if>
+ 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
+
+
+
+
+
+
+
+
+
\ 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 html body part ------------------------>
+ // <------------------------ 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);
+ // ------------------------ text plain body part ------------------------>
+
+ // 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
*