diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml
index 4d72df3e65..a5f06cf3f7 100644
--- a/config/alfresco/cache-context.xml
+++ b/config/alfresco/cache-context.xml
@@ -736,4 +736,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.cache.routingContentStoreSharedCache
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.routingContentStoreTransactionalCache
+
+
+ 1000
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml
index 37e67b8875..9da0c7486d 100644
--- a/config/alfresco/content-services-context.xml
+++ b/config/alfresco/content-services-context.xml
@@ -104,6 +104,42 @@
+
+
+
+
+
+
+
+
+
+
+ default
+
+
+
+
+
+
+
+ defaultStoreSelector
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 38d4842c04..6a555db6ff 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -859,6 +859,9 @@
+
+
+
diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml
index 2f3dfa53af..6aa9af1a79 100644
--- a/config/alfresco/ehcache-default.xml
+++ b/config/alfresco/ehcache-default.xml
@@ -422,4 +422,13 @@
overflowToDisk="false"
/>
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/messages/content-service.properties b/config/alfresco/messages/content-service.properties
index d70c90bcad..74723c8fcb 100644
--- a/config/alfresco/messages/content-service.properties
+++ b/config/alfresco/messages/content-service.properties
@@ -19,4 +19,6 @@ content.http_reader.err.unrecognized=An unrecognized error occured when attempti
metadata.extraction.err.type_conversion=Metadata extraction failed because an extracted value failed to convert to the required type: \n Extractor: {0} \n Target Property QName: {1} \n Required Type: {2} \n Extracted Value: {3}
-transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file.
\ No newline at end of file
+transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file.
+
+content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}).
\ No newline at end of file
diff --git a/config/alfresco/messages/dictionary-messages.properties b/config/alfresco/messages/dictionary-messages.properties
index 27e8f1e966..7d397c4c5c 100644
--- a/config/alfresco/messages/dictionary-messages.properties
+++ b/config/alfresco/messages/dictionary-messages.properties
@@ -13,6 +13,7 @@ d_dictionary.constraint.err.invalid_type=Constraint type ''{0}'' on constraint '
d_dictionary.constraint.err.property_simple_and_list="Constraint ''{0}'' has both a simple and list value for property ''{1}''
d_dictionary.constraint.err.construct_failure=Failed to construct an instance of type ''{0}'' for constraint ''{1}''
d_dictionary.constraint.err.property_mismatch=Property mismatch setting property ''{0}'' on constraint ''{1}''
+d_dictionary.constraint.err.reserved_property=Property ''{0}'' is reserved and can't be set on constraint ''{1}''.
d_dictionary.constraint.err.property_not_set=Property ''{0}'' has not been set on constraint ''{1}''
d_dictionary.constraint.err.evaluate_exception=Exception during evaluation of constraint ''{0}'': {1}
@@ -23,6 +24,8 @@ d_dictionary.property.err.duplicate_constraint_on_property=Found duplicate const
d_dictionary.property.err.cannot_relax_mandatory=Cannot relax mandatory attribute of property ''{0}
d_dictionary.property.err.cannot_relax_mandatory_enforcement=Cannot relax mandatory attribute enforcement of property ''{0}
+d_dictionary.constraint.registered.not_registered=There is no constraint registered by name ''{0}''.
+
d_dictionary.constraint.regex.no_match=Value ''{0}'' does not match regular expression: {1}
d_dictionary.constraint.regex.match=Value ''{0}'' matches regular expression: {1}
d_dictionary.constraint.regex.error.cm\:filename=Value ''{0}'' is not valid as a file name. This property must be a valid file name.
diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml
index 2af9b53be9..70941d9363 100644
--- a/config/alfresco/model/contentModel.xml
+++ b/config/alfresco/model/contentModel.xml
@@ -20,6 +20,9 @@
false
+
+ defaultStoreSelector
+
@@ -956,7 +959,21 @@
-
+
+
+ ContentStore Selector
+
+
+ Store Name
+ d:text
+ true
+
+
+
+
+
+
+
Preferences
diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java
index bda5e4dcf7..1c2cd866d2 100644
--- a/source/java/org/alfresco/model/ContentModel.java
+++ b/source/java/org/alfresco/model/ContentModel.java
@@ -267,6 +267,10 @@ public interface ContentModel
static final QName ASPECT_THUMBNAILED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "thumbnailed");
static final QName ASSOC_THUMBNAILS = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "thumbnails");
+ // StoreSelector Aspect
+ static final QName ASPECT_STORE_SELECTOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "storeSelector");
+ static final QName PROP_STORE_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "storeName");
+
// Preference Aspect
static final QName ASPECT_PREFERENCES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "preferences");
static final QName PROP_PREFERENCE_VALUES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "preferenceValues");
diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java
index ebeac021d4..82cc4b74b0 100644
--- a/source/java/org/alfresco/repo/avm/AVMNodeService.java
+++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java
@@ -1302,10 +1302,15 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
// TODO Not sure this try block is necessary.
try
{
- // Invoke policy behaviors.
-// invokeBeforeUpdateNode(nodeRef);
-// Map oldProps = getProperties(nodeRef);
+ // Prepare fr policy invocation.
+ Map propsBefore = null;
+ if (fInvokePolicies)
+ {
+ propsBefore = getProperties(nodeRef);
+ }
+ // Remove all properties
fAVMService.deleteNodeProperties(avmVersionPath.getSecond());
+ // Rebuild node properties
Map values = new HashMap();
for (Map.Entry entry : properties.entrySet())
{
@@ -1332,7 +1337,14 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
PropertyValue propertyValue = makePropertyValue(propertyDef, value);
values.put(propertyQName, propertyValue);
}
+ // Finally set node properties
fAVMService.setNodeProperties(avmVersionPath.getSecond(), values);
+ // Invoke policies
+ if (fInvokePolicies)
+ {
+ Map propsAfter = properties;
+ invokeOnUpdateProperties(nodeRef, propsBefore, propsAfter);
+ }
// Invoke policy behaviors.
// invokeOnUpdateNode(nodeRef);
// invokeOnUpdateProperties(nodeRef, oldProps, properties);
@@ -1413,13 +1425,6 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
try
{
fAVMService.setContentData(avmVersionPath.getSecond(), (ContentData)value);
- if (fInvokePolicies)
- {
- Map propsBefore = new HashMap();
- Map propsAfter = new HashMap();
- propsAfter.put(ContentModel.PROP_CONTENT, value);
- invokeOnUpdateProperties(nodeRef, propsBefore, propsAfter);
- }
}
catch (ClassCastException e)
{
@@ -1430,10 +1435,20 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
}
try
{
- // Map propsBefore = getProperties(nodeRef);
+ Map propsBefore = null;
+ if (fInvokePolicies)
+ {
+ propsBefore = getProperties(nodeRef);
+ }
PropertyDefinition propertyDef = dictionaryService.getProperty(qname);
PropertyValue propertyValue = makePropertyValue(propertyDef, value);
fAVMService.setNodeProperty(avmVersionPath.getSecond(), qname, propertyValue);
+ if (fInvokePolicies)
+ {
+ Map propsAfter = new HashMap(propsBefore);
+ propsAfter.put(qname, value);
+ invokeOnUpdateProperties(nodeRef, propsBefore, propsAfter);
+ }
// Map propsAfter = getProperties(nodeRef);
// Invoke policy behaviors.
// invokeOnUpdateNode(nodeRef);
diff --git a/source/java/org/alfresco/repo/content/ContentTestSuite.java b/source/java/org/alfresco/repo/content/ContentTestSuite.java
index 12f039b254..5367cd1a41 100644
--- a/source/java/org/alfresco/repo/content/ContentTestSuite.java
+++ b/source/java/org/alfresco/repo/content/ContentTestSuite.java
@@ -37,6 +37,7 @@ import org.alfresco.repo.content.metadata.OpenOfficeMetadataExtracterTest;
import org.alfresco.repo.content.metadata.PdfBoxMetadataExtracterTest;
import org.alfresco.repo.content.replication.ContentStoreReplicatorTest;
import org.alfresco.repo.content.replication.ReplicatingContentStoreTest;
+import org.alfresco.repo.content.routing.StoreSelectorAspectContentStoreTest;
import org.alfresco.repo.content.transform.BinaryPassThroughContentTransformerTest;
import org.alfresco.repo.content.transform.ComplexContentTransformerTest;
import org.alfresco.repo.content.transform.ContentTransformerRegistryTest;
@@ -96,6 +97,7 @@ public class ContentTestSuite extends TestSuite
suite.addTestSuite(MimetypeMapTest.class);
suite.addTestSuite(RoutingContentServiceTest.class);
suite.addTestSuite(RoutingContentStoreTest.class);
+ suite.addTestSuite(StoreSelectorAspectContentStoreTest.class);
return suite;
}
diff --git a/source/java/org/alfresco/repo/content/NodeContentContext.java b/source/java/org/alfresco/repo/content/NodeContentContext.java
index 392ec56c79..d537160182 100644
--- a/source/java/org/alfresco/repo/content/NodeContentContext.java
+++ b/source/java/org/alfresco/repo/content/NodeContentContext.java
@@ -62,6 +62,19 @@ public class NodeContentContext extends ContentContext
this.propertyQName = propertyQName;
}
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("NodeContentContext")
+ .append("[ contentUrl=").append(getContentUrl())
+ .append(", existing=").append((getExistingContentReader() == null ? false : true))
+ .append(", nodeRef=").append(nodeRef)
+ .append(", propertyQName=").append(propertyQName)
+ .append("]");
+ return sb.toString();
+ }
+
/**
* @return Returns the node holding the content metadata
*/
diff --git a/source/java/org/alfresco/repo/content/routing/StoreSelectorAspectContentStore.java b/source/java/org/alfresco/repo/content/routing/StoreSelectorAspectContentStore.java
new file mode 100644
index 0000000000..fb8647513b
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/routing/StoreSelectorAspectContentStore.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.content.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.content.AbstractRoutingContentStore;
+import org.alfresco.repo.content.ContentContext;
+import org.alfresco.repo.content.ContentStore;
+import org.alfresco.repo.content.NodeContentContext;
+import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.util.PropertyCheck;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Implementation of a {@link AbstractRoutingContentStore routing content store} that diverts
+ * and moves content based on the cm:storeSelector aspect.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class StoreSelectorAspectContentStore extends AbstractRoutingContentStore implements InitializingBean
+{
+ private static final String ERR_INVALID_DEFAULT_STORE = "content.routing.err.invalid_default_store";
+
+ private static Log logger = LogFactory.getLog(StoreSelectorAspectContentStore.class);
+
+ private NodeService nodeService;
+ private Map storesByName;
+ private List stores;
+ private String defaultStoreName;
+
+ public StoreSelectorAspectContentStore()
+ {
+ }
+
+ /**
+ * @param nodeService the service to access the properties
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * @param storesByName a map of content stores keyed by a common name
+ */
+ public void setStoresByName(Map storesByName)
+ {
+ this.storesByName = storesByName;
+ this.stores = new ArrayList(storesByName.values());
+ }
+
+ /**
+ * @return Returns the stores keyed by store name
+ */
+ public Map getStoresByName()
+ {
+ return storesByName;
+ }
+
+ /**
+ * Set the name of the store to select if the content being created is not associated
+ * with any specific value in the cm:storeSelector or if the aspect is not
+ * present.
+ *
+ * @param defaultStoreName the name of one of the stores
+ *
+ * @see #setStoresByName(Map)
+ */
+ public void setDefaultStoreName(String defaultStoreName)
+ {
+ this.defaultStoreName = defaultStoreName;
+ }
+
+ /**
+ * Checks that the required properties are present
+ */
+ public void afterPropertiesSet() throws Exception
+ {
+ PropertyCheck.mandatory(this, "nodeService", nodeService);
+ PropertyCheck.mandatory(this, "storesByName", storesByName);
+ PropertyCheck.mandatory(this, "defaultStoreName", defaultStoreName);
+ // Check that the default store name is valid
+ if (storesByName.get(defaultStoreName) == null)
+ {
+ AlfrescoRuntimeException.create(ERR_INVALID_DEFAULT_STORE, defaultStoreName, storesByName.keySet());
+ }
+ }
+
+ @Override
+ protected List getAllStores()
+ {
+ return stores;
+ }
+
+ @Override
+ protected ContentStore selectWriteStore(ContentContext ctx)
+ {
+ ContentStore store;
+ String storeNameProp;
+ if (!(ctx instanceof NodeContentContext))
+ {
+ storeNameProp = "";
+ store = storesByName.get(defaultStoreName);
+ }
+ else
+ {
+ NodeRef nodeRef = ((NodeContentContext) ctx).getNodeRef(); // Never null
+ storeNameProp = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_STORE_NAME);
+ if (storeNameProp == null)
+ {
+ storeNameProp = "";
+ store = storesByName.get(defaultStoreName);
+ }
+ else
+ {
+ store = storesByName.get(storeNameProp);
+ if (store == null)
+ {
+ // There was no store with that name
+ storeNameProp = "";
+ store = storesByName.get(defaultStoreName);
+ }
+ }
+ }
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "ContentStore selected: \n" +
+ " Node context: " + ctx + "\n" +
+ " Store name: " + storeNameProp + "\n" +
+ " Store Selected: " + store);
+ }
+ return store;
+ }
+
+ /**
+ * A constraint that acts as a list of values, where the values are the store names
+ * injected into the {@link StoreSelectorAspectContentStore}.
+ *
+ * If the store is not active or is incorrectly configured, then this constraint
+ * will contain a single value of 'Default'. Any attempt to set another value will
+ * lead to constraint failures.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+ public static class StoreSelectorConstraint extends ListOfValuesConstraint
+ {
+ private StoreSelectorAspectContentStore store;
+ /**
+ * Required default constructor
+ */
+ public StoreSelectorConstraint()
+ {
+ }
+
+ public void setStore(StoreSelectorAspectContentStore store)
+ {
+ this.store = store;
+ }
+
+ @Override
+ public void initialize()
+ {
+ checkPropertyNotNull("store", store);
+ List allowedValues = new ArrayList(store.getStoresByName().keySet());
+ super.setAllowedValues(allowedValues);
+ // Now initialize as we have set the LOV
+ super.initialize();
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/routing/StoreSelectorAspectContentStoreTest.java b/source/java/org/alfresco/repo/content/routing/StoreSelectorAspectContentStoreTest.java
new file mode 100644
index 0000000000..d6ab836277
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/routing/StoreSelectorAspectContentStoreTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.content.routing;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.content.ContentServiceImpl;
+import org.alfresco.repo.content.ContentStore;
+import org.alfresco.repo.content.filestore.FileContentStore;
+import org.alfresco.repo.content.routing.StoreSelectorAspectContentStore.StoreSelectorConstraint;
+import org.alfresco.repo.node.integrity.IntegrityException;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.TempFileProvider;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * Tests {@link StoreSelectorAspectContentStore}
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class StoreSelectorAspectContentStoreTest extends TestCase
+{
+ private static final String STORE_ONE = "Store1";
+ private static final String STORE_TWO = "Store2";
+ private static final String STORE_THREE = "Store3";
+
+ private static ConfigurableApplicationContext ctx =
+ (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private NodeService nodeService;
+ private FileFolderService fileFolderService;
+
+ private Map storesByName;
+ private FileContentStore fileStore1;
+ private FileContentStore fileStore2;
+ private FileContentStore fileStore3;
+ private StoreSelectorAspectContentStore store;
+ private NodeRef contentNodeRef;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ transactionService = serviceRegistry.getTransactionService();
+ nodeService = serviceRegistry.getNodeService();
+ fileFolderService = serviceRegistry.getFileFolderService();
+
+ AuthenticationUtil.pushAuthentication();
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
+
+ fileStore1 = new FileContentStore(
+ ctx,
+ TempFileProvider.getSystemTempDir() + "/fileStore1");
+ fileStore2 = new FileContentStore(
+ ctx,
+ TempFileProvider.getSystemTempDir() + "/fileStore2");
+ fileStore3 = new FileContentStore(
+ ctx,
+ TempFileProvider.getSystemTempDir() + "/fileStore3");
+
+ storesByName = new HashMap(7);
+ storesByName.put(STORE_ONE, fileStore1);
+ storesByName.put(STORE_TWO, fileStore2);
+ storesByName.put(STORE_THREE, fileStore3);
+
+ store = (StoreSelectorAspectContentStore) ctx.getBean("storeSelectorContentStore");
+ store.setStoresByName(storesByName);
+ store.setDefaultStoreName(STORE_ONE);
+ store.afterPropertiesSet();
+
+ // Force the constraint to re-initialize
+ StoreSelectorConstraint storeConstraint = (StoreSelectorConstraint) ctx.getBean("storeSelectorContentStore.constraint");
+ storeConstraint.initialize();
+
+ // Change the content service's default store
+ ContentServiceImpl contentService = (ContentServiceImpl) ctx.getBean("contentService");
+ contentService.setStore(store);
+
+ // Create a content node
+ RetryingTransactionCallback makeNodeCallback = new RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ StoreRef storeRef = nodeService.createStore(
+ StoreRef.PROTOCOL_TEST,
+ getName() + "_" + System.currentTimeMillis());
+ NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
+ // Create a folder
+ NodeRef folderNodeRef = nodeService.createNode(
+ rootNodeRef,
+ ContentModel.ASSOC_CHILDREN,
+ ContentModel.ASSOC_CHILDREN,
+ ContentModel.TYPE_FOLDER).getChildRef();
+ // Add some content
+ return fileFolderService.create(
+ folderNodeRef,
+ getName() + ".txt",
+ ContentModel.TYPE_CONTENT).getNodeRef();
+ }
+ };
+ contentNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(makeNodeCallback);
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ AuthenticationUtil.popAuthentication();
+ }
+
+ /**
+ * Writes to the file
+ * @return Returns the new content URL
+ */
+ private String writeToFile()
+ {
+ RetryingTransactionCallback writeContentCallback = new RetryingTransactionCallback()
+ {
+ public String execute() throws Throwable
+ {
+ ContentWriter writer = fileFolderService.getWriter(contentNodeRef);
+ writer.putContent("Some test content");
+ return writer.getContentUrl();
+ }
+ };
+ return transactionService.getRetryingTransactionHelper().doInTransaction(writeContentCallback);
+ }
+
+ /**
+ * Set the name of the store that must hold the content
+ * @param storeName the name of the store
+ */
+ private void setStoreNameProperty(String storeName)
+ {
+ // The nodeService is transactional
+ nodeService.setProperty(contentNodeRef, ContentModel.PROP_STORE_NAME, storeName);
+ }
+
+ /**
+ * Ensure that a nullcm:storeName property is acceptable.
+ */
+ public void testNullStoreNameProperty() throws Exception
+ {
+ try
+ {
+ setStoreNameProperty(null);
+ }
+ catch (Throwable e)
+ {
+ throw new Exception("Failed to set store name property to null", e);
+ }
+ }
+
+ /**
+ * Ensure that an invalid cm:storeName property is kicked out.
+ */
+ public void testInvalidStoreNameProperty() throws Exception
+ {
+ RetryingTransactionCallback
@@ -159,6 +166,7 @@
+
diff --git a/source/java/org/alfresco/service/cmr/dictionary/Constraint.java b/source/java/org/alfresco/service/cmr/dictionary/Constraint.java
index 55e2deeca1..4aa3edf027 100644
--- a/source/java/org/alfresco/service/cmr/dictionary/Constraint.java
+++ b/source/java/org/alfresco/service/cmr/dictionary/Constraint.java
@@ -56,7 +56,7 @@ public interface Constraint
/**
* Returns the parameters passed to the instance of the constraint.
*
- * @return Map of parameters, null if there are no parameters
+ * @return Map of parameters or an empty Map if none exist
*/
public Map getParameters();