diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index e9a7ba5fff..00e106f8c6 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -64,6 +64,17 @@ + + + + + / + alfresco/bootstrap/spacesArchive.xml + + + + + diff --git a/config/alfresco/bootstrap/spacesArchive.xml b/config/alfresco/bootstrap/spacesArchive.xml new file mode 100644 index 0000000000..4622bae5fb --- /dev/null +++ b/config/alfresco/bootstrap/spacesArchive.xml @@ -0,0 +1,16 @@ + + + + + + + GROUP_EVERYONE + Read + + + + + \ No newline at end of file diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index bdfcce1566..5a9a8dfc36 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -186,6 +186,23 @@ + + + ${spaces.archive.store} + + + + ${spaces.archive.store} + + + + + ${spaces.store} diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index fe5ac5a120..8ae007bec4 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -44,6 +44,7 @@ Folder cm:cmobject + true d:boolean @@ -68,6 +69,7 @@ Content cm:cmobject + true d:content diff --git a/config/alfresco/model/dictionaryModel.xml b/config/alfresco/model/dictionaryModel.xml index 8b99b02613..95fa5e2b69 100644 --- a/config/alfresco/model/dictionaryModel.xml +++ b/config/alfresco/model/dictionaryModel.xml @@ -78,6 +78,16 @@ org.alfresco.service.cmr.repository.NodeRef + + org.apache.lucene.analysis.standard.StandardAnalyzer + org.alfresco.service.cmr.repository.ChildAssociationRef + + + + org.apache.lucene.analysis.standard.StandardAnalyzer + org.alfresco.service.cmr.repository.AssociationRef + + org.apache.lucene.analysis.standard.StandardAnalyzer org.alfresco.service.cmr.repository.Path diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index a8f2ba6067..f65db447b1 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -122,6 +122,53 @@ Incomplete + + + + Archived + + + d:noderef + true + + + d:text + true + + + d:datetime + true + + + + + + + d:childassocref + false + true + + + + d:childassocref + false + true + + + + d:assocref + false + true + + + + d:assocref + false + true + + + + diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index ca79b78a4f..96a8be786b 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -19,17 +19,29 @@ + + + + + archive://SpacesStore + + + + - - - - + - - + + - + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 499a5b91f6..e185d91381 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -80,6 +80,9 @@ alfresco_user_store.system_container.childname=sys:system alfresco_user_store.user_container.childname=sys:people alfresco_user_store.authorities_container.childname=sys:authorities +# Spaces Archive Configuration +spaces.archive.store=archive://SpacesStore + # Spaces Configuration spaces.store=workspace://SpacesStore diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 25f1e99b11..bdf9c44490 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -39,6 +39,17 @@ public interface ContentModel // tag for incomplete nodes static final QName ASPECT_INCOMPLETE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "incomplete"); + // archived nodes aspect constants + static final QName ASPECT_ARCHIVED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived"); + static final QName PROP_ARCHIVED_ORIGINAL_PARENT = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalParent"); + static final QName PROP_ARCHIVED_BY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedBy"); + static final QName PROP_ARCHIVED_DATE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedDate"); + static final QName ASPECT_ARCHIVED_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived-assocs"); + static final QName PROP_ARCHIVED_PARENT_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedParentAssocs"); + static final QName PROP_ARCHIVED_CHILD_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedChildAssocs"); + static final QName PROP_ARCHIVED_SOURCE_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedSourceAssocs"); + static final QName PROP_ARCHIVED_TARGET_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedTargetAssocs"); + // referenceable aspect constants static final QName TYPE_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); static final QName PROP_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); @@ -60,8 +71,6 @@ public interface ContentModel static final QName PROP_SYS_VERSION_SCHEMA = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionSchema"); static final QName PROP_SYS_VERSION_EDITION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionEdition"); - - // // Content Model Definitions // diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 3fc1f8de32..169c932c70 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -145,6 +145,17 @@ public class DictionaryDAOTest extends TestCase // make sure it is the correct type of constraint assertTrue("Expected type REGEX constraint", constraint instanceof RegexConstraint); } + + public void testArchive() + { + QName testFileQName = QName.createQName(TEST_URL, "file"); + ClassDefinition fileClassDef = service.getClass(testFileQName); + assertTrue("File type should have the archive flag", fileClassDef.isArchive()); + + QName testFolderQName = QName.createQName(TEST_URL, "folder"); + ClassDefinition folderClassDef = service.getClass(testFolderQName); + assertFalse("Folder type should not have the archive flag", folderClassDef.isArchive()); + } public void testMandatoryEnforced() { diff --git a/source/java/org/alfresco/repo/dictionary/M2AnonymousTypeDefinition.java b/source/java/org/alfresco/repo/dictionary/M2AnonymousTypeDefinition.java index ac43ef9e1c..39b1241817 100644 --- a/source/java/org/alfresco/repo/dictionary/M2AnonymousTypeDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2AnonymousTypeDefinition.java @@ -130,7 +130,11 @@ import org.alfresco.service.namespace.QName; return type.isAspect(); } - + public boolean isArchive() + { + return type.isArchive(); + } + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.ClassDefinition#getProperties() */ diff --git a/source/java/org/alfresco/repo/dictionary/M2Class.java b/source/java/org/alfresco/repo/dictionary/M2Class.java index 1701d6e70d..7cece04111 100644 --- a/source/java/org/alfresco/repo/dictionary/M2Class.java +++ b/source/java/org/alfresco/repo/dictionary/M2Class.java @@ -32,6 +32,7 @@ public abstract class M2Class private String title = null; private String description = null; private String parentName = null; + private boolean archive = false; private List properties = new ArrayList(); private List propertyOverrides = new ArrayList(); @@ -91,6 +92,16 @@ public abstract class M2Class } + public boolean isArchive() + { + return archive; + } + + public void setArchive(boolean archive) + { + this.archive = archive; + } + public M2Property createProperty(String name) { M2Property property = new M2Property(); diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index 2f8e407c13..8601167e70 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -46,6 +46,7 @@ import org.alfresco.service.namespace.QName; protected M2Class m2Class; protected QName name; protected QName parentName = null; + protected boolean archive = false; private Map propertyOverrides = new HashMap(); private Map properties = new HashMap(); @@ -73,6 +74,7 @@ import org.alfresco.service.namespace.QName; // Resolve Names this.name = QName.createQName(m2Class.getName(), resolver); + this.archive = m2Class.isArchive(); if (m2Class.getParentName() != null && m2Class.getParentName().length() > 0) { this.parentName = QName.createQName(m2Class.getParentName(), resolver); @@ -316,14 +318,6 @@ import org.alfresco.service.namespace.QName; } return value; } - - /* (non-Javadoc) - * @see org.alfresco.repo.dictionary.ClassDefinition#isAspect() - */ - public boolean isAspect() - { - return (m2Class instanceof M2Aspect); - } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.ClassDefinition#getParentName() @@ -333,6 +327,19 @@ import org.alfresco.service.namespace.QName; return parentName; } + /* (non-Javadoc) + * @see org.alfresco.repo.dictionary.ClassDefinition#isAspect() + */ + public boolean isAspect() + { + return (m2Class instanceof M2Aspect); + } + + public boolean isArchive() + { + return archive; + } + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.ClassDefinition#getProperties() */ diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index 1a1113310f..a9a74f0592 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -110,6 +110,7 @@ test:base + true diff --git a/source/java/org/alfresco/repo/dictionary/m2binding.xml b/source/java/org/alfresco/repo/dictionary/m2binding.xml index 90459e7b59..df04117d7e 100644 --- a/source/java/org/alfresco/repo/dictionary/m2binding.xml +++ b/source/java/org/alfresco/repo/dictionary/m2binding.xml @@ -60,6 +60,7 @@ + diff --git a/source/java/org/alfresco/repo/domain/PropertyValue.java b/source/java/org/alfresco/repo/domain/PropertyValue.java index f60d4f8a33..f4fad1fa2a 100644 --- a/source/java/org/alfresco/repo/domain/PropertyValue.java +++ b/source/java/org/alfresco/repo/domain/PropertyValue.java @@ -25,6 +25,8 @@ import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; @@ -183,6 +185,34 @@ public class PropertyValue implements Cloneable, Serializable return DefaultTypeConverter.INSTANCE.convert(NodeRef.class, value); } }, + CHILD_ASSOC_REF + { + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(ChildAssociationRef.class, value); + } + }, + ASSOC_REF + { + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(AssociationRef.class, value); + } + }, QNAME { @Override @@ -296,6 +326,14 @@ public class PropertyValue implements Cloneable, Serializable { return ValueType.NODEREF; } + else if (value instanceof ChildAssociationRef) + { + return ValueType.CHILD_ASSOC_REF; + } + else if (value instanceof AssociationRef) + { + return ValueType.ASSOC_REF; + } else if (value instanceof QName) { return ValueType.QNAME; @@ -328,6 +366,8 @@ public class PropertyValue implements Cloneable, Serializable valueTypesByPropertyType.put(DataTypeDefinition.CONTENT, ValueType.CONTENT); valueTypesByPropertyType.put(DataTypeDefinition.TEXT, ValueType.STRING); valueTypesByPropertyType.put(DataTypeDefinition.NODE_REF, ValueType.NODEREF); + valueTypesByPropertyType.put(DataTypeDefinition.CHILD_ASSOC_REF, ValueType.CHILD_ASSOC_REF); + valueTypesByPropertyType.put(DataTypeDefinition.ASSOC_REF, ValueType.ASSOC_REF); valueTypesByPropertyType.put(DataTypeDefinition.PATH, ValueType.PATH); valueTypesByPropertyType.put(DataTypeDefinition.QNAME, ValueType.QNAME); } diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java index 4e37bc31e2..df770553b8 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java @@ -30,7 +30,6 @@ import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.DbAccessControlList; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.domain.Store; import org.alfresco.service.cmr.repository.NodeRef; diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index f00bc2d017..492f76b400 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -113,13 +113,15 @@ public abstract class AbstractNodeServiceImpl implements NodeService private AssociationPolicyDelegate onDeleteAssociationDelegate; /** - * @param policyComponent the component with which to register class policies and behaviour - * @param dictionaryService - * used to check that node operations conform to the model + * */ - protected AbstractNodeServiceImpl(PolicyComponent policyComponent) + protected AbstractNodeServiceImpl() { this.uuid = GUID.generate(); + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { this.policyComponent = policyComponent; } diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 02526cf62d..cdcd66f480 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -373,16 +373,21 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest return ret; } - private int countNodesById(NodeRef nodeRef) + private int countNodesByReference(NodeRef nodeRef) { String query = "select count(node.uuid)" + " from " + NodeImpl.class.getName() + " node" + - " where node.uuid = ?"; + " where" + + " node.uuid = ? and" + + " node.store.key.protocol = ? and" + + " node.store.key.identifier = ?"; Session session = getSession(); List results = session.createQuery(query) .setString(0, nodeRef.getId()) + .setString(1, nodeRef.getStoreRef().getProtocol()) + .setString(2, nodeRef.getStoreRef().getIdentifier()) .list(); Integer count = (Integer) results.get(0); return count.intValue(); @@ -591,7 +596,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest ContentModel.TYPE_CONTAINER); NodeRef nodeRef = assocRef.getChildRef(); // count the nodes with the given id - int count = countNodesById(nodeRef); + int count = countNodesByReference(nodeRef); assertEquals("Unexpected number of nodes present", 1, count); } @@ -693,11 +698,11 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest // delete n1 nodeService.deleteNode(n1Ref); - assertEquals("Node not directly deleted", 0, countNodesById(n1Ref)); - assertEquals("Node not cascade deleted", 0, countNodesById(n3Ref)); - assertEquals("Node incorrectly cascade deleted", 1, countNodesById(n4Ref)); - assertEquals("Node not cascade deleted", 0, countNodesById(n6Ref)); - assertEquals("Node not cascade deleted", 0, countNodesById(n8Ref)); + assertEquals("Node not directly deleted", 0, countNodesByReference(n1Ref)); + assertEquals("Node not cascade deleted", 0, countNodesByReference(n3Ref)); + assertEquals("Node incorrectly cascade deleted", 1, countNodesByReference(n4Ref)); + assertEquals("Node not cascade deleted", 0, countNodesByReference(n6Ref)); + assertEquals("Node not cascade deleted", 0, countNodesByReference(n8Ref)); // commit to check setComplete(); diff --git a/source/java/org/alfresco/repo/node/StoreArchiveMap.java b/source/java/org/alfresco/repo/node/StoreArchiveMap.java new file mode 100644 index 0000000000..841ea6a363 --- /dev/null +++ b/source/java/org/alfresco/repo/node/StoreArchiveMap.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.StoreRef; + +/** + * A map component that maps node stores to their archive stores. + * + * @author Derek Hulley + */ +public class StoreArchiveMap +{ + private Map storeArchiveMap; + + public StoreArchiveMap() + { + storeArchiveMap = new HashMap(0); + } + + public Map getArchiveMap() + { + return storeArchiveMap; + } + + public void setArchiveMap(Map archiveMap) + { + // translate all the entries to references + for (Map.Entry entry : archiveMap.entrySet()) + { + String storeRefKeyStr = entry.getKey(); + String storeRefValueStr = entry.getValue(); + StoreRef storeRefKey = null; + StoreRef storeRefValue = null; + try + { + storeRefKey = new StoreRef(storeRefKeyStr); + storeRefValue = new StoreRef(storeRefValueStr); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Unable create store references from map entry: " + entry); + } + storeArchiveMap.put(storeRefKey, storeRefValue); + } + } +} diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 215e811dee..269d6875f8 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -35,7 +36,9 @@ import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.domain.Store; import org.alfresco.repo.node.AbstractNodeServiceImpl; -import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.node.StoreArchiveMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -56,8 +59,11 @@ import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.NodeRef.Status; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; /** @@ -67,20 +73,31 @@ import org.springframework.util.Assert; */ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { - private final DictionaryService dictionaryService; - private final NodeDaoService nodeDaoService; + private static Log logger = LogFactory.getLog(DbNodeServiceImpl.class); - public DbNodeServiceImpl( - PolicyComponent policyComponent, - DictionaryService dictionaryService, - NodeDaoService nodeDaoService) + private DictionaryService dictionaryService; + private NodeDaoService nodeDaoService; + private StoreArchiveMap storeArchiveMap; + + public DbNodeServiceImpl() + { + } + + public void setDictionaryService(DictionaryService dictionaryService) { - super(policyComponent); - this.dictionaryService = dictionaryService; + } + + public void setNodeDaoService(NodeDaoService nodeDaoService) + { this.nodeDaoService = nodeDaoService; } + public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap) + { + this.storeArchiveMap = storeArchiveMap; + } + /** * Performs a null-safe get of the node * @@ -97,6 +114,22 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } return unchecked; } + + /** + * Performs a null-safe get of the store + * @param storeRef the store to retrieve + * @return Returns the store entity (never null) + * @throws InvalidStoreRefException if the referenced store could not be found + */ + private Store getStoreNotNull(StoreRef storeRef) throws InvalidStoreRefException + { + Store unchecked = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + if (unchecked == null) + { + throw new InvalidStoreRefException("Store does not exist: " + storeRef, storeRef); + } + return unchecked; + } public boolean exists(StoreRef storeRef) { @@ -116,7 +149,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public Status getNodeStatus(NodeRef nodeRef) { - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef); + NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); if (nodeStatus == null) // node never existed { return null; @@ -306,7 +339,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // add all the aspects to the node Set nodeAspects = node.getAspects(); - for (AspectDefinition defaultAspectDef : defaultAspectDefs) + for (AspectDefinition defaultAspectDef : defaultAspectDefs) { invokeBeforeAddAspect(nodeRef, defaultAspectDef.getName()); nodeAspects.add(defaultAspectDef.getName()); @@ -613,104 +646,232 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Node node = getNodeNotNull(nodeRef); // get the primary parent-child relationship before it is gone ChildAssociationRef childAssocRef = getPrimaryParent(nodeRef); - // get type and aspect QNames as they will be unavailable after the delete - QName nodeTypeQName = node.getTypeQName(); + // get type and aspect QNames as they will be unavailable after the delete + QName nodeTypeQName = node.getTypeQName(); Set nodeAspectQNames = node.getAspects(); - // delete it - nodeDaoService.deleteNode(node, true); + + // check if we need to archive the node + StoreRef storeRef = nodeRef.getStoreRef(); + StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + // get the type and check if we need archiving + TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); + if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) + { + // perform a normal deletion + nodeDaoService.deleteNode(node, true); + } + else + { + // archive it + archiveNode(nodeRef, archiveStoreRef); + } // Invoke policy behaviours invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames); } -// /** -// * Recursive method to ensure cascade-deletion works with full invocation of policy behaviours. -// *

-// * The recursion will first cascade down primary associations before deleting all regular and -// * child associations to and from it. After this, the node itself is deleted. This bottom-up -// * behaviour ensures that the policy invocation behaviour, which currently relies on being able -// * to inspect association source types, gets fired correctly. -// */ -// public void deleteNode(NodeRef nodeRef) -// { -// // Invoke policy behaviours -// invokeBeforeDeleteNode(nodeRef); -// -// // get the node -// Node node = getNodeNotNull(nodeRef); -// -// // get node info (for invocation purposes) before any deletions occur -// // get the primary parent-child relationship before it is gone -// ChildAssociationRef primaryParentAssocRef = getPrimaryParent(nodeRef); -// // get type and aspect QNames as they will be unavailable after the delete -// QName nodeTypeQName = node.getTypeQName(); -// Set nodeAspectQNames = node.getAspects(); -// -// // get all associations, forcing a load of the collections -// Collection childAssocs = new ArrayList(node.getChildAssocs()); -// Collection parentAssocs = new ArrayList(node.getParentAssocs()); -// Collection sourceAssocs = new ArrayList(node.getSourceNodeAssocs()); -// Collection targetAssocs = new ArrayList(node.getTargetNodeAssocs()); -// -// // remove all child associations, including the primary one -// for (ChildAssoc childAssoc : childAssocs) -// { -// ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); -// // cascade into primary associations -// if (childAssoc.getIsPrimary()) -// { -// NodeRef childNodeRef = childAssocRef.getChildRef(); -// this.deleteNode(childNodeRef); -// } -// -// // one or more of these associations may have been dealt with when deleting the -// // child, so check that the association is valid -// -// // invoke pre-deletion behaviour -// invokeBeforeDeleteChildAssociation(childAssocRef); -// // remove it - cascade just to be sure -// nodeDaoService.deleteChildAssoc(childAssoc, true); -// // invoke post-deletion behaviour -// invokeOnDeleteChildAssociation(childAssocRef); -// } -// // remove all parent associations, including the primary one -// for (ChildAssoc parentAssoc : parentAssocs) -// { -// ChildAssociationRef parentAssocRef = parentAssoc.getChildAssocRef(); -// // invoke pre-deletion behaviour -// invokeBeforeDeleteChildAssociation(parentAssocRef); -// // remove it - don't cascade as this is a parent assoc -// nodeDaoService.deleteChildAssoc(parentAssoc, false); -// // invoke post-deletion behaviour -// invokeOnDeleteChildAssociation(parentAssocRef); -// } -// // remove all source node associations -// for (NodeAssoc sourceAssoc : sourceAssocs) -// { -// AssociationRef sourceAssocRef = sourceAssoc.getNodeAssocRef(); -// // remove it -// nodeDaoService.deleteNodeAssoc(sourceAssoc); -// // invoke post-deletion behaviour -// invokeOnDeleteAssociation(sourceAssocRef); -// } -// // remove all target node associations -// for (NodeAssoc targetAssoc : targetAssocs) -// { -// AssociationRef targetAssocRef = targetAssoc.getNodeAssocRef(); -// // remove it -// nodeDaoService.deleteNodeAssoc(targetAssoc); -// // invoke post-deletion behaviour -// invokeOnDeleteAssociation(targetAssocRef); -// } -// -// // delete it -// // We cascade so that we are sure that any new children created by policy implementations are -// // removed. There won't be any noticiations for these, but it prevents the cascade and -// // notifications from chasing each other -// nodeDaoService.deleteNode(node, true); -// -// // Invoke policy behaviours -// invokeOnDeleteNode(primaryParentAssocRef, nodeTypeQName, nodeAspectQNames); -// } + + private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) + { + Node node = getNodeNotNull(nodeRef); + Store archiveStore = getStoreNotNull(archiveStoreRef); + ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node); + + // add the aspect + node.getAspects().add(ContentModel.ASPECT_ARCHIVED); + Map properties = node.getProperties(); + PropertyValue archivedByProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), + AuthenticationUtil.getCurrentUserName()); + properties.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); + PropertyValue archivedDateProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), + new Date()); + properties.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); + PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT), + primaryParentAssoc.getParent().getNodeRef()); + properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT, archivedPrimaryParentNodeRefProperty); + + // get the IDs of all the node's primary children, including its own + Map nodesById = new HashMap(29); + getPrimaryChildren(node, nodesById); + + // move each node into the archive store + for (Node nodeToMove : nodesById.values()) + { + NodeRef oldNodeRef = nodeToMove.getNodeRef(); + nodeToMove.setStore(archiveStore); + NodeRef newNodeRef = nodeToMove.getNodeRef(); + + // update change statuses + String txnId = AlfrescoTransactionSupport.getTransactionId(); + NodeStatus oldNodeStatus = nodeDaoService.getNodeStatus(oldNodeRef, true); + oldNodeStatus.setNode(null); + oldNodeStatus.setChangeTxnId(txnId); + NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true); + newNodeStatus.setNode(nodeToMove); + newNodeStatus.setChangeTxnId(txnId); + } + + // archive all the associations between the archived nodes and non-archived nodes + for (Node nodeToArchive : nodesById.values()) + { + archiveAssocs(nodeToArchive, nodesById); + } + + // the node reference has changed due to the store move + nodeRef = node.getNodeRef(); + + // now associate the top-level node with the root of the new store + NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef); + Node archiveStoreRootNode = getNodeNotNull(archiveStoreRootNodeRef); + QName assocTypeQName = ContentModel.ASSOC_CHILDREN; + QName assocQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem"); + + invokeBeforeCreateChildAssociation(archiveStoreRootNodeRef, nodeRef, assocTypeQName, assocQName); + invokeBeforeUpdateNode(archiveStoreRootNodeRef); + + // create a new assoc + ChildAssoc newAssoc = nodeDaoService.newChildAssoc(archiveStoreRootNode, node, true, assocTypeQName, assocQName); + + // invoke policy behaviour + invokeOnCreateChildAssociation(newAssoc.getChildAssocRef()); + invokeOnUpdateNode(archiveStoreRootNodeRef); + } + + /** + * Fill the map of all primary children below the given node. + * The given node will be added to the map and the method is recursive + * to all primary children. + */ + private void getPrimaryChildren(Node node, Map nodesById) + { + Long id = node.getId(); + if (nodesById.containsKey(id)) + { + // this ID was already added - circular reference + logger.warn("Circular hierarchy found including node " + id); + return; + } + // add the node to the map + nodesById.put(id, node); + // recurse into the primary children + Collection childAssocs = node.getChildAssocs(); + for (ChildAssoc childAssoc : childAssocs) + { + // cascade into primary associations + if (childAssoc.getIsPrimary()) + { + Node primaryChild = childAssoc.getChild(); + getPrimaryChildren(primaryChild, nodesById); + } + } + } + + /** + * Archive all associations to and from the given node, with the + * exception of associations to or from nodes in the given map. + * @param node the node whose associations must be archived + * @param nodesById a map of nodes partaking in the archival process + */ + private void archiveAssocs(Node node, Map nodesById) + { + List childAssocsToDelete = new ArrayList(5); + // child associations + ArrayList archivedChildAssocRefs = new ArrayList(5); + for (ChildAssoc assoc : node.getChildAssocs()) + { + Long relatedNodeId = assoc.getChild().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + childAssocsToDelete.add(assoc); + archivedChildAssocRefs.add(assoc.getChildAssocRef()); + } + // parent associations + ArrayList archivedParentAssocRefs = new ArrayList(5); + for (ChildAssoc assoc : node.getParentAssocs()) + { + Long relatedNodeId = assoc.getParent().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + childAssocsToDelete.add(assoc); + archivedParentAssocRefs.add(assoc.getChildAssocRef()); + } + + List nodeAssocsToDelete = new ArrayList(5); + // source associations + ArrayList archivedSourceAssocRefs = new ArrayList(5); + for (NodeAssoc assoc : node.getSourceNodeAssocs()) + { + Long relatedNodeId = assoc.getSource().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + nodeAssocsToDelete.add(assoc); + archivedSourceAssocRefs.add(assoc.getNodeAssocRef()); + } + // target associations + ArrayList archivedTargetAssocRefs = new ArrayList(5); + for (NodeAssoc assoc : node.getTargetNodeAssocs()) + { + Long relatedNodeId = assoc.getSource().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + nodeAssocsToDelete.add(assoc); + archivedTargetAssocRefs.add(assoc.getNodeAssocRef()); + } + // delete child assocs + for (ChildAssoc assoc : childAssocsToDelete) + { + nodeDaoService.deleteChildAssoc(assoc, false); + } + // delete node assocs + for (NodeAssoc assoc : nodeAssocsToDelete) + { + nodeDaoService.deleteNodeAssoc(assoc); + } + + // add archived aspect + node.getAspects().add(ContentModel.ASPECT_ARCHIVED_ASSOCS); + // set properties + Map properties = node.getProperties(); + + if (archivedParentAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS, propertyValue); + } + if (archivedChildAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS, propertyValue); + } + if (archivedSourceAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, propertyValue); + } + if (archivedTargetAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, propertyValue); + } + } public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) { diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index 63d60f8440..43341b58c3 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -112,7 +112,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest *

  • deletion
  • * */ - public void testNodeStatus() throws Exception + public void testNodeStatus() throws Throwable { Map assocRefs = buildNodeGraph(); // get the node to play with @@ -190,13 +190,13 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest public Object doWork() { // check n6 - NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref); + NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false); if (!n6Status.isDeleted()) { throw new RuntimeException("Deleted node does not have deleted status"); } // n8 is a primary child - it should be deleted too - NodeStatus n8Status = nodeDaoService.getNodeStatus(n8Ref); + NodeStatus n8Status = nodeDaoService.getNodeStatus(n8Ref, false); if (!n8Status.isDeleted()) { throw new RuntimeException("Cascade-deleted node does not have deleted status"); @@ -228,7 +228,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest TransactionUtil.executeInUserTransaction(txnService, checkRecreateWork); } - private void executeAndCheck(NodeRef nodeRef, TransactionWork work) throws Exception + private void executeAndCheck(NodeRef nodeRef, TransactionWork work) throws Throwable { UserTransaction txn = txnService.getUserTransaction(); txn.begin(); @@ -248,7 +248,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest assertEquals("Change didn't update status", currentTxnId, newStatus.getChangeTxnId()); txn.commit(); } - catch (Exception e) + catch (Throwable e) { try { txn.rollback(); } catch (Throwable ee) {} throw e; diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 985a83a012..9dbdaccffc 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -70,10 +70,11 @@ public interface NodeDaoService * null is returned. * * @param nodeRef the node reference + * @param create true to create the entity if it doesn't exist * @return Returns the node status if the node exists or once existed, otherwise - * returns null. + * returns null if create == false */ - public NodeStatus getNodeStatus(NodeRef nodeRef); + public NodeStatus getNodeStatus(NodeRef nodeRef, boolean create); /** * Sets the current transaction ID on the node status. Note that the node diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index e747bcb958..e670eadf59 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -177,14 +177,13 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements /** * Fetch the node status, if it exists */ - public NodeStatus getNodeStatus(NodeRef nodeRef) + public NodeStatus getNodeStatus(NodeRef nodeRef, boolean create) { + NodeKey nodeKey = new NodeKey(nodeRef); + NodeStatus status = null; try { - NodeKey nodeKey = new NodeKey(nodeRef); - Object obj = getHibernateTemplate().get(NodeStatusImpl.class, nodeKey); - // done - return (NodeStatus) obj; + status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, nodeKey); } catch (DataAccessException e) { @@ -195,6 +194,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } throw e; } + // create if necessary + if (status == null && create) + { + status = new NodeStatusImpl(); + status.setKey(nodeKey); + status.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); + getHibernateTemplate().save(status); + } + // done + return status; } public void recordChangeId(NodeRef nodeRef) @@ -260,7 +269,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements public Node getNode(NodeRef nodeRef) { // get it via the node status - NodeStatus status = getNodeStatus(nodeRef); + NodeStatus status = getNodeStatus(nodeRef, false); if (status == null) { // no status implies no node @@ -309,17 +318,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } // update the node status NodeRef nodeRef = node.getNodeRef(); - NodeStatus nodeStatus = getNodeStatus(nodeRef); - if (nodeStatus == null) - { - logger.warn("Node to be deleted does not have a status: \n" + - " node: " + node.getId()); - } - else - { - nodeStatus.setNode(null); - nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - } + NodeStatus nodeStatus = getNodeStatus(nodeRef, true); + nodeStatus.setNode(null); + nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); // finally delete the node getHibernateTemplate().delete(node); // done diff --git a/source/java/org/alfresco/service/cmr/dictionary/ClassDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/ClassDefinition.java index b209bb48f4..aa4b245248 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/ClassDefinition.java +++ b/source/java/org/alfresco/service/cmr/dictionary/ClassDefinition.java @@ -58,6 +58,11 @@ public interface ClassDefinition * @return true => aspect, false => type */ public boolean isAspect(); + + /** + * @return Return true if the type should be archived on delete + */ + public boolean isArchive(); /** * @return the properties of the class, including inherited properties diff --git a/source/java/org/alfresco/service/cmr/dictionary/DataTypeDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/DataTypeDefinition.java index b2d48833be..31cf6034e9 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/DataTypeDefinition.java +++ b/source/java/org/alfresco/service/cmr/dictionary/DataTypeDefinition.java @@ -45,6 +45,8 @@ public interface DataTypeDefinition public QName QNAME = QName.createQName(NamespaceService.DICTIONARY_MODEL_1_0_URI, "qname"); public QName CATEGORY = QName.createQName(NamespaceService.DICTIONARY_MODEL_1_0_URI, "category"); public QName NODE_REF = QName.createQName(NamespaceService.DICTIONARY_MODEL_1_0_URI, "noderef"); + public QName CHILD_ASSOC_REF = QName.createQName(NamespaceService.DICTIONARY_MODEL_1_0_URI, "childassocref"); + public QName ASSOC_REF = QName.createQName(NamespaceService.DICTIONARY_MODEL_1_0_URI, "assocref"); public QName PATH = QName.createQName(NamespaceService.DICTIONARY_MODEL_1_0_URI, "path"); diff --git a/source/java/org/alfresco/service/cmr/repository/AssociationRef.java b/source/java/org/alfresco/service/cmr/repository/AssociationRef.java index aee6645f23..31720d79ca 100644 --- a/source/java/org/alfresco/service/cmr/repository/AssociationRef.java +++ b/source/java/org/alfresco/service/cmr/repository/AssociationRef.java @@ -17,7 +17,9 @@ package org.alfresco.service.cmr.repository; import java.io.Serializable; +import java.util.StringTokenizer; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; @@ -29,6 +31,8 @@ import org.alfresco.util.EqualsHelper; public class AssociationRef implements EntityRef, Serializable { private static final long serialVersionUID = 3977867284482439475L; + + private static final String FILLER = "|"; private NodeRef sourceRef; private QName assocTypeQName; @@ -65,32 +69,36 @@ public class AssociationRef implements EntityRef, Serializable throw new IllegalArgumentException("Target reference may not be null"); } } - + /** - * Get the qualified name of the source-target association - * - * @return Returns the qualified name of the source-target association. + * @param childAssocRefStr a string of the form sourceNodeRef|targetNodeRef|assocTypeQName */ - public QName getTypeQName() + public AssociationRef(String assocRefStr) { - return assocTypeQName; + StringTokenizer tokenizer = new StringTokenizer(assocRefStr, FILLER); + if (tokenizer.countTokens() != 3) + { + throw new AlfrescoRuntimeException("Unable to parse association string: " + assocRefStr); + } + String sourceNodeRefStr = tokenizer.nextToken(); + String targetNodeRefStr = tokenizer.nextToken(); + String assocTypeQNameStr = tokenizer.nextToken(); + + this.sourceRef = new NodeRef(sourceNodeRefStr); + this.targetRef = new NodeRef(targetNodeRefStr); + this.assocTypeQName = QName.createQName(assocTypeQNameStr); } /** - * @return Returns the child node reference - never null + * @return Returns a string of the form sourceNodeRef|targetNodeRef|assocTypeQName|assocQName */ - public NodeRef getTargetRef() + public String toString() { - return targetRef; - } - - /** - * @return Returns the parent node reference, which may be null if this - * represents the imaginary reference to the root node - */ - public NodeRef getSourceRef() - { - return sourceRef; + StringBuilder sb = new StringBuilder(180); + sb.append(sourceRef).append(FILLER) + .append(targetRef).append(FILLER) + .append(assocTypeQName); + return sb.toString(); } /** @@ -126,12 +134,30 @@ public class AssociationRef implements EntityRef, Serializable return hashCode; } - public String toString() + /** + * Get the qualified name of the source-target association + * + * @return Returns the qualified name of the source-target association. + */ + public QName getTypeQName() { - StringBuffer buffer = new StringBuffer(); - buffer.append(getSourceRef()); - buffer.append(" --- ").append(getTypeQName()).append(" ---> "); - buffer.append(getTargetRef()); - return buffer.toString(); + return assocTypeQName; + } + + /** + * @return Returns the child node reference - never null + */ + public NodeRef getTargetRef() + { + return targetRef; + } + + /** + * @return Returns the parent node reference, which may be null if this + * represents the imaginary reference to the root node + */ + public NodeRef getSourceRef() + { + return sourceRef; } } diff --git a/source/java/org/alfresco/service/cmr/repository/ChildAssociationRef.java b/source/java/org/alfresco/service/cmr/repository/ChildAssociationRef.java index 5694920be8..e2b30ff0d9 100644 --- a/source/java/org/alfresco/service/cmr/repository/ChildAssociationRef.java +++ b/source/java/org/alfresco/service/cmr/repository/ChildAssociationRef.java @@ -17,7 +17,9 @@ package org.alfresco.service.cmr.repository; import java.io.Serializable; +import java.util.StringTokenizer; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; @@ -39,6 +41,8 @@ public class ChildAssociationRef implements EntityRef, Comparable, Serializable { private static final long serialVersionUID = 4051322336257127729L; + + private static final String FILLER = "|"; private QName assocTypeQName; private NodeRef parentRef; @@ -97,7 +101,47 @@ public class ChildAssociationRef { this(assocTypeQName, parentRef, childQName, childRef, false, -1); } + + /** + * @param childAssocRefStr a string of the form parentNodeRef|childNodeRef|assocTypeQName|assocQName|isPrimary|nthSibling + */ + public ChildAssociationRef(String childAssocRefStr) + { + StringTokenizer tokenizer = new StringTokenizer(childAssocRefStr, FILLER); + if (tokenizer.countTokens() != 6) + { + throw new AlfrescoRuntimeException("Unable to parse child association string: " + childAssocRefStr); + } + String parentNodeRefStr = tokenizer.nextToken(); + String childNodeRefStr = tokenizer.nextToken(); + String assocTypeQNameStr = tokenizer.nextToken(); + String assocQNameStr = tokenizer.nextToken(); + String isPrimaryStr = tokenizer.nextToken(); + String nthSiblingStr = tokenizer.nextToken(); + + this.parentRef = new NodeRef(parentNodeRefStr); + this.childRef = new NodeRef(childNodeRefStr); + this.assocTypeQName = QName.createQName(assocTypeQNameStr); + this.childQName = QName.createQName(assocQNameStr); + this.isPrimary = Boolean.parseBoolean(isPrimaryStr); + this.nthSibling = Integer.parseInt(nthSiblingStr); + } + /** + * @return Returns a string of the form parentNodeRef|childNodeRef|assocTypeQName|assocQName|isPrimary|nthSibling + */ + public String toString() + { + StringBuilder sb = new StringBuilder(250); + sb.append(parentRef).append(FILLER) + .append(childRef).append(FILLER) + .append(assocTypeQName).append(FILLER) + .append(childQName).append(FILLER) + .append(isPrimary).append(FILLER) + .append(nthSibling); + return sb.toString(); + } + /** * Compares: *
      @@ -144,16 +188,6 @@ public class ChildAssociationRef return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); } - public String toString() - { - StringBuffer sb = new StringBuffer(); - sb.append("[").append(getTypeQName()).append("]"); - sb.append(getParentRef()); - sb.append(" --- ").append(getQName()).append(" ---> "); - sb.append(getChildRef()); - return sb.toString(); - } - /** * Get the qualified name of the association type * diff --git a/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java b/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java index f5423744e3..ff00b2dc7a 100644 --- a/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java +++ b/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java @@ -30,8 +30,11 @@ import java.util.Calendar; import java.util.Date; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.EntityRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.namespace.QName; @@ -223,6 +226,24 @@ public class DefaultTypeConverter }); + INSTANCE.addConverter(String.class, ChildAssociationRef.class, new TypeConverter.Converter() + { + public ChildAssociationRef convert(String source) + { + return new ChildAssociationRef(source); + } + + }); + + INSTANCE.addConverter(String.class, AssociationRef.class, new TypeConverter.Converter() + { + public AssociationRef convert(String source) + { + return new AssociationRef(source); + } + + }); + INSTANCE.addConverter(String.class, InputStream.class, new TypeConverter.Converter() { public InputStream convert(String source) @@ -582,19 +603,19 @@ public class DefaultTypeConverter INSTANCE.addDynamicTwoStageConverter(QName.class, String.class, InputStream.class); // - // NodeRef + // EntityRef (NodeRef, ChildAssociationRef, NodeAssociationRef) // - INSTANCE.addConverter(NodeRef.class, String.class, new TypeConverter.Converter() + INSTANCE.addConverter(EntityRef.class, String.class, new TypeConverter.Converter() { - public String convert(NodeRef source) + public String convert(EntityRef source) { return source.toString(); } }); - INSTANCE.addDynamicTwoStageConverter(NodeRef.class, String.class, InputStream.class); - + INSTANCE.addDynamicTwoStageConverter(EntityRef.class, String.class, InputStream.class); + // // ContentData //