diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index cc93252202..5253fb490c 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -459,6 +459,14 @@
+
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/repo/props/PropertyValueComponent.java b/source/java/org/alfresco/repo/props/PropertyValueComponent.java
new file mode 100644
index 0000000000..8884011f2e
--- /dev/null
+++ b/source/java/org/alfresco/repo/props/PropertyValueComponent.java
@@ -0,0 +1,151 @@
+/*
+ * 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.props;
+
+import java.io.Serializable;
+
+import org.springframework.dao.DataIntegrityViolationException;
+
+/**
+ * A component that gives high-level access to manipulate standalone properties.
+ *
+ * There are two types of properties: shared and unshared.
+ *
+ * Simple, Shared Properties:
+ * These properties are globally unique (apart from values that don't have a meaningful key).
+ * If two different applications attempt to create the same value, then the same ID will
+ * be returned (after conflict resolution), assuming that the value is not treated as binary
+ * data. It is not possible to modify or delete these values. You should store types that
+ * can be converted to and from a well-known type. Complex collections should not be stored
+ * using this value.
+ * Unshared Properties:
+ * These properties may be duplicated, modifed and deleted. It is not possible to look
+ * values up and therefore new IDs are generated for each creation. Complex values can
+ * be stored in these properties and will be exploded recursively.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public interface PropertyValueComponent
+{
+ /**
+ * @param id the ID (may not be null)
+ * @throws DataIntegrityViolationException if the ID is invalid
+ */
+ Serializable getSharedValueById(Long id);
+ /**
+ * @param value the value to find the ID for (may be null)
+ * @return Returns the ID of the shared value or null if it doesn't exist
+ */
+ Long getSharedValueId(Serializable value);
+ /**
+ * @param value the value to find the ID for (may be null)
+ * @return Returns the ID of the shared value (created or not)
+ */
+ Long getOrCreateSharedValue(Serializable value);
+
+ /**
+ * @param id the ID (may not be null)
+ * @return Returns the value of the property (never null)
+ * @throws DataIntegrityViolationException if the ID is invalid
+ */
+ Serializable getUnsharedPropertyById(Long id);
+ /**
+ * @param value the value to create (may be null)
+ * @return Returns the new property's ID
+ */
+ Long createUnsharedProperty(Serializable value);
+ /**
+ * @param id the ID of the root property to change (may not be null)
+ * @param value the new property value
+ * @throws DataIntegrityViolationException if the ID is invalid
+ */
+ void updateUnsharedProperty(Long id, Serializable value);
+ /**
+ * @param id the ID of the root property to delete (may be null)
+ * @throws DataIntegrityViolationException if the ID is invalid
+ */
+ void deleteUnsharedProperty(Long id);
+
+ /**
+ * Create a new combination of three unique properties.
+ *
+ * @param value1 the first property, which should denote application name or use-case ID
+ * (null allowed)
+ * @param value2 the second property, which should denote the context or container value
+ * (null allowed)
+ * @param value3 the third property, which should denote the unique value within the context
+ * of the previous two properties (null allowed)
+ * @return Returns the ID of the unique property combination for later updates
+ * @throws PropertyUniqueConstraintViolation if the combination is not unique
+ */
+ Long createPropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3);
+ /**
+ * Get the ID of a unique property context.
+ *
+ * @see #createPropertyUniqueContext(Serializable, Serializable, Serializable)
+ */
+ Long getPropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3);
+ /**
+ * Update a unique property context.
+ *
+ * @see #createPropertyUniqueContext(Serializable, Serializable, Serializable)
+ */
+ void updatePropertyUniqueContext(Long id, Serializable value1, Serializable value2, Serializable value3);
+ /**
+ * Update a combination of three unique properties. If the before values exist, then they
+ * are updated to the new values. If the before values don't exist, then the new values
+ * are created assuming no pre-existence -
+ * using {@link #createPropertyUniqueContext(Serializable, Serializable, Serializable) create} is better
+ * if there is no pre-existing set of values.
+ *
+ * @param value1Before the first property before (null allowed)
+ * @param value2Before the second property before (null allowed)
+ * @param value3Before the third property before (null allowed)
+ * @param value1 the first property (null allowed)
+ * @param value2 the second property (null allowed)
+ * @param value3 the third property (null allowed)
+ * @return Returns the ID of the unique property combination for later updates
+ * @throws PropertyUniqueConstraintViolation if the new combination is not unique
+ */
+ Long updatePropertyUniqueContext(
+ Serializable value1Before, Serializable value2Before, Serializable value3Before,
+ Serializable value1, Serializable value2, Serializable value3);
+ /**
+ * Delete a unique property context.
+ *
+ * @see #createPropertyUniqueContext(Serializable, Serializable, Serializable)
+ */
+ void deletePropertyUniqueContext(Long id);
+ /**
+ * Delete a combination of three unique properties. It doesn't matter if the unique combination
+ * already existed or not.
+ *
+ * @param value1 the first property (null allowed)
+ * @param value2 the second property (null allowed)
+ * @param value3 the third property (null allowed)
+ */
+ void deletePropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3);
+}
diff --git a/source/java/org/alfresco/repo/props/PropertyValueComponentImpl.java b/source/java/org/alfresco/repo/props/PropertyValueComponentImpl.java
new file mode 100644
index 0000000000..7edfda9c93
--- /dev/null
+++ b/source/java/org/alfresco/repo/props/PropertyValueComponentImpl.java
@@ -0,0 +1,270 @@
+/*
+ * 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.props;
+
+import java.io.Serializable;
+
+import org.alfresco.repo.domain.propval.PropertyValueDAO;
+import org.alfresco.util.Pair;
+import org.alfresco.util.PropertyCheck;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This component provides a clearer distinction between shared and unshared properties and
+ * avoids the need to access the DAO, which is much more obscure and potentially harmful.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class PropertyValueComponentImpl implements PropertyValueComponent
+{
+ private static final Log logger = LogFactory.getLog(PropertyValueComponentImpl.class);
+
+ private PropertyValueDAO propertyValueDAO;
+
+ /**
+ * Set the underlying DAO that manipulates the database data
+ */
+ public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO)
+ {
+ this.propertyValueDAO = propertyValueDAO;
+ }
+
+ /**
+ * Ensures that all necessary properties have been set
+ */
+ public void init()
+ {
+ PropertyCheck.mandatory(this, "propertyValueDAO", propertyValueDAO);
+ }
+
+ /**
+ * @return If the pair value is not null, returns the first value (ID)
+ */
+ private Long getEntityPairId(Pair pair)
+ {
+ if (pair == null)
+ {
+ return null;
+ }
+ else
+ {
+ return pair.getFirst();
+ }
+ }
+
+ /**
+ * @return If the pair value is not null, returns the second value (value)
+ */
+ private Serializable getEntityPairValue(Pair pair)
+ {
+ if (pair == null)
+ {
+ return null;
+ }
+ else
+ {
+ return pair.getSecond();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Serializable getSharedValueById(Long id)
+ {
+ Pair pair = propertyValueDAO.getPropertyValueById(id);
+ return getEntityPairValue(pair);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public Long getSharedValueId(Serializable value)
+ {
+ Pair pair = propertyValueDAO.getPropertyValue(value);
+ return getEntityPairId(pair);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public Long getOrCreateSharedValue(Serializable value)
+ {
+ Pair pair = propertyValueDAO.getOrCreatePropertyValue(value);
+ return getEntityPairId(pair);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Serializable getUnsharedPropertyById(Long id)
+ {
+ return propertyValueDAO.getPropertyById(id);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public Long createUnsharedProperty(Serializable value)
+ {
+ return propertyValueDAO.createProperty(value);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void updateUnsharedProperty(Long id, Serializable value)
+ {
+ propertyValueDAO.updateProperty(id, value);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void deleteUnsharedProperty(Long id)
+ {
+ propertyValueDAO.deleteProperty(id);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Long createPropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3)
+ {
+ Long id = propertyValueDAO.createPropertyUniqueContext(value1, value2, value3);
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Created unique property context: \n" +
+ " ID: " + id + "\n" +
+ " Value1: " + value1 + "\n" +
+ " Value2: " + value2 + "\n" +
+ " Value3: " + value3);
+ }
+ return id;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public Long getPropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3)
+ {
+ return propertyValueDAO.getPropertyUniqueContext(value1, value2, value3);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void updatePropertyUniqueContext(Long id, Serializable value1, Serializable value2, Serializable value3)
+ {
+ propertyValueDAO.updatePropertyUniqueContext(id, value1, value2, value3);
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Updated unique property context: \n" +
+ " ID: " + id + "\n" +
+ " Value1: " + value1 + "\n" +
+ " Value2: " + value2 + "\n" +
+ " Value3: " + value3);
+ }
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public Long updatePropertyUniqueContext(
+ Serializable value1Before, Serializable value2Before, Serializable value3Before,
+ Serializable value1, Serializable value2, Serializable value3)
+ {
+ Long id = getPropertyUniqueContext(value1Before, value2Before, value3Before);
+ if (id == null)
+ {
+ // It didn't exist before, so just create it
+ id = createPropertyUniqueContext(value1, value2, value3);
+ }
+ else
+ {
+ // Update itUpdated
+ updatePropertyUniqueContext(id, value1, value2, value3);
+ }
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Updated unique property context: \n" +
+ " Value1 Before: " + value1Before + "\n" +
+ " Value2 Before: " + value2Before + "\n" +
+ " Value3 Before: " + value3Before + "\n" +
+ " Value1: " + value1 + "\n" +
+ " Value2: " + value2 + "\n" +
+ " Value3: " + value3);
+ }
+ return id;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void deletePropertyUniqueContext(Long id)
+ {
+ propertyValueDAO.deletePropertyUniqueContext(id);
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Deleted unique property context: \n" +
+ " ID: " + id);
+ }
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void deletePropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3)
+ {
+ // Get the ID of the values
+ Long id = getPropertyUniqueContext(value1, value2, value3);
+ if (id == null)
+ {
+ // No need to delete
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "No unique property context exists: \n" +
+ " Value1: " + value1 + "\n" +
+ " Value2: " + value2 + "\n" +
+ " Value3: " + value3);
+ }
+ }
+ else
+ {
+ deletePropertyUniqueContext(id);
+ // Deleted
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Deleted unique property context: \n" +
+ " Value1: " + value1 + "\n" +
+ " Value2: " + value2 + "\n" +
+ " Value3: " + value3);
+ }
+ }
+ // Done
+ }
+}
diff --git a/source/java/org/alfresco/repo/props/PropertyValueComponentTest.java b/source/java/org/alfresco/repo/props/PropertyValueComponentTest.java
new file mode 100644
index 0000000000..ed74887369
--- /dev/null
+++ b/source/java/org/alfresco/repo/props/PropertyValueComponentTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.props;
+
+import junit.framework.TestCase;
+
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * @see PropertyValueComponent
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class PropertyValueComponentTest extends TestCase
+{
+ private static final Log logger = LogFactory.getLog(PropertyValueComponentTest.class);
+
+ private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private PropertyValueComponent propertyValueComponent;
+
+ @Override
+ public void setUp()
+ {
+ transactionService = (TransactionService) ctx.getBean("transactionService");
+ propertyValueComponent = (PropertyValueComponent) ctx.getBean("propertyValueComponent");
+ }
+
+ public void testSetUp()
+ {
+ }
+
+ public void testUniqueContext_UpdateFromNothing()
+ {
+ // The non-existent source properties
+ final String value1 = GUID.generate();
+ final String value2 = GUID.generate();
+ final String value3 = GUID.generate();
+ // The target properties
+ final String context = getName();
+ final StoreRef store = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
+ final String uuid1 = GUID.generate();
+ final String uuid2 = GUID.generate();
+
+ RetryingTransactionCallback updateCallback = new RetryingTransactionCallback()
+ {
+ public Long execute() throws Throwable
+ {
+ return propertyValueComponent.updatePropertyUniqueContext(
+ value1, value2, value3,
+ context, store, uuid1);
+ }
+ };
+ final Long id1 = transactionService.getRetryingTransactionHelper().doInTransaction(updateCallback);
+ // Now attempt to create the context again and check for failure
+ RetryingTransactionCallback updateAgainCallback = new RetryingTransactionCallback()
+ {
+ public Long execute() throws Throwable
+ {
+ return propertyValueComponent.updatePropertyUniqueContext(
+ context, store, uuid1,
+ context, store, uuid2);
+ }
+ };
+ final Long id2 = transactionService.getRetryingTransactionHelper().doInTransaction(updateAgainCallback);
+ assertEquals("The ID should be reused for the update", id1, id2);
+ // Now create the uuid1 again (should work)
+ final Long id3 = transactionService.getRetryingTransactionHelper().doInTransaction(updateCallback);
+ assertNotSame("Should have created a new instance", id1, id3);
+
+ // Now test failure
+ RetryingTransactionCallback updateFailureCallback = new RetryingTransactionCallback()
+ {
+ public Long execute() throws Throwable
+ {
+ return propertyValueComponent.updatePropertyUniqueContext(
+ context, store, uuid1,
+ context, store, uuid2);
+ }
+ };
+ try
+ {
+ transactionService.getRetryingTransactionHelper().doInTransaction(updateFailureCallback);
+ fail("Expected to get a PropertyUniqueConstraintViolation");
+ }
+ catch (PropertyUniqueConstraintViolation e)
+ {
+ // Expected
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Expected exception: " + e.getMessage());
+ }
+ }
+ }
+}