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()); + } + } + } +}