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 @@
Foldercm:cmobject
+ trued:boolean
@@ -68,6 +69,7 @@
Contentcm:cmobject
+ trued: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.StandardAnalyzerorg.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 @@
+
+
+
+
+
+
+
-
-
-
-
+
-
-
+
+
-
+
+
+
+
+
+
+
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