diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 08fa7cefb7..3d82e6c580 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -213,6 +213,7 @@ + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 257871cc8f..99b552abd7 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -1074,6 +1074,10 @@ attributes.propcleaner.cronExpression=* * * * * ? 2099 # Control Alfresco JMX connectivity alfresco.jmx.connector.enabled=true +# Dissallow Attribute Service Entries with "Serializable" objects in key Segments +# Please, see MNT-11895 for details. +system.propval.uniquenessCheck.enabled=true + # SurfConfigFolder Patch # # Do we defer running the surf-config folder patch? diff --git a/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java b/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java index 6f78dbd95e..b1ff8a3899 100644 --- a/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. + * Copyright (C) 2005-2014 Alfresco Software Limited. * * This file is part of Alfresco * @@ -129,6 +129,19 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO private SimpleCache propertyUniqueContextCache; // cluster-aware + /** + * Flag to throw exception if type of the key doesn't guarantee uniqueness, @see MNT-11895 + */ + private boolean uniquenessCheckEnabled = true; + + /** + * Setter for uniquenessCheckEnabled flag + */ + public void setUniquenessCheckEnabled(boolean uniquenessCheckEnabled) + { + this.uniquenessCheckEnabled = uniquenessCheckEnabled; + } + /** * Set the cache to use for unique property lookups */ @@ -1162,13 +1175,37 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO } } + private void checkUniquenessGuarantee(Serializable... values) + { + for (int i = 0; i < values.length; i++) + { + PersistedType persistedType = PropertyValueEntity.getPersistedTypeEnum(values[i], converter); + if (persistedType == PersistedType.SERIALIZABLE) + { + if (uniquenessCheckEnabled) + { + throw new IllegalArgumentException("Type of the KEY-" + i + " (" + values[i].getClass() + ") cannot guarantee uniqueness. " + + "Please, see https://issues.alfresco.com/jira/browse/MNT-11895 for details. " + + "Set system.propval.uniquenessCheck.enabled=false to not throw the exception."); + } + else + { + logger.warn("Type of the KEY-" + i + " (" + values[i].getClass() + ") cannot guarantee uniqueness. " + + "Please, see https://issues.alfresco.com/jira/browse/MNT-11895 for details. " + + "Set system.propval.uniquenessCheck.enabled=true to throw the exception."); + } + } + } + } + public Pair createPropertyUniqueContext( Serializable value1, Serializable value2, Serializable value3, Serializable propertyValue1) { /* - * Use savepoints so that the PropertyUniqueConstraintViolation can be caught and handled in-transactioin + * Use savepoints so that the PropertyUniqueConstraintViolation can be caught and handled in-transaction */ + checkUniquenessGuarantee(value1, value2, value3); // Translate the properties. Null values are acceptable Long id1 = getOrCreatePropertyValue(value1).getFirst(); diff --git a/source/test-java/org/alfresco/repo/attributes/AttributeServiceTest.java b/source/test-java/org/alfresco/repo/attributes/AttributeServiceTest.java index a44005e88b..2c3e6f2965 100644 --- a/source/test-java/org/alfresco/repo/attributes/AttributeServiceTest.java +++ b/source/test-java/org/alfresco/repo/attributes/AttributeServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2014 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,15 +21,21 @@ package org.alfresco.repo.attributes; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import junit.framework.TestCase; +import org.alfresco.repo.domain.propval.DefaultPropertyTypeConverter; import org.alfresco.repo.domain.propval.PropValGenerator; +import org.alfresco.repo.domain.propval.PropertyTypeConverter; import org.alfresco.repo.domain.propval.PropertyValueDAO; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.domain.propval.PropertyValueEntity; +import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback; +import org.alfresco.service.cmr.attributes.DuplicateAttributeException; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.Pair; import org.apache.commons.lang.mutable.MutableInt; @@ -192,4 +198,103 @@ public class AttributeServiceTest extends TestCase fail(msg); } } + + public void testKeySegmentsGuaranteeUniqueness() + { + final PropertyTypeConverter converter = new DefaultPropertyTypeConverter(); + + final int VAL1 = 0; + final int VAL2 = 1; + final int KEY_INT_1 = 1; + final int KEY_INT_2 = 2; + final String KEY_STR_1 = "string"; + final String KEY_STR_2 = "string2"; + final TestIdentifier.TestEnum KEY_ENUM = TestIdentifier.TestEnum.ONE; + final HashMap KEY_MAP = new HashMap<>(); + final NodeRef KEY_NODEREF = new NodeRef("workspace://SpacesStore/5980bbdb-8a31-437e-95c8-d5092c3c58fc"); + final TestIdentifier KEY_SERIALIZABLE = new TestIdentifier(KEY_STR_2); + + try + { + // check integer keys + assertEquals(PropertyValueEntity.getPersistedTypeEnum(KEY_INT_2, converter), PersistedType.LONG); + attributeService.createAttribute(VAL1, KEY_INT_1, KEY_INT_2); + try + { + attributeService.createAttribute(VAL2, KEY_INT_1, KEY_INT_2); + fail("Duplicate attribute creation should not be allowed"); + } + catch (DuplicateAttributeException expected) + { + } + + // check noderef keys + assertEquals(PropertyValueEntity.getPersistedTypeEnum(KEY_NODEREF, converter), PersistedType.STRING); + attributeService.createAttribute(VAL1, KEY_INT_1, KEY_NODEREF); + try + { + attributeService.createAttribute(VAL2, KEY_INT_1, KEY_NODEREF); + fail("Duplicate attribute creation should not be allowed"); + } + catch (DuplicateAttributeException expected) + { + } + + // check enum keys + assertEquals(PropertyValueEntity.getPersistedTypeEnum(KEY_ENUM, converter), PersistedType.ENUM); + attributeService.createAttribute(VAL1, KEY_INT_1, KEY_ENUM); + try + { + attributeService.createAttribute(VAL2, KEY_INT_1, KEY_ENUM); + fail("Duplicate attribute creation should not be allowed"); + } + catch (DuplicateAttributeException expected) + { + } + + // check constructable keys + assertEquals(PropertyValueEntity.getPersistedTypeEnum(KEY_MAP, converter), PersistedType.CONSTRUCTABLE); + attributeService.createAttribute(VAL1, KEY_INT_1, KEY_MAP); + try + { + attributeService.createAttribute(VAL2, KEY_INT_1, KEY_MAP); + fail("Duplicate attribute creation should not be allowed"); + } + catch (DuplicateAttributeException expected) + { + } + + // check string keys + assertEquals(PropertyValueEntity.getPersistedTypeEnum(KEY_STR_2, converter), PersistedType.STRING); + attributeService.createAttribute(VAL1, KEY_STR_1, KEY_STR_2); + try + { + attributeService.createAttribute(VAL2, KEY_STR_1, KEY_STR_2); + fail("Duplicate attribute creation should not be allowed"); + } + catch (DuplicateAttributeException expected) + { + } + + // check custom type serializable key + assertEquals(PropertyValueEntity.getPersistedTypeEnum(KEY_SERIALIZABLE, converter), PersistedType.SERIALIZABLE); + try + { + attributeService.createAttribute(VAL1, KEY_STR_1, KEY_SERIALIZABLE); + fail("Keys of SERIALIZABLE persisted type are not allowed because it cannot guarantee uniqueness"); + } + catch (IllegalArgumentException expected) + { + } + } + finally + { + attributeService.removeAttribute(KEY_INT_1, KEY_INT_2); + attributeService.removeAttribute(KEY_INT_1, KEY_NODEREF); + attributeService.removeAttribute(KEY_INT_1, KEY_ENUM); + attributeService.removeAttribute(KEY_INT_1, KEY_MAP); + attributeService.removeAttribute(KEY_STR_1, KEY_STR_2); + attributeService.removeAttribute(KEY_STR_1, KEY_SERIALIZABLE); + } + } } diff --git a/source/test-java/org/alfresco/repo/attributes/TestIdentifier.java b/source/test-java/org/alfresco/repo/attributes/TestIdentifier.java new file mode 100644 index 0000000000..ce0213c03b --- /dev/null +++ b/source/test-java/org/alfresco/repo/attributes/TestIdentifier.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.attributes; + +import java.io.Serializable; + +public class TestIdentifier implements Serializable +{ + public enum TestEnum + { + ONE, TWO + } + + private static final long serialVersionUID = 1L; + private String id; + + public TestIdentifier(String id) + { + this.id = id; + } + + @Override + public boolean equals(Object o) + { + if (o == null) return false; + if (!(o instanceof TestIdentifier)) return false; + TestIdentifier other = (TestIdentifier) o; + if (!this.id.equals(other.id)) return false; + return true; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + (null == id ? 0 : id.hashCode()); + return hash; + } + +}