mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-16 17:55:15 +00:00
Low-level archive and restore functionality
- Full tests of archive and restore against the contentModel.xml - TODO: Test permissions of archive store - Currently on a single, simple restoreNode method on NodeService - TODO: NodeRestoreService implementation to provide helpers around mass restoration, purging, etc git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2782 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
parent
3d4d316bdd
commit
8c92948879
@ -127,8 +127,8 @@
|
|||||||
<aspect name="sys:archived">
|
<aspect name="sys:archived">
|
||||||
<title>Archived</title>
|
<title>Archived</title>
|
||||||
<properties>
|
<properties>
|
||||||
<property name="sys:archivedOriginalParent">
|
<property name="sys:archivedOriginalParentAssoc">
|
||||||
<type>d:noderef</type>
|
<type>d:childassocref</type>
|
||||||
<mandatory>true</mandatory>
|
<mandatory>true</mandatory>
|
||||||
</property>
|
</property>
|
||||||
<property name="sys:archivedBy">
|
<property name="sys:archivedBy">
|
||||||
|
@ -348,6 +348,8 @@
|
|||||||
org.alfresco.service.cmr.repository.NodeService.getSourceAssocs=ROLE_AUTHENTICATED
|
org.alfresco.service.cmr.repository.NodeService.getSourceAssocs=ROLE_AUTHENTICATED
|
||||||
org.alfresco.service.cmr.repository.NodeService.getPath=ACL_NODE.0.sys:base.ReadProperties
|
org.alfresco.service.cmr.repository.NodeService.getPath=ACL_NODE.0.sys:base.ReadProperties
|
||||||
org.alfresco.service.cmr.repository.NodeService.getPaths=ACL_NODE.0.sys:base.ReadProperties
|
org.alfresco.service.cmr.repository.NodeService.getPaths=ACL_NODE.0.sys:base.ReadProperties
|
||||||
|
org.alfresco.service.cmr.repository.NodeService.getStoreArchiveNode=ACL_NODE.0.sys:base.Read
|
||||||
|
org.alfresco.service.cmr.repository.NodeService.restoreNode=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren
|
||||||
</value>
|
</value>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
@ -41,7 +41,7 @@ public interface ContentModel
|
|||||||
|
|
||||||
// archived nodes aspect constants
|
// archived nodes aspect constants
|
||||||
static final QName ASPECT_ARCHIVED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived");
|
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_ORIGINAL_PARENT_ASSOC = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalParentAssoc");
|
||||||
static final QName PROP_ARCHIVED_BY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedBy");
|
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 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 ASPECT_ARCHIVED_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived-assocs");
|
||||||
|
@ -145,7 +145,7 @@
|
|||||||
name="node"
|
name="node"
|
||||||
class="org.alfresco.repo.domain.hibernate.NodeImpl"
|
class="org.alfresco.repo.domain.hibernate.NodeImpl"
|
||||||
column="node_id"
|
column="node_id"
|
||||||
unique="true"
|
unique="false"
|
||||||
not-null="false"
|
not-null="false"
|
||||||
fetch="join"
|
fetch="join"
|
||||||
lazy="false" />
|
lazy="false" />
|
||||||
|
@ -0,0 +1,464 @@
|
|||||||
|
/*
|
||||||
|
* 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.archive;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.transaction.Status;
|
||||||
|
import javax.transaction.UserTransaction;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.node.StoreArchiveMap;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||||
|
import org.alfresco.service.ServiceRegistry;
|
||||||
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
||||||
|
import org.alfresco.service.cmr.security.PermissionService;
|
||||||
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
|
import org.alfresco.util.TestWithUserUtils;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the archive and restore functionality provided by the low-level
|
||||||
|
* node service.
|
||||||
|
*
|
||||||
|
* @author Derek Hulley
|
||||||
|
*/
|
||||||
|
public class ArchiveAndRestoreTest extends TestCase
|
||||||
|
{
|
||||||
|
private static final String USER_A = "AAAAA";
|
||||||
|
private static final String USER_B = "BBBBB";
|
||||||
|
private static final QName ASSOC_ATTACHMENTS = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "attachments");
|
||||||
|
private static final QName QNAME_A = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "a");
|
||||||
|
private static final QName QNAME_B = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "b");
|
||||||
|
private static final QName QNAME_AA = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "aa");
|
||||||
|
private static final QName QNAME_BB = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bb");
|
||||||
|
|
||||||
|
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||||
|
|
||||||
|
private NodeService nodeService;
|
||||||
|
private PermissionService permissionService;
|
||||||
|
private AuthenticationComponent authenticationComponent;
|
||||||
|
private AuthenticationService authenticationService;
|
||||||
|
private TransactionService transactionService;
|
||||||
|
|
||||||
|
private UserTransaction txn;
|
||||||
|
private StoreRef workStoreRef;
|
||||||
|
private NodeRef workStoreRootNodeRef;
|
||||||
|
private StoreRef archiveStoreRef;
|
||||||
|
private NodeRef archiveStoreRootNodeRef;
|
||||||
|
|
||||||
|
private NodeRef a;
|
||||||
|
private NodeRef b;
|
||||||
|
private NodeRef aa;
|
||||||
|
private NodeRef bb;
|
||||||
|
AssociationRef assocAtoB;
|
||||||
|
AssociationRef assocAAtoBB;
|
||||||
|
ChildAssociationRef childAssocAtoAA;
|
||||||
|
ChildAssociationRef childAssocBtoBB;
|
||||||
|
ChildAssociationRef childAssocBtoAA;
|
||||||
|
ChildAssociationRef childAssocAtoBB;
|
||||||
|
private NodeRef a_;
|
||||||
|
private NodeRef b_;
|
||||||
|
private NodeRef aa_;
|
||||||
|
private NodeRef bb_;
|
||||||
|
ChildAssociationRef childAssocAtoAA_;
|
||||||
|
ChildAssociationRef childAssocBtoBB_;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry");
|
||||||
|
nodeService = serviceRegistry.getNodeService();
|
||||||
|
permissionService = serviceRegistry.getPermissionService();
|
||||||
|
authenticationService = serviceRegistry.getAuthenticationService();
|
||||||
|
authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
|
||||||
|
transactionService = serviceRegistry.getTransactionService();
|
||||||
|
|
||||||
|
// Start a transaction
|
||||||
|
txn = transactionService.getUserTransaction();
|
||||||
|
txn.begin();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authenticationComponent.setSystemUserAsCurrentUser();
|
||||||
|
// Create the work store
|
||||||
|
workStoreRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis());
|
||||||
|
workStoreRootNodeRef = nodeService.getRootNode(workStoreRef);
|
||||||
|
archiveStoreRef = nodeService.createStore("archive", getName() + System.currentTimeMillis());
|
||||||
|
archiveStoreRootNodeRef = nodeService.getRootNode(archiveStoreRef);
|
||||||
|
|
||||||
|
// Map the work store to the archive store. This will already be wired into the NodeService.
|
||||||
|
StoreArchiveMap archiveMap = (StoreArchiveMap) ctx.getBean("storeArchiveMap");
|
||||||
|
archiveMap.getArchiveMap().put(workStoreRef, archiveStoreRef);
|
||||||
|
|
||||||
|
// grant everyone rights to the work store
|
||||||
|
permissionService.setPermission(
|
||||||
|
workStoreRootNodeRef,
|
||||||
|
PermissionService.ALL_AUTHORITIES,
|
||||||
|
PermissionService.ALL_PERMISSIONS,
|
||||||
|
true);
|
||||||
|
|
||||||
|
TestWithUserUtils.createUser(USER_A, USER_A, workStoreRootNodeRef, nodeService, authenticationService);
|
||||||
|
TestWithUserUtils.createUser(USER_B, USER_B, workStoreRootNodeRef, nodeService, authenticationService);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authenticationComponent.clearCurrentSecurityContext();
|
||||||
|
}
|
||||||
|
// authenticate as normal user
|
||||||
|
authenticationService.authenticate(USER_A, USER_A.toCharArray());
|
||||||
|
createNodeStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tearDown() throws Exception
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (txn.getStatus() == Status.STATUS_ACTIVE)
|
||||||
|
{
|
||||||
|
txn.rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the following:
|
||||||
|
* <pre>
|
||||||
|
* root
|
||||||
|
* / | \
|
||||||
|
* / | \
|
||||||
|
* / | \
|
||||||
|
* / | \
|
||||||
|
* A <-> B X
|
||||||
|
* |\ /|
|
||||||
|
* | \ / |
|
||||||
|
* | \/ |
|
||||||
|
* | /\ |
|
||||||
|
* | / \ |
|
||||||
|
* |/ \|
|
||||||
|
* AA <-> BB
|
||||||
|
* </pre>
|
||||||
|
* Explicit UUIDs are used for debugging purposes.
|
||||||
|
* <p>
|
||||||
|
* <b>A</b>, <b>B</b>, <b>AA</b> and <b>BB</b> are set up to archive automatically
|
||||||
|
* on deletion.
|
||||||
|
*/
|
||||||
|
private void createNodeStructure() throws Exception
|
||||||
|
{
|
||||||
|
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(5);
|
||||||
|
|
||||||
|
properties.put(ContentModel.PROP_NODE_UUID, "a");
|
||||||
|
a = nodeService.createNode(
|
||||||
|
workStoreRootNodeRef,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
QNAME_A,
|
||||||
|
ContentModel.TYPE_FOLDER,
|
||||||
|
properties).getChildRef();
|
||||||
|
properties.put(ContentModel.PROP_NODE_UUID, "aa");
|
||||||
|
childAssocAtoAA = nodeService.createNode(
|
||||||
|
a,
|
||||||
|
ContentModel.ASSOC_CONTAINS,
|
||||||
|
QNAME_AA,
|
||||||
|
ContentModel.TYPE_CONTENT,
|
||||||
|
properties);
|
||||||
|
aa = childAssocAtoAA.getChildRef();
|
||||||
|
properties.put(ContentModel.PROP_NODE_UUID, "b");
|
||||||
|
b = nodeService.createNode(
|
||||||
|
workStoreRootNodeRef,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
QNAME_B,
|
||||||
|
ContentModel.TYPE_FOLDER,
|
||||||
|
properties).getChildRef();
|
||||||
|
properties.put(ContentModel.PROP_NODE_UUID, "bb");
|
||||||
|
childAssocBtoBB = nodeService.createNode(
|
||||||
|
b,
|
||||||
|
ContentModel.ASSOC_CONTAINS,
|
||||||
|
QNAME_BB,
|
||||||
|
ContentModel.TYPE_CONTENT,
|
||||||
|
properties);
|
||||||
|
bb = childAssocBtoBB.getChildRef();
|
||||||
|
assocAtoB = nodeService.createAssociation(a, b, ASSOC_ATTACHMENTS);
|
||||||
|
assocAAtoBB = nodeService.createAssociation(aa, bb, ASSOC_ATTACHMENTS);
|
||||||
|
childAssocBtoAA = nodeService.addChild(
|
||||||
|
b,
|
||||||
|
aa,
|
||||||
|
ContentModel.ASSOC_CONTAINS,
|
||||||
|
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "aa"));
|
||||||
|
childAssocAtoBB = nodeService.addChild(
|
||||||
|
a,
|
||||||
|
bb,
|
||||||
|
ContentModel.ASSOC_CONTAINS,
|
||||||
|
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bb"));
|
||||||
|
|
||||||
|
// deduce the references
|
||||||
|
a_ = new NodeRef(archiveStoreRef, a.getId());
|
||||||
|
b_ = new NodeRef(archiveStoreRef, b.getId());
|
||||||
|
aa_ = new NodeRef(archiveStoreRef, aa.getId());
|
||||||
|
bb_ = new NodeRef(archiveStoreRef, bb.getId());
|
||||||
|
childAssocAtoAA_ = new ChildAssociationRef(
|
||||||
|
childAssocAtoAA.getTypeQName(),
|
||||||
|
a_,
|
||||||
|
childAssocAtoAA.getQName(),
|
||||||
|
aa_);
|
||||||
|
childAssocBtoBB_ = new ChildAssociationRef(
|
||||||
|
childAssocBtoBB.getTypeQName(),
|
||||||
|
b_,
|
||||||
|
childAssocBtoBB.getQName(),
|
||||||
|
bb_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNodeExistence(NodeRef nodeRef, boolean exists)
|
||||||
|
{
|
||||||
|
assertEquals("Node should " + (exists ? "" : "not") + "exist", exists, nodeService.exists(nodeRef));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyChildAssocExistence(ChildAssociationRef childAssocRef, boolean exists)
|
||||||
|
{
|
||||||
|
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(
|
||||||
|
childAssocRef.getParentRef(),
|
||||||
|
childAssocRef.getTypeQName(),
|
||||||
|
childAssocRef.getQName());
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
assertEquals("Expected exactly one match for child association: " + childAssocRef, 1, childAssocs.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assertEquals("Expected zero matches for child association: " + childAssocRef, 0, childAssocs.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyTargetAssocExistence(AssociationRef assocRef, boolean exists)
|
||||||
|
{
|
||||||
|
List<AssociationRef> assocs = nodeService.getTargetAssocs(
|
||||||
|
assocRef.getSourceRef(),
|
||||||
|
assocRef.getTypeQName());
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
assertEquals("Expected exactly one match for target association: " + assocRef, 1, assocs.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assertEquals("Expected zero matches for target association: " + assocRef, 0, assocs.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyAll()
|
||||||
|
{
|
||||||
|
// work store references
|
||||||
|
verifyNodeExistence(a, true);
|
||||||
|
verifyNodeExistence(b, true);
|
||||||
|
verifyNodeExistence(aa, true);
|
||||||
|
verifyNodeExistence(bb, true);
|
||||||
|
verifyChildAssocExistence(childAssocAtoAA, true);
|
||||||
|
verifyChildAssocExistence(childAssocBtoBB, true);
|
||||||
|
verifyChildAssocExistence(childAssocAtoBB, true);
|
||||||
|
verifyChildAssocExistence(childAssocBtoAA, true);
|
||||||
|
verifyTargetAssocExistence(assocAtoB, true);
|
||||||
|
verifyTargetAssocExistence(assocAAtoBB, true);
|
||||||
|
// archive store references
|
||||||
|
verifyNodeExistence(a_, false);
|
||||||
|
verifyNodeExistence(b_, false);
|
||||||
|
verifyNodeExistence(aa_, false);
|
||||||
|
verifyNodeExistence(bb_, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSetUp() throws Exception
|
||||||
|
{
|
||||||
|
verifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetStoreArchiveNode() throws Exception
|
||||||
|
{
|
||||||
|
NodeRef archiveNodeRef = nodeService.getStoreArchiveNode(workStoreRef);
|
||||||
|
assertEquals("Mapping of archived store is not correct", archiveStoreRootNodeRef, archiveNodeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testArchiveAndRestoreNodeBB() throws Exception
|
||||||
|
{
|
||||||
|
// delete a child
|
||||||
|
nodeService.deleteNode(bb);
|
||||||
|
// check
|
||||||
|
verifyNodeExistence(b, true);
|
||||||
|
verifyNodeExistence(bb, false);
|
||||||
|
verifyChildAssocExistence(childAssocAtoBB, false);
|
||||||
|
verifyChildAssocExistence(childAssocBtoBB, false);
|
||||||
|
verifyNodeExistence(b_, false);
|
||||||
|
verifyNodeExistence(bb_, true);
|
||||||
|
|
||||||
|
// restore the node
|
||||||
|
nodeService.restoreNode(bb_, null, null, null);
|
||||||
|
// check
|
||||||
|
verifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testArchiveAndRestoreNodeB() throws Exception
|
||||||
|
{
|
||||||
|
// delete a child
|
||||||
|
nodeService.deleteNode(b);
|
||||||
|
// check
|
||||||
|
verifyNodeExistence(b, false);
|
||||||
|
verifyNodeExistence(bb, false);
|
||||||
|
verifyChildAssocExistence(childAssocAtoBB, false);
|
||||||
|
verifyTargetAssocExistence(assocAtoB, false);
|
||||||
|
verifyTargetAssocExistence(assocAAtoBB, false);
|
||||||
|
verifyNodeExistence(b_, true);
|
||||||
|
verifyNodeExistence(bb_, true);
|
||||||
|
verifyChildAssocExistence(childAssocBtoBB_, true);
|
||||||
|
|
||||||
|
// restore the node
|
||||||
|
nodeService.restoreNode(b_, null, null, null);
|
||||||
|
// check
|
||||||
|
verifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testArchiveAndRestoreAll_B_A() throws Exception
|
||||||
|
{
|
||||||
|
// delete both trees in order 'b', 'a'
|
||||||
|
nodeService.deleteNode(b);
|
||||||
|
nodeService.deleteNode(a);
|
||||||
|
// restore in reverse order
|
||||||
|
nodeService.restoreNode(a_, null, null, null);
|
||||||
|
nodeService.restoreNode(b_, null, null, null);
|
||||||
|
// check
|
||||||
|
verifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testArchiveAndRestoreAll_A_B() throws Exception
|
||||||
|
{
|
||||||
|
// delete both trees in order 'b', 'a'
|
||||||
|
nodeService.deleteNode(a);
|
||||||
|
nodeService.deleteNode(b);
|
||||||
|
// restore in reverse order
|
||||||
|
nodeService.restoreNode(b_, null, null, null);
|
||||||
|
nodeService.restoreNode(a_, null, null, null);
|
||||||
|
// check
|
||||||
|
verifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testArchiveAndRestoreWithMissingAssocTargets() throws Exception
|
||||||
|
{
|
||||||
|
// delete a then b
|
||||||
|
nodeService.deleteNode(a);
|
||||||
|
nodeService.deleteNode(b);
|
||||||
|
// in restoring 'a' first, there will be some associations that won't be recreated
|
||||||
|
nodeService.restoreNode(a_, null, null, null);
|
||||||
|
nodeService.restoreNode(b_, null, null, null);
|
||||||
|
|
||||||
|
// check
|
||||||
|
verifyNodeExistence(a, true);
|
||||||
|
verifyNodeExistence(b, true);
|
||||||
|
verifyNodeExistence(aa, true);
|
||||||
|
verifyNodeExistence(bb, true);
|
||||||
|
verifyChildAssocExistence(childAssocAtoAA, true);
|
||||||
|
verifyChildAssocExistence(childAssocBtoBB, true);
|
||||||
|
verifyChildAssocExistence(childAssocAtoBB, false);
|
||||||
|
verifyChildAssocExistence(childAssocBtoAA, false);
|
||||||
|
verifyTargetAssocExistence(assocAtoB, false);
|
||||||
|
verifyTargetAssocExistence(assocAAtoBB, false);
|
||||||
|
verifyNodeExistence(a_, false);
|
||||||
|
verifyNodeExistence(b_, false);
|
||||||
|
verifyNodeExistence(aa_, false);
|
||||||
|
verifyNodeExistence(bb_, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the archival is performed based on the node type.
|
||||||
|
*/
|
||||||
|
public void testTypeDetection()
|
||||||
|
{
|
||||||
|
// change the type of 'a'
|
||||||
|
nodeService.setType(a, ContentModel.TYPE_CONTAINER);
|
||||||
|
// delete it
|
||||||
|
nodeService.deleteNode(a);
|
||||||
|
// it must be gone
|
||||||
|
verifyNodeExistence(a, false);
|
||||||
|
verifyNodeExistence(a_, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to measure how much archiving affects the deletion performance.
|
||||||
|
*/
|
||||||
|
public void testArchiveVsDeletePerformance() throws Exception
|
||||||
|
{
|
||||||
|
// Start by deleting the node structure and then recreating it.
|
||||||
|
// Only measure the delete speed
|
||||||
|
int iterations = 100;
|
||||||
|
long cumulatedArchiveTimeNs = 0;
|
||||||
|
long cumulatedRestoreTimeNs = 0;
|
||||||
|
for (int i = 0; i < iterations; i++)
|
||||||
|
{
|
||||||
|
// timed delete
|
||||||
|
long start = System.nanoTime();
|
||||||
|
nodeService.deleteNode(b);
|
||||||
|
long end = System.nanoTime();
|
||||||
|
cumulatedArchiveTimeNs += (end - start);
|
||||||
|
// now restore
|
||||||
|
start = System.nanoTime();
|
||||||
|
nodeService.restoreNode(b_, null, null, null);
|
||||||
|
end = System.nanoTime();
|
||||||
|
cumulatedRestoreTimeNs += (end - start);
|
||||||
|
}
|
||||||
|
double averageArchiveTimeMs = (double)cumulatedArchiveTimeNs / 1E6 / (double)iterations;
|
||||||
|
double averageRestoreTimeMs = (double)cumulatedRestoreTimeNs / 1E6 / (double)iterations;
|
||||||
|
System.out.println("Average archive time: " + averageArchiveTimeMs + " ms");
|
||||||
|
System.out.println("Average restore time: " + averageRestoreTimeMs + " ms");
|
||||||
|
|
||||||
|
// Now force full deletions and creations
|
||||||
|
StoreArchiveMap archiveMap = (StoreArchiveMap) ctx.getBean("storeArchiveMap");
|
||||||
|
archiveMap.getArchiveMap().clear();
|
||||||
|
long cumulatedDeleteTimeNs = 0;
|
||||||
|
long cumulatedCreateTimeNs = 0;
|
||||||
|
for (int i = 0; i < iterations; i++)
|
||||||
|
{
|
||||||
|
// timed delete
|
||||||
|
long start = System.nanoTime();
|
||||||
|
nodeService.deleteNode(b);
|
||||||
|
long end = System.nanoTime();
|
||||||
|
cumulatedDeleteTimeNs += (end - start);
|
||||||
|
// delete 'a' as well
|
||||||
|
nodeService.deleteNode(a);
|
||||||
|
// now rebuild
|
||||||
|
start = System.nanoTime();
|
||||||
|
createNodeStructure();
|
||||||
|
end = System.nanoTime();
|
||||||
|
cumulatedCreateTimeNs += (end - start);
|
||||||
|
}
|
||||||
|
double averageDeleteTimeMs = (double)cumulatedDeleteTimeNs / 1E6 / (double)iterations;
|
||||||
|
double averageCreateTimeMs = (double)cumulatedCreateTimeNs / 1E6 / (double)iterations;
|
||||||
|
System.out.println("Average delete time: " + averageDeleteTimeMs + " ms");
|
||||||
|
System.out.println("Average create time: " + averageCreateTimeMs + " ms");
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.domain.ChildAssoc;
|
import org.alfresco.repo.domain.ChildAssoc;
|
||||||
import org.alfresco.repo.domain.Node;
|
import org.alfresco.repo.domain.Node;
|
||||||
@ -81,6 +82,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
|||||||
|
|
||||||
public DbNodeServiceImpl()
|
public DbNodeServiceImpl()
|
||||||
{
|
{
|
||||||
|
storeArchiveMap = new StoreArchiveMap(); // in case it is not set
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDictionaryService(DictionaryService dictionaryService)
|
public void setDictionaryService(DictionaryService dictionaryService)
|
||||||
@ -115,22 +117,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
|||||||
return unchecked;
|
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)
|
public boolean exists(StoreRef storeRef)
|
||||||
{
|
{
|
||||||
Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier());
|
Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier());
|
||||||
@ -429,6 +415,15 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
|||||||
invokeBeforeUpdateNode(oldParentNode.getNodeRef()); // old parent will be updated
|
invokeBeforeUpdateNode(oldParentNode.getNodeRef()); // old parent will be updated
|
||||||
invokeBeforeUpdateNode(newParentRef); // new parent ditto
|
invokeBeforeUpdateNode(newParentRef); // new parent ditto
|
||||||
|
|
||||||
|
// If the node is moving stores, then drag the node hierarchy with it
|
||||||
|
if (!nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef()))
|
||||||
|
{
|
||||||
|
Store newStore = newParentNode.getStore();
|
||||||
|
moveNodeToStore(nodeToMove, newStore);
|
||||||
|
// the node reference will have changed too
|
||||||
|
nodeToMoveRef = nodeToMove.getNodeRef();
|
||||||
|
}
|
||||||
|
|
||||||
// remove the child assoc from the old parent
|
// remove the child assoc from the old parent
|
||||||
// don't cascade as we will still need the node afterwards
|
// don't cascade as we will still need the node afterwards
|
||||||
nodeDaoService.deleteChildAssoc(oldAssoc, false);
|
nodeDaoService.deleteChildAssoc(oldAssoc, false);
|
||||||
@ -670,209 +665,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
|||||||
invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames);
|
invokeOnDeleteNode(childAssocRef, 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<QName, PropertyValue> 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<Long, Node> nodesById = new HashMap<Long, Node>(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<Long, Node> 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<ChildAssoc> 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<Long, Node> nodesById)
|
|
||||||
{
|
|
||||||
List<ChildAssoc> childAssocsToDelete = new ArrayList<ChildAssoc>(5);
|
|
||||||
// child associations
|
|
||||||
ArrayList<ChildAssociationRef> archivedChildAssocRefs = new ArrayList<ChildAssociationRef>(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<ChildAssociationRef> archivedParentAssocRefs = new ArrayList<ChildAssociationRef>(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<NodeAssoc> nodeAssocsToDelete = new ArrayList<NodeAssoc>(5);
|
|
||||||
// source associations
|
|
||||||
ArrayList<AssociationRef> archivedSourceAssocRefs = new ArrayList<AssociationRef>(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<AssociationRef> archivedTargetAssocRefs = new ArrayList<AssociationRef>(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<QName, PropertyValue> 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)
|
public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName)
|
||||||
{
|
{
|
||||||
// Invoke policy behaviours
|
// Invoke policy behaviours
|
||||||
@ -1482,4 +1274,388 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
|||||||
// done
|
// done
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef)
|
||||||
|
{
|
||||||
|
Node node = getNodeNotNull(nodeRef);
|
||||||
|
ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node);
|
||||||
|
|
||||||
|
// add the aspect
|
||||||
|
node.getAspects().add(ContentModel.ASPECT_ARCHIVED);
|
||||||
|
Map<QName, PropertyValue> 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_ASSOC),
|
||||||
|
primaryParentAssoc.getChildAssocRef());
|
||||||
|
properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty);
|
||||||
|
|
||||||
|
// move the node
|
||||||
|
NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef);
|
||||||
|
moveNode(
|
||||||
|
nodeRef,
|
||||||
|
archiveStoreRootNodeRef,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem"));
|
||||||
|
|
||||||
|
// get the IDs of all the node's primary children, including its own
|
||||||
|
Map<Long, Node> nodesById = getNodeHierarchy(node, null);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs all the necessary housekeeping involved in changing a node's store.
|
||||||
|
* This method cascades down through all the primary children of the node as
|
||||||
|
* well.
|
||||||
|
*
|
||||||
|
* @param node the node whose store is changing
|
||||||
|
* @param store the new store for the node
|
||||||
|
*/
|
||||||
|
private void moveNodeToStore(Node node, Store store)
|
||||||
|
{
|
||||||
|
// get the IDs of all the node's primary children, including its own
|
||||||
|
Map<Long, Node> nodesById = getNodeHierarchy(node, null);
|
||||||
|
|
||||||
|
// move each node into the archive store
|
||||||
|
for (Node nodeToMove : nodesById.values())
|
||||||
|
{
|
||||||
|
NodeRef oldNodeRef = nodeToMove.getNodeRef();
|
||||||
|
nodeToMove.setStore(store);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param node the start of the hierarchy
|
||||||
|
* @param nodesById a map of nodes that will be reused as the return value
|
||||||
|
* @return Returns a map of nodes in the hierarchy keyed by their IDs
|
||||||
|
*/
|
||||||
|
private Map<Long, Node> getNodeHierarchy(Node node, Map<Long, Node> nodesById)
|
||||||
|
{
|
||||||
|
if (nodesById == null)
|
||||||
|
{
|
||||||
|
nodesById = new HashMap<Long, Node>(23);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long id = node.getId();
|
||||||
|
if (nodesById.containsKey(id))
|
||||||
|
{
|
||||||
|
// this ID was already added - circular reference
|
||||||
|
logger.warn("Circular hierarchy found including node " + id);
|
||||||
|
return nodesById;
|
||||||
|
}
|
||||||
|
// add the node to the map
|
||||||
|
nodesById.put(id, node);
|
||||||
|
// recurse into the primary children
|
||||||
|
Collection<ChildAssoc> childAssocs = node.getChildAssocs();
|
||||||
|
for (ChildAssoc childAssoc : childAssocs)
|
||||||
|
{
|
||||||
|
// cascade into primary associations
|
||||||
|
if (childAssoc.getIsPrimary())
|
||||||
|
{
|
||||||
|
Node primaryChild = childAssoc.getChild();
|
||||||
|
nodesById = getNodeHierarchy(primaryChild, nodesById);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodesById;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Archive all associations to and from the given node, with the
|
||||||
|
* exception of associations to or from nodes in the given map.
|
||||||
|
* <p>
|
||||||
|
* Primary parent associations are also ignored.
|
||||||
|
*
|
||||||
|
* @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<Long, Node> nodesById)
|
||||||
|
{
|
||||||
|
List<ChildAssoc> childAssocsToDelete = new ArrayList<ChildAssoc>(5);
|
||||||
|
// child associations
|
||||||
|
ArrayList<ChildAssociationRef> archivedChildAssocRefs = new ArrayList<ChildAssociationRef>(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<ChildAssociationRef> archivedParentAssocRefs = new ArrayList<ChildAssociationRef>(5);
|
||||||
|
for (ChildAssoc assoc : node.getParentAssocs())
|
||||||
|
{
|
||||||
|
Long relatedNodeId = assoc.getParent().getId();
|
||||||
|
if (nodesById.containsKey(relatedNodeId))
|
||||||
|
{
|
||||||
|
// a sibling in the archive process
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (assoc.getIsPrimary())
|
||||||
|
{
|
||||||
|
// ignore the primary parent as this is handled more specifically
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
childAssocsToDelete.add(assoc);
|
||||||
|
archivedParentAssocRefs.add(assoc.getChildAssocRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NodeAssoc> nodeAssocsToDelete = new ArrayList<NodeAssoc>(5);
|
||||||
|
// source associations
|
||||||
|
ArrayList<AssociationRef> archivedSourceAssocRefs = new ArrayList<AssociationRef>(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<AssociationRef> archivedTargetAssocRefs = new ArrayList<AssociationRef>(5);
|
||||||
|
for (NodeAssoc assoc : node.getTargetNodeAssocs())
|
||||||
|
{
|
||||||
|
Long relatedNodeId = assoc.getTarget().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<QName, PropertyValue> 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 NodeRef getStoreArchiveNode(StoreRef storeRef)
|
||||||
|
{
|
||||||
|
StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef);
|
||||||
|
if (archiveStoreRef == null)
|
||||||
|
{
|
||||||
|
// no mapping for the given store
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getRootNode(archiveStoreRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef targetParentNodeRef, QName assocTypeQName, QName assocQName)
|
||||||
|
{
|
||||||
|
Node archivedNode = getNodeNotNull(archivedNodeRef);
|
||||||
|
Set<QName> aspects = archivedNode.getAspects();
|
||||||
|
Map<QName, PropertyValue> properties = archivedNode.getProperties();
|
||||||
|
// the node must be a top-level archive node
|
||||||
|
if (!aspects.contains(ContentModel.ASPECT_ARCHIVED))
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("The node to archive is not an archive node");
|
||||||
|
}
|
||||||
|
ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue(
|
||||||
|
dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC),
|
||||||
|
properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC));
|
||||||
|
// remove the aspect archived aspect
|
||||||
|
aspects.remove(ContentModel.ASPECT_ARCHIVED);
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_BY);
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_DATE);
|
||||||
|
|
||||||
|
if (targetParentNodeRef == null)
|
||||||
|
{
|
||||||
|
// we must restore to the original location
|
||||||
|
targetParentNodeRef = originalPrimaryParentAssocRef.getParentRef();
|
||||||
|
}
|
||||||
|
// check the associations
|
||||||
|
if (assocTypeQName == null)
|
||||||
|
{
|
||||||
|
assocTypeQName = originalPrimaryParentAssocRef.getTypeQName();
|
||||||
|
}
|
||||||
|
if (assocQName == null)
|
||||||
|
{
|
||||||
|
assocQName = originalPrimaryParentAssocRef.getQName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the node to the target parent, which may or may not be the original parent
|
||||||
|
moveNode(
|
||||||
|
archivedNodeRef,
|
||||||
|
targetParentNodeRef,
|
||||||
|
assocTypeQName,
|
||||||
|
assocQName);
|
||||||
|
|
||||||
|
// get the IDs of all the node's primary children, including its own
|
||||||
|
Map<Long, Node> restoredNodesById = getNodeHierarchy(archivedNode, null);
|
||||||
|
// Restore the archived associations, if required
|
||||||
|
for (Node restoredNode : restoredNodesById.values())
|
||||||
|
{
|
||||||
|
restoreAssocs(restoredNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the node reference has changed due to the store move
|
||||||
|
NodeRef restoredNodeRef = archivedNode.getNodeRef();
|
||||||
|
|
||||||
|
// done
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("Restored node: \n" +
|
||||||
|
" original noderef: " + archivedNodeRef + "\n" +
|
||||||
|
" restored noderef: " + restoredNodeRef + "\n" +
|
||||||
|
" new parent: " + targetParentNodeRef);
|
||||||
|
}
|
||||||
|
return restoredNodeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreAssocs(Node node)
|
||||||
|
{
|
||||||
|
NodeRef nodeRef = node.getNodeRef();
|
||||||
|
// set properties
|
||||||
|
Map<QName, PropertyValue> properties = node.getProperties();
|
||||||
|
|
||||||
|
// restore parent associations
|
||||||
|
Collection<ChildAssociationRef> parentAssocRefs = (Collection<ChildAssociationRef>) getProperty(
|
||||||
|
nodeRef,
|
||||||
|
ContentModel.PROP_ARCHIVED_PARENT_ASSOCS);
|
||||||
|
if (parentAssocRefs != null)
|
||||||
|
{
|
||||||
|
for (ChildAssociationRef assocRef : parentAssocRefs)
|
||||||
|
{
|
||||||
|
NodeRef parentNodeRef = assocRef.getParentRef();
|
||||||
|
if (!exists(parentNodeRef))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Node parentNode = getNodeNotNull(parentNodeRef);
|
||||||
|
nodeDaoService.newChildAssoc(parentNode, node, assocRef.isPrimary(), assocRef.getTypeQName(), assocRef.getQName());
|
||||||
|
}
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS);
|
||||||
|
}
|
||||||
|
// restore child associations
|
||||||
|
Collection<ChildAssociationRef> childAssocRefs = (Collection<ChildAssociationRef>) getProperty(
|
||||||
|
nodeRef,
|
||||||
|
ContentModel.PROP_ARCHIVED_CHILD_ASSOCS);
|
||||||
|
if (childAssocRefs != null)
|
||||||
|
{
|
||||||
|
for (ChildAssociationRef assocRef : childAssocRefs)
|
||||||
|
{
|
||||||
|
NodeRef childNodeRef = assocRef.getChildRef();
|
||||||
|
if (!exists(childNodeRef))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Node childNode = getNodeNotNull(childNodeRef);
|
||||||
|
nodeDaoService.newChildAssoc(node, childNode, assocRef.isPrimary(), assocRef.getTypeQName(), assocRef.getQName());
|
||||||
|
}
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS);
|
||||||
|
}
|
||||||
|
// restore source associations
|
||||||
|
Collection<AssociationRef> sourceAssocRefs = (Collection<AssociationRef>) getProperty(
|
||||||
|
nodeRef,
|
||||||
|
ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS);
|
||||||
|
if (sourceAssocRefs != null)
|
||||||
|
{
|
||||||
|
for (AssociationRef assocRef : sourceAssocRefs)
|
||||||
|
{
|
||||||
|
NodeRef sourceNodeRef = assocRef.getSourceRef();
|
||||||
|
if (!exists(sourceNodeRef))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Node sourceNode = getNodeNotNull(sourceNodeRef);
|
||||||
|
nodeDaoService.newNodeAssoc(sourceNode, node, assocRef.getTypeQName());
|
||||||
|
}
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS);
|
||||||
|
}
|
||||||
|
// restore target associations
|
||||||
|
Collection<AssociationRef> targetAssocRefs = (Collection<AssociationRef>) getProperty(
|
||||||
|
nodeRef,
|
||||||
|
ContentModel.PROP_ARCHIVED_TARGET_ASSOCS);
|
||||||
|
if (targetAssocRefs != null)
|
||||||
|
{
|
||||||
|
for (AssociationRef assocRef : targetAssocRefs)
|
||||||
|
{
|
||||||
|
NodeRef targetNodeRef = assocRef.getTargetRef();
|
||||||
|
if (!exists(targetNodeRef))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Node targetNode = getNodeNotNull(targetNodeRef);
|
||||||
|
nodeDaoService.newNodeAssoc(node, targetNode, assocRef.getTypeQName());
|
||||||
|
}
|
||||||
|
properties.remove(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS);
|
||||||
|
}
|
||||||
|
// remove the aspect
|
||||||
|
node.getAspects().remove(ContentModel.ASPECT_ARCHIVED_ASSOCS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,7 @@ import org.alfresco.service.cmr.repository.NodeService;
|
|||||||
import org.alfresco.service.cmr.repository.Path;
|
import org.alfresco.service.cmr.repository.Path;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef.Status;
|
import org.alfresco.service.cmr.repository.NodeRef.Status;
|
||||||
import org.alfresco.service.cmr.search.QueryParameterDefinition;
|
|
||||||
import org.alfresco.service.cmr.search.SearchService;
|
import org.alfresco.service.cmr.search.SearchService;
|
||||||
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.namespace.QNamePattern;
|
import org.alfresco.service.namespace.QNamePattern;
|
||||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||||
@ -534,29 +532,19 @@ public class NodeServiceImpl implements NodeService, VersionModel
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<NodeRef> selectNodes(NodeRef contextNode, String XPath, QueryParameterDefinition[] parameters, NamespacePrefixResolver namespacePrefixResolver, boolean followAllParentLinks)
|
/**
|
||||||
|
* @throws UnsupportedOperationException always
|
||||||
|
*/
|
||||||
|
public NodeRef getStoreArchiveNode(StoreRef storeRef)
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
|
||||||
throw new UnsupportedOperationException(MSG_UNSUPPORTED);
|
throw new UnsupportedOperationException(MSG_UNSUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Serializable> selectProperties(NodeRef contextNode, String XPath, QueryParameterDefinition[] parameters, NamespacePrefixResolver namespacePrefixResolver, boolean followAllParentLinks)
|
/**
|
||||||
|
* @throws UnsupportedOperationException always
|
||||||
|
*/
|
||||||
|
public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef targetParentNodeRef, QName assocTypeQName, QName assocQName)
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
|
||||||
throw new UnsupportedOperationException(MSG_UNSUPPORTED);
|
throw new UnsupportedOperationException(MSG_UNSUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean contains(NodeRef nodeRef, QName property, String sqlLikePattern)
|
|
||||||
{
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean like(NodeRef nodeRef, QName property, String sqlLikePattern, boolean includeFTS)
|
|
||||||
{
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,10 @@ public interface NodeService
|
|||||||
* <p>
|
* <p>
|
||||||
* This involves changing the node's primary parent and possibly the name of the
|
* This involves changing the node's primary parent and possibly the name of the
|
||||||
* association referencing it.
|
* association referencing it.
|
||||||
|
* <p>
|
||||||
|
* If the new parent is in a different store from the original, then the entire
|
||||||
|
* node hierarchy is moved to the new store. Inter-store associations are not
|
||||||
|
* affected.
|
||||||
*
|
*
|
||||||
* @param nodeToMoveRef the node to move
|
* @param nodeToMoveRef the node to move
|
||||||
* @param newParentRef the new parent of the moved node
|
* @param newParentRef the new parent of the moved node
|
||||||
@ -475,4 +479,29 @@ public interface NodeService
|
|||||||
* @throws InvalidNodeRefException if the node could not be found
|
* @throws InvalidNodeRefException if the node could not be found
|
||||||
*/
|
*/
|
||||||
public List<Path> getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException;
|
public List<Path> getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the node where archived items will have gone when deleted from the given store.
|
||||||
|
*
|
||||||
|
* @param storeRef the store that items were deleted from
|
||||||
|
* @return Returns the archive node parent
|
||||||
|
*/
|
||||||
|
public NodeRef getStoreArchiveNode(StoreRef storeRef);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore an individual node (along with its sub-tree nodes) to the target location.
|
||||||
|
* The archived node must have the {@link org.alfresco.model.ContentModel#ASPECT_ARCHIVED archived aspect}
|
||||||
|
* set against it.
|
||||||
|
*
|
||||||
|
* @param archivedNodeRef the archived node
|
||||||
|
* @param targetParentNodeRef
|
||||||
|
* @param assocTypeQName
|
||||||
|
* @param assocQName
|
||||||
|
* @return Returns the reference to the newly created node
|
||||||
|
*/
|
||||||
|
public NodeRef restoreNode(
|
||||||
|
NodeRef archivedNodeRef,
|
||||||
|
NodeRef targetParentNodeRef,
|
||||||
|
QName assocTypeQName,
|
||||||
|
QName assocQName);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user