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 null cm: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 setInvalidStoreNameCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + setStoreNameProperty("bogus"); + return null; + } + }; + try + { + transactionService.getRetryingTransactionHelper().doInTransaction(setInvalidStoreNameCallback, false, true); + setStoreNameProperty("bogus"); + fail("Expected integrity error for bogus store name"); + } + catch (IntegrityException e) + { + // Expected + } + } + + /** + * Check that the default store is used if the property is not set + */ + public void testWriteWithoutAspect() throws Exception + { + String contentUrl = writeToFile(); + // The content should be in the default store + assertTrue("Default store does not have content", fileStore1.exists(contentUrl)); + assertFalse("Mapped store should not have content", fileStore2.exists(contentUrl)); + assertFalse("Mapped store should not have content", fileStore3.exists(contentUrl)); + } + + public void testSimpleWritesWithAspect() throws Exception + { + for (Map.Entry entry : storesByName.entrySet()) + { + String storeName = entry.getKey(); + ContentStore store = entry.getValue(); + setStoreNameProperty(storeName); + String contentUrl = writeToFile(); + assertTrue("Content not in store " + storeName, store.exists(contentUrl)); + } + } +} diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index e20669072b..e2ba3f5deb 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -41,8 +41,12 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.EhCacheAdapter; import org.alfresco.repo.dictionary.DictionaryDAOImpl.DictionaryRegistry; import org.alfresco.repo.dictionary.NamespaceDAOImpl.NamespaceRegistry; +import org.alfresco.repo.dictionary.constraint.AbstractConstraint; +import org.alfresco.repo.dictionary.constraint.ConstraintRegistry; import org.alfresco.repo.dictionary.constraint.RegexConstraint; +import org.alfresco.repo.dictionary.constraint.RegisteredConstraint; import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; +import org.alfresco.repo.dictionary.constraint.UserNameConstraint; import org.alfresco.repo.tenant.SingleTServiceImpl; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.AssociationDefinition; @@ -73,6 +77,17 @@ public class DictionaryDAOTest extends TestCase @Override public void setUp() { + // Registered the required constraints + ConstraintRegistry constraintRegistry = ConstraintRegistry.getInstance(); + AbstractConstraint constraintReg1 = new UserNameConstraint(); + constraintReg1.setShortName("cm:reg1"); + constraintReg1.setRegistry(constraintRegistry); + constraintReg1.initialize(); + AbstractConstraint constraintReg2 = new UserNameConstraint(); + constraintReg2.setShortName("cm:reg2"); + constraintReg2.setRegistry(constraintRegistry); + constraintReg2.initialize(); + // register resource bundles for messages I18NUtil.registerResourceBundle(TEST_RESOURCE_MESSAGES); @@ -185,6 +200,10 @@ public class DictionaryDAOTest extends TestCase public void testConstraints() { + // Check that the registered constraints are correct + assertNotNull("Constraint reg1 not registered", ConstraintRegistry.getInstance().getConstraint("cm:reg1")); + assertNotNull("Constraint reg2 not registered", ConstraintRegistry.getInstance().getConstraint("cm:reg2")); + // get the constraints for a property without constraints QName propNoConstraintsQName = QName.createQName(TEST_URL, "fileprop"); PropertyDefinition propNoConstraintsDef = service.getProperty(propNoConstraintsQName); @@ -195,7 +214,10 @@ public class DictionaryDAOTest extends TestCase PropertyDefinition propDef = service.getProperty(prop1QName); List constraints = propDef.getConstraints(); assertNotNull("Null constraints list", constraints); - assertEquals("Incorrect number of constraints", 2, constraints.size()); + assertEquals("Incorrect number of constraints", 3, constraints.size()); + assertTrue("Constraint instance incorrect", constraints.get(0).getConstraint() instanceof RegexConstraint); + assertTrue("Constraint instance incorrect", constraints.get(1).getConstraint() instanceof StringLengthConstraint); + assertTrue("Constraint instance incorrect", constraints.get(2).getConstraint() instanceof RegisteredConstraint); // check the individual constraints ConstraintDefinition constraintDef = constraints.get(0); @@ -203,9 +225,6 @@ public class DictionaryDAOTest extends TestCase // check that the constraint implementation is valid (it used a reference) Constraint constraint = constraintDef.getConstraint(); assertNotNull("Reference constraint has no implementation", constraint); - - // make sure it is the correct type of constraint - assertTrue("Expected type REGEX constraint", constraint instanceof RegexConstraint); } public void testConstraintsOverrideInheritance() @@ -219,25 +238,28 @@ public class DictionaryDAOTest extends TestCase PropertyDefinition prop1Def = service.getProperty(baseQName, prop1QName); assertNotNull(prop1Def); List prop1Constraints = prop1Def.getConstraints(); - assertEquals("Incorrect number of constraints", 2, prop1Constraints.size()); + assertEquals("Incorrect number of constraints", 3, prop1Constraints.size()); assertTrue("Constraint instance incorrect", prop1Constraints.get(0).getConstraint() instanceof RegexConstraint); assertTrue("Constraint instance incorrect", prop1Constraints.get(1).getConstraint() instanceof StringLengthConstraint); + assertTrue("Constraint instance incorrect", prop1Constraints.get(2).getConstraint() instanceof RegisteredConstraint); // check the inherited property on folder (must be same as above) prop1Def = service.getProperty(folderQName, prop1QName); assertNotNull(prop1Def); prop1Constraints = prop1Def.getConstraints(); - assertEquals("Incorrect number of constraints", 2, prop1Constraints.size()); + assertEquals("Incorrect number of constraints", 3, prop1Constraints.size()); assertTrue("Constraint instance incorrect", prop1Constraints.get(0).getConstraint() instanceof RegexConstraint); assertTrue("Constraint instance incorrect", prop1Constraints.get(1).getConstraint() instanceof StringLengthConstraint); + assertTrue("Constraint instance incorrect", prop1Constraints.get(2).getConstraint() instanceof RegisteredConstraint); // check the overridden property on file (must be reverse of above) prop1Def = service.getProperty(fileQName, prop1QName); assertNotNull(prop1Def); prop1Constraints = prop1Def.getConstraints(); - assertEquals("Incorrect number of constraints", 2, prop1Constraints.size()); + assertEquals("Incorrect number of constraints", 3, prop1Constraints.size()); assertTrue("Constraint instance incorrect", prop1Constraints.get(0).getConstraint() instanceof StringLengthConstraint); assertTrue("Constraint instance incorrect", prop1Constraints.get(1).getConstraint() instanceof RegexConstraint); + assertTrue("Constraint instance incorrect", prop1Constraints.get(2).getConstraint() instanceof RegisteredConstraint); } public void testArchive() diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java index 176cc3f46e..9b0f2f1a57 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -29,6 +29,7 @@ import java.util.List; import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint; import org.alfresco.repo.dictionary.constraint.NumericRangeConstraint; import org.alfresco.repo.dictionary.constraint.RegexConstraint; +import org.alfresco.repo.dictionary.constraint.RegisteredConstraint; import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; @@ -48,36 +49,26 @@ import org.springframework.beans.PropertyAccessException; */ /* package */class M2ConstraintDefinition implements ConstraintDefinition { + private static final String PROP_SHORT_NAME = "shortName"; + public static final String ERR_CYCLIC_REF = "d_dictionary.constraint.err.cyclic_ref"; - public static final String ERR_TYPE_AND_REF = "d_dictionary.constraint.err.type_and_ref"; - public static final String ERR_TYPE_OR_REF = "d_dictionary.constraint.err.type_or_ref"; - public static final String ERR_REF_NOT_FOUND = "d_dictionary.constraint.err.ref_not_found"; - public static final String ERR_ANON_NEEDS_PROPERTY = "d_dictionary.constraint.err.anon_needs_property"; - public static final String ERR_INVALID_TYPE = "d_dictionary.constraint.err.invalid_type"; - public static final String ERR_SIMPLE_AND_LIST = "d_dictionary.constraint.err.property_simple_and_list"; - public static final String ERR_CONSTRUCT_FAILURE = "d_dictionary.constraint.err.construct_failure"; - public static final String ERR_PROPERTY_MISMATCH = "d_dictionary.constraint.err.property_mismatch"; + public static final String ERR_RESERVED_PROPERTY = "d_dictionary.constraint.err.reserved_property"; private static int anonPropCount = 0; private ModelDefinition model; - private NamespacePrefixResolver prefixResolver; - private M2Constraint m2Constraint; - private QName name; - private Constraint constraint; - private boolean resolving; /* package */M2ConstraintDefinition(M2PropertyDefinition m2PropertyDef, M2Constraint m2Constraint, @@ -176,6 +167,7 @@ import org.springframework.beans.PropertyAccessException; // try to establish it as a class try { + @SuppressWarnings("unchecked") Class clazz = Class.forName(type); constraint = (Constraint) clazz.newInstance(); } @@ -200,6 +192,13 @@ import org.springframework.beans.PropertyAccessException; { for (M2NamedValue namedValue : constraintNamedValues) { + String namedValueName = namedValue.getName(); + // Check for reserved properties + if (namedValueName.equals(PROP_SHORT_NAME)) + { + throw new DictionaryException(ERR_RESERVED_PROPERTY, PROP_SHORT_NAME, namedValueName); + } + Object value = null; if (namedValue.getSimpleValue() != null && namedValue.getListValue() != null) { @@ -215,24 +214,24 @@ import org.springframework.beans.PropertyAccessException; } try { - beanWrapper.setPropertyValue(namedValue.getName(), value); + beanWrapper.setPropertyValue(namedValueName, value); } catch (PropertyAccessException e) { - throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValue.getName(), shortName); + throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValueName, shortName); } catch (InvalidPropertyException e) { - throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValue.getName(), shortName); + throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValueName, shortName); } } - // Pass in the name as a special property, if it is available - if (beanWrapper.isWritableProperty("_shortName")) + // Pass in the short name as a special property, if it is available + if (beanWrapper.isWritableProperty(PROP_SHORT_NAME)) { try { - beanWrapper.setPropertyValue("_shortName", shortName); + beanWrapper.setPropertyValue(PROP_SHORT_NAME, shortName); } catch (PropertyAccessException e) { @@ -279,6 +278,14 @@ import org.springframework.beans.PropertyAccessException; */ public static enum ConstraintType { + REGISTERED + { + @Override + protected Constraint newInstance() + { + return new RegisteredConstraint(); + } + }, REGEX { @Override diff --git a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java index 88718f5db7..abf1acc71b 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java @@ -25,6 +25,8 @@ package org.alfresco.repo.dictionary.constraint; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintException; @@ -44,20 +46,31 @@ public abstract class AbstractConstraint implements Constraint /** The constraint name. May be useful in error messages */ private String shortName; - + private ConstraintRegistry registry; + /** * Sets the constraint name. Automatically called after construction. Please excuse the strange method name as we * want the property name to begin with an underscore to avoid property name clashes. * * @param shortName + * @deprecated */ public void set_shortName(String shortName) { - this.shortName = shortName; + setShortName(shortName); } - + /** - * Gets the constraint name. May be useful in error messages. + * Sets the constraint name + * @param name + */ + public void setShortName(String name) + { + this.shortName = name; + } + + /** + * Gets the constraint name. * * @return the constraint name. */ @@ -66,14 +79,41 @@ public abstract class AbstractConstraint implements Constraint return this.shortName; } - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getType() + /** + * Optionally specify the registry that will be used to register the constraint. + * This is used when instantiating constraints outside the dictionary. + * + * @param registry the constraint registry */ + public void setRegistry(ConstraintRegistry registry) + { + this.registry = registry; + } + public String getType() { return this.getClass().getName(); } - + + public Map getParameters() + { + return new HashMap(3); + } + + /** + * {@inheritDoc} + *

+ * Registers the constraint with the registry, if present. Call this method if + * you want the constraint to be auto-registered. + */ + public void initialize() + { + if (registry != null) + { + registry.register(shortName, this); + } + } + /** * Check that the given value is not null. * @@ -86,7 +126,7 @@ public abstract class AbstractConstraint implements Constraint { if (value == null) { - throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, value); + throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, name, getShortName()); } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/AuthorityNameConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/AuthorityNameConstraint.java index ec71aea417..dff539dc9b 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/AuthorityNameConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/AuthorityNameConstraint.java @@ -24,8 +24,6 @@ */ package org.alfresco.repo.dictionary.constraint; -import java.util.Map; - import org.alfresco.service.cmr.dictionary.ConstraintException; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; @@ -33,7 +31,6 @@ import org.alfresco.service.cmr.security.AuthorityType; public class AuthorityNameConstraint extends AbstractConstraint { - private static final String ERR_INVALID_AUTHORITY_NAME = "d_dictionary.constraint.authority_name.invalid_authority_name"; private static final String ERR_NON_STRING = "d_dictionary.constraint.authority_name.non_string"; @@ -57,20 +54,4 @@ public class AuthorityNameConstraint extends AbstractConstraint throw new ConstraintException(ERR_INVALID_AUTHORITY_NAME, value, type); } } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#initialize() - */ - public void initialize() - { - - } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getParameters() - */ - public Map getParameters() - { - return null; - } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintRegistry.java b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintRegistry.java new file mode 100644 index 0000000000..e9cd9146f4 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintRegistry.java @@ -0,0 +1,93 @@ +/* + * 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.dictionary.constraint; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.dictionary.Constraint; + +/** + * A registry of constraints. + * + * @author Derek Hulley + * @since 3.2 + */ +public class ConstraintRegistry +{ + private static ConstraintRegistry instance = new ConstraintRegistry(); + + private Map constraints; + + /** + * @return Returns the singleton + */ + public static ConstraintRegistry getInstance() + { + return instance; + } + + /** + * Private constructor + * @see #getInstance() + */ + private ConstraintRegistry() + { + constraints = new HashMap(13); + } + + /** + * Register the constraint by name + */ + public void register(String name, Constraint constraint) + { + if (this == instance) + { + constraints.put(name, constraint); + } + else + { + instance.register(name, constraint); + } + } + + /** + * Get the constraint by name + * + * @param name the name by which the constraint was registered + * @return Returns the constraint or null if it does not exist. + */ + public Constraint getConstraint(String name) + { + if (this == instance) + { + return constraints.get(name); + } + else + { + return instance.getConstraint(name); + } + } +} diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java index 13c4449170..5bd6ab86cf 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java @@ -319,9 +319,7 @@ public class ConstraintsTest extends TestCase { private List tested; - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#initialize() - */ + @Override public void initialize() { tested = new ArrayList(4); @@ -348,13 +346,5 @@ public class ConstraintsTest extends TestCase throw new ConstraintException("Non-String value"); } } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getParameters() - */ - public Map getParameters() - { - return null; - } } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java index 34bb67cd98..0e38616c2a 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java @@ -59,8 +59,8 @@ public class ListOfValuesConstraint extends AbstractConstraint caseSensitive = true; } - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getType() + /** + * {@inheritDoc} */ @Override public String getType() @@ -135,10 +135,23 @@ public class ListOfValuesConstraint extends AbstractConstraint this.caseSensitive = caseSensitive; } + @Override public void initialize() { + super.initialize(); checkPropertyNotNull("allowedValues", allowedValues); } + + @Override + public Map getParameters() + { + Map params = new HashMap(2); + + params.put("caseSensitive", this.caseSensitive); + params.put("allowedValues", this.allowedValues); + + return params; + } protected void evaluateSingleValue(Object value) { @@ -168,17 +181,4 @@ public class ListOfValuesConstraint extends AbstractConstraint } } } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getParameters() - */ - public Map getParameters() - { - Map params = new HashMap(2); - - params.put("caseSensitive", this.caseSensitive); - params.put("allowedValues", this.allowedValues); - - return params; - } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java index 168f418a03..9bb45e4b08 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java @@ -53,9 +53,6 @@ public class NumericRangeConstraint extends AbstractConstraint private double minValue = Double.MIN_VALUE; private double maxValue = Double.MAX_VALUE; - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getType() - */ @Override public String getType() { @@ -118,17 +115,18 @@ public class NumericRangeConstraint extends AbstractConstraint } this.maxValue = maxValue; } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#initialize() - */ - public void initialize() - { - } - /* - * @see org.alfresco.repo.dictionary.constraint.AbstractConstraint#evaluateSingleValue(java.lang.Object) - */ + @Override + public Map getParameters() + { + Map params = new HashMap(2); + + params.put("minValue", this.minValue); + params.put("maxValue", this.maxValue); + + return params; + } + protected void evaluateSingleValue(Object value) { // ensure that the value can be converted to a double @@ -154,17 +152,4 @@ public class NumericRangeConstraint extends AbstractConstraint throw new ConstraintException(ERR_OUT_OF_RANGE, checkValue, minValue, maxValue); } } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getParameters() - */ - public Map getParameters() - { - Map params = new HashMap(2); - - params.put("minValue", this.minValue); - params.put("maxValue", this.maxValue); - - return params; - } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java index 11299e1e01..3a0ad4a2f3 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java @@ -57,9 +57,9 @@ public class RegexConstraint extends AbstractConstraint private String expression; private Pattern patternMatcher; private boolean requiresMatch = true; - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getType() + + /** + * {@inheritDoc} */ public String getType() { @@ -113,7 +113,19 @@ public class RegexConstraint extends AbstractConstraint { this.requiresMatch = requiresMatch; } + + @Override + public Map getParameters() + { + Map params = new HashMap(2); + + params.put("expression", this.expression); + params.put("requiresMatch", this.requiresMatch); + + return params; + } + @Override public void initialize() { checkPropertyNotNull("expression", expression); @@ -146,14 +158,4 @@ public class RegexConstraint extends AbstractConstraint } } } - - public Map getParameters() - { - Map params = new HashMap(2); - - params.put("expression", this.expression); - params.put("requiresMatch", this.requiresMatch); - - return params; - } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/RegisteredConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/RegisteredConstraint.java new file mode 100644 index 0000000000..adfb2e59b3 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/RegisteredConstraint.java @@ -0,0 +1,127 @@ +/* + * 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.dictionary.constraint; + +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.DictionaryException; + +/** + * Constraint implementation that defers to constraints registered with the + * static instance of the {@link ConstraintRegistry}. + * + * @see #setAllowedValues(List) + * @see #setCaseSensitive(boolean) + * + * @author Derek Hulley + */ +public final class RegisteredConstraint implements Constraint +{ + private static final String ERR_NAME_NOT_REGISTERED = "d_dictionary.constraint.registered.not_registered"; + + private String shortName; + private String registeredName; + + public RegisteredConstraint() + { + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(80); + sb.append("RegisteredConstraint") + .append("[ registeredName=").append(registeredName) + .append(", constraint=").append(ConstraintRegistry.getInstance().getConstraint(registeredName)) + .append("]"); + return sb.toString(); + } + + public String getShortName() + { + return shortName; + } + + public void setShortName(String shortName) + { + this.shortName = shortName; + } + + /** + * Set the name of the constraint that will be used to look up the constraint + * that will be delegated to. + */ + public void setRegisteredName(String registeredName) + { + this.registeredName = registeredName; + } + + public void initialize() + { + if (registeredName == null) + { + throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, "registeredName"); + } + } + + /** + * @return the constraint that matches the registered name + */ + private Constraint getConstraint() + { + Constraint constraint = ConstraintRegistry.getInstance().getConstraint(registeredName); + if (constraint == null) + { + throw new DictionaryException(ERR_NAME_NOT_REGISTERED, registeredName); + } + return constraint; + } + + /** + * Defers to the registered constraint + */ + public String getType() + { + return getConstraint().getType(); + } + + /** + * Defers to the registered constraint + */ + public Map getParameters() + { + return getConstraint().getParameters(); + } + + /** + * Defers to the registered constraint + */ + public void evaluate(Object value) + { + getConstraint().evaluate(value); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java index 06a1fb5904..b90c341b40 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java @@ -50,8 +50,8 @@ public class StringLengthConstraint extends AbstractConstraint private int minLength = 0; private int maxLength = Integer.MAX_VALUE; - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getType() + /** + * {@inheritDoc} */ @Override public String getType() @@ -116,8 +116,15 @@ public class StringLengthConstraint extends AbstractConstraint this.maxLength = maxLength; } - public void initialize() + @Override + public Map getParameters() { + Map params = new HashMap(2); + + params.put("minLength", this.minLength); + params.put("maxLength", this.maxLength); + + return params; } protected void evaluateSingleValue(Object value) @@ -144,17 +151,4 @@ public class StringLengthConstraint extends AbstractConstraint throw new ConstraintException(ERR_INVALID_LENGTH, checkValue, minLength, maxLength); } } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getParameters() - */ - public Map getParameters() - { - Map params = new HashMap(2); - - params.put("minLength", this.minLength); - params.put("maxLength", this.maxLength); - - return params; - } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/UserNameConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/UserNameConstraint.java index 33ccb6d612..e017aecc32 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/UserNameConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/UserNameConstraint.java @@ -24,8 +24,6 @@ */ package org.alfresco.repo.dictionary.constraint; -import java.util.Map; - import org.alfresco.service.cmr.dictionary.ConstraintException; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; @@ -62,20 +60,4 @@ public class UserNameConstraint extends AbstractConstraint throw new ConstraintException(ERR_INVALID_USERNAME, value, type); } } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#initialize() - */ - public void initialize() - { - - } - - /* - * @see org.alfresco.service.cmr.dictionary.Constraint#getParameters() - */ - public Map getParameters() - { - return null; - } } diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index cc8e44c0c7..1da3ea1c5c 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -23,6 +23,12 @@ + + cm:reg1 + + + cm:reg2 + [A-Z]* false @@ -69,6 +75,7 @@ + @@ -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();