diff --git a/config/alfresco/bootstrap/spaces.xml b/config/alfresco/bootstrap/spaces.xml
index 18d37d6e33..0d2d5665c9 100644
--- a/config/alfresco/bootstrap/spaces.xml
+++ b/config/alfresco/bootstrap/spaces.xml
@@ -2,75 +2,82 @@
xmlns:cm="http://www.alfresco.org/model/content/1.0"
xmlns:app="http://www.alfresco.org/model/application/1.0">
-
-
-
-
- GROUP_EVERYONE
- Consumer
-
-
-
- ${spaces.company_home.name}
- space-icon-default
- ${spaces.company_home.name}
- ${spaces.company_home.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}
-
-
-
-
- GROUP_EVERYONE
- Contributor
-
-
-
- ${spaces.savedsearches.name}
- space-icon-default
- ${spaces.savedsearches.name}
- ${spaces.savedsearches.description}
-
-
-
-
-
-
- guest
- Consumer
-
-
+
+
+
+ GROUP_EVERYONE
+ Consumer
+
+
+
+ ${spaces.company_home.name}
+ space-icon-default
+ ${spaces.company_home.name}
+ ${spaces.company_home.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}
+
+
+
+
GROUP_EVERYONE
- Consumer
-
-
-
- ${spaces.guest_home.name}
- space-icon-default
- ${spaces.guest_home.name}
- ${spaces.guest_home.description}
+ Contributor
+
+
+
+ ${spaces.savedsearches.name}
+ space-icon-default
+ ${spaces.savedsearches.name}
+ ${spaces.savedsearches.description}
+
+
-
-
+
+
+
+ guest
+ Consumer
+
+
+ GROUP_EVERYONE
+ Consumer
+
+
+
+ ${spaces.guest_home.name}
+ space-icon-default
+ ${spaces.guest_home.name}
+ ${spaces.guest_home.description}
+
+
+
diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 73ed20cf93..ca532b33d6 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -741,6 +741,7 @@
${spaces.dictionary.childname}
${spaces.templates.childname}
${spaces.templates.content.childname}
+ ${spaces.templates.email.childname}
${spaces.savedsearches.childname}
diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties
index 1feb9b1afa..13c73f403e 100644
--- a/config/alfresco/messages/bootstrap-spaces.properties
+++ b/config/alfresco/messages/bootstrap-spaces.properties
@@ -12,6 +12,9 @@ spaces.templates.description=Space templates
spaces.templates.content.name=Presentation Templates
spaces.templates.content.description=Presentation templates
+spaces.templates.email.name=Email Templates
+spaces.templates.email.description=Email templates
+
spaces.savedsearches.name=Saved Searches
spaces.savedsearches.description=Saved Searches
diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties
index 4904d81e29..6c01ea98dd 100644
--- a/config/alfresco/messages/patch-service.properties
+++ b/config/alfresco/messages/patch-service.properties
@@ -42,4 +42,8 @@ patch.contentPermission.description=Update permission entries from 'cm:content'
patch.contentPermission.result=Created the following permission reference names: {0}. \nUpdated {1} permission entries.
patch.forumsIcons.description=Updates forums icon references
-patch.forumsIcons.result=Updated {0} icon references
\ No newline at end of file
+patch.forumsIcons.result=Updated {0} icon references
+
+patch.emailTemplatesFolder.description=Ensures the existence of the 'Email Templates' folder.
+patch.emailTemplatesFolder.result.exists=The email templates folder already exists: {0}
+patch.emailTemplatesFolder.result.created=The email templates folder was successfully created: {0}
diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml
index b55ab6cb26..1f788b0ea2 100644
--- a/config/alfresco/model-specific-services-context.xml
+++ b/config/alfresco/model-specific-services-context.xml
@@ -19,6 +19,7 @@
/${spaces.company_home.childname}/${spaces.dictionary.childname}
/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.childname}
/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname}
+ /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}
diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml
index 8587332e51..444506f106 100644
--- a/config/alfresco/patch/patch-services-context.xml
+++ b/config/alfresco/patch/patch-services-context.xml
@@ -296,5 +296,16 @@
+
+ patch.emailTemplatesFolder
+ patch.emailTemplatesFolder.description
+ 0
+ 8
+ 9
+
+
+
+
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index cd0f73d9d7..34350f7ce8 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -88,6 +88,7 @@ spaces.guest_home.childname=app:guest_home
spaces.dictionary.childname=app:dictionary
spaces.templates.childname=app:space_templates
spaces.templates.content.childname=app:content_templates
+spaces.templates.email.childname=app:email_templates
spaces.savedsearches.childname=app:saved_searches
# Folders for storing people
diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties
index fff64d2a1a..e86b455a1f 100644
--- a/config/alfresco/version.properties
+++ b/config/alfresco/version.properties
@@ -15,4 +15,4 @@ version.edition=Community Network
# Schema number
-version.schema=8
+version.schema=9
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesFolderPatch.java
new file mode 100644
index 0000000000..2c169a2eda
--- /dev/null
+++ b/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesFolderPatch.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+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.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.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.springframework.context.MessageSource;
+
+/**
+ * Ensures that the email templates folder is present.
+ *
+ * This uses the bootstrap importer to get the paths to look for. If not present,
+ * the required structures are created.
+ *
+ * This class should be replaced with a more generic ImporterPatch
+ * that can do conditional importing into given locations.
+ *
+ * JIRA: {@link http://www.alfresco.org/jira/browse/AR-342 AR-342}
+ *
+ * @author Kevin Roast
+ */
+public class EmailTemplatesFolderPatch extends AbstractPatch
+{
+ private static final String MSG_EXISTS = "patch.emailTemplatesFolder.result.exists";
+ private static final String MSG_CREATED = "patch.emailTemplatesFolder.result.created";
+
+ public static final String PROPERTY_COMPANY_HOME_CHILDNAME = "spaces.company_home.childname";
+ public static final String PROPERTY_DICTIONARY_CHILDNAME = "spaces.dictionary.childname";
+ public static final String PROPERTY_EMAIL_TEMPLATES_FOLDER_CHILDNAME = "spaces.templates.email.childname";
+ private static final String PROPERTY_EMAIL_TEMPLATES_FOLDER_NAME = "spaces.templates.email.name";
+ private static final String PROPERTY_EMAIL_TEMPLATES_FOLDER_DESCRIPTION = "spaces.templates.email.description";
+ private static final String PROPERTY_ICON = "space-icon-default";
+
+ private ImporterBootstrap importerBootstrap;
+ private NamespaceService namespaceService;
+ private SearchService searchService;
+ private NodeService nodeService;
+ private MessageSource messageSource;
+
+ protected NodeRef dictionaryNodeRef;
+ protected Properties configuration;
+ protected NodeRef emailTemplatesFolderNodeRef;
+
+ public void setImporterBootstrap(ImporterBootstrap importerBootstrap)
+ {
+ this.importerBootstrap = importerBootstrap;
+ }
+
+ public void setNamespaceService(NamespaceService namespaceService)
+ {
+ this.namespaceService = namespaceService;
+ }
+
+ public void setSearchService(SearchService searchService)
+ {
+ this.searchService = searchService;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setMessageSource(MessageSource messageSource)
+ {
+ this.messageSource = messageSource;
+ }
+
+ /**
+ * Ensure that required common properties have been set
+ */
+ protected void checkCommonProperties() throws Exception
+ {
+ if (importerBootstrap == null)
+ {
+ throw new PatchException("'importerBootstrap' property has not been set");
+ }
+ else if (namespaceService == null)
+ {
+ throw new PatchException("'namespaceService' property has not been set");
+ }
+ else if (searchService == null)
+ {
+ throw new PatchException("'searchService' property has not been set");
+ }
+ else if (nodeService == null)
+ {
+ throw new PatchException("'nodeService' property has not been set");
+ }
+ }
+
+ /**
+ * 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 dictionaryChildName = configuration.getProperty(PROPERTY_DICTIONARY_CHILDNAME);
+ if (dictionaryChildName == null || dictionaryChildName.length() == 0)
+ {
+ throw new PatchException("Bootstrap property '" + PROPERTY_DICTIONARY_CHILDNAME + "' is not present");
+ }
+ String emailTemplatesChildName = configuration.getProperty(PROPERTY_EMAIL_TEMPLATES_FOLDER_CHILDNAME);
+ if (emailTemplatesChildName == null || emailTemplatesChildName.length() == 0)
+ {
+ throw new PatchException("Bootstrap property '" + PROPERTY_EMAIL_TEMPLATES_FOLDER_CHILDNAME + "' is not present");
+ }
+
+ // build the search string to get the dictionary node
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("/").append(companyHomeChildName)
+ .append("/").append(dictionaryChildName);
+ String xpath = sb.toString();
+
+ // get the dictionary node
+ 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.dictionaryNodeRef = nodeRefs.get(0);
+
+ // Now we have the optional part. Check for the existence of the email templates folder
+ xpath = emailTemplatesChildName;
+ nodeRefs = searchService.selectNodes(dictionaryNodeRef, xpath, null, namespaceService, false);
+ if (nodeRefs.size() > 1)
+ {
+ throw new PatchException("XPath returned too many results: \n" +
+ " dictionary node: " + dictionaryNodeRef + "\n" +
+ " xpath: " + xpath + "\n" +
+ " results: " + nodeRefs);
+ }
+ else if (nodeRefs.size() == 0)
+ {
+ // the node does not exist
+ this.emailTemplatesFolderNodeRef = null;
+ }
+ else
+ {
+ // we have the email templates folder noderef
+ this.emailTemplatesFolderNodeRef = nodeRefs.get(0);
+ }
+ }
+
+ @Override
+ protected String applyInternal() throws Exception
+ {
+ // common properties must be set before we can continue
+ checkCommonProperties();
+ if (messageSource == null)
+ {
+ throw new PatchException("'messageSource' property has not been set");
+ }
+
+ setUp();
+
+ // create the folder if needed - output a message to describe the result
+ String msg = null;
+ if (emailTemplatesFolderNodeRef == null)
+ {
+ createFolder();
+ msg = I18NUtil.getMessage(MSG_CREATED, emailTemplatesFolderNodeRef);
+ }
+ else
+ {
+ msg = I18NUtil.getMessage(MSG_EXISTS, emailTemplatesFolderNodeRef);
+ }
+
+ return msg;
+ }
+
+ private void createFolder()
+ {
+ // get required properties
+ String emailTemplatesChildName = configuration.getProperty(PROPERTY_EMAIL_TEMPLATES_FOLDER_CHILDNAME);
+ if (emailTemplatesChildName == null)
+ {
+ throw new PatchException("Bootstrap property '" + PROPERTY_EMAIL_TEMPLATES_FOLDER_CHILDNAME + "' is not present");
+ }
+
+ String emailTemplatesName = messageSource.getMessage(
+ PROPERTY_EMAIL_TEMPLATES_FOLDER_NAME,
+ null,
+ I18NUtil.getLocale());
+ if (emailTemplatesName == null || emailTemplatesName.length() == 0)
+ {
+ throw new PatchException("Bootstrap property '" + PROPERTY_EMAIL_TEMPLATES_FOLDER_NAME + "' is not present");
+ }
+
+ String emailTemplatesDescription = messageSource.getMessage(
+ PROPERTY_EMAIL_TEMPLATES_FOLDER_DESCRIPTION,
+ null,
+ I18NUtil.getLocale());
+ if (emailTemplatesDescription == null || emailTemplatesDescription.length() == 0)
+ {
+ throw new PatchException("Bootstrap property '" + PROPERTY_EMAIL_TEMPLATES_FOLDER_DESCRIPTION + "' is not present");
+ }
+
+ Map properties = new HashMap(7);
+ properties.put(ContentModel.PROP_NAME, emailTemplatesName);
+ properties.put(ContentModel.PROP_TITLE, emailTemplatesName);
+ properties.put(ContentModel.PROP_DESCRIPTION, emailTemplatesDescription);
+ properties.put(ContentModel.PROP_ICON, PROPERTY_ICON);
+
+ // create the node
+ ChildAssociationRef childAssocRef = nodeService.createNode(
+ dictionaryNodeRef,
+ ContentModel.ASSOC_CONTAINS,
+ QName.resolveToQName(namespaceService, emailTemplatesChildName),
+ ContentModel.TYPE_FOLDER,
+ properties);
+ emailTemplatesFolderNodeRef = childAssocRef.getChildRef();
+
+ // add the required aspects
+ nodeService.addAspect(emailTemplatesFolderNodeRef, ContentModel.ASPECT_UIFACETS, null);
+ }
+}