it.doqui.index.ecmengine.business.personalization.splitting.SplittingDbNodeServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for it.doqui.index.ecmengine.business.personalization.splitting.SplittingDbNodeServiceImpl.java

Source

/* Index ECM Engine - A system for managing the capture (when created
 * or received), classification (cataloguing), storage, retrieval,
 * revision, sharing, reuse and disposition of documents.
 *
 * Copyright (C) 2008 Regione Piemonte
 * Copyright (C) 2008 Provincia di Torino
 * Copyright (C) 2008 Comune di Torino
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

package it.doqui.index.ecmengine.business.personalization.splitting;

import it.doqui.index.ecmengine.business.personalization.splitting.util.SplittingNodeServiceConstants;
import it.doqui.index.ecmengine.util.EcmEngineModelConstants;

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.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.domain.ChildAssoc;
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.repo.node.AbstractNodeServiceImpl;
import org.alfresco.repo.node.StoreArchiveMap;
import org.alfresco.repo.node.db.DbNodeServiceImpl;
import org.alfresco.repo.node.db.NodeDaoService;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.InvalidAspectException;
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.AssociationExistsException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.CyclicChildRelationshipException;
import org.alfresco.service.cmr.repository.InvalidChildAssociationRefException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.InvalidStoreRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
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.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.util.Assert;

public class SplittingDbNodeServiceImpl extends AbstractNodeServiceImpl implements SplittingNodeService {
    private static Log logger = LogFactory.getLog(SplittingNodeServiceConstants.ECMENGINE_SPLITTING_LOG_CATEGORY);
    private static Log loggerPaths = LogFactory
            .getLog(SplittingNodeServiceConstants.ECMENGINE_SPLITTING_LOG_CATEGORY + ".paths");

    private NodeDaoService nodeDaoService;
    private StoreArchiveMap storeArchiveMap;
    private NodeService avmNodeService;
    private TenantService tenantService;
    private int partsCount;

    public void setPartsCount(int count) {
        this.partsCount = count;
    }

    public SplittingDbNodeServiceImpl() {
        logger.debug("[SplittingDbNodeServiceImpl::constructor] BEGIN");
        this.storeArchiveMap = new StoreArchiveMap(); // in case it is not set
        logger.debug("[SplittingDbNodeServiceImpl::constructor] END");
    }

    public void setNodeDaoService(NodeDaoService nodeDaoService) {
        this.nodeDaoService = nodeDaoService;
    }

    public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap) {
        this.storeArchiveMap = storeArchiveMap;
    }

    public void setAvmNodeService(NodeService avmNodeService) {
        this.avmNodeService = avmNodeService;
    }

    public void setTenantService(TenantService tenantService) {
        this.tenantService = tenantService;
    }

    //private Indexer indexer;
    //public void setIndexer(Indexer indexer) {
    //this.indexer = indexer;
    //}

    //private NodeIndexer nodeIndexer;
    /**
     * @param nodeIndexer       the indexer that will be notified of node additions,
     *                          modifications and deletions
     */
    //public void setNodeIndexer(NodeIndexer nodeIndexer)
    //{
    //this.nodeIndexer = nodeIndexer;
    //}

    /**
     * Performs a null-safe get of the node
     *
     * @param nodeRef the node to retrieve
     * @return Returns the node entity (never null)
     * @throws InvalidNodeRefException if the referenced node could not be found
     */
    private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getNodeNotNull] BEGIN");
        Node unchecked = null;

        try {
            ParameterCheck.mandatory("nodeRef", nodeRef);

            nodeRef = tenantService.getName(nodeRef);
            unchecked = nodeDaoService.getNode(nodeRef);

            if (unchecked == null) {
                throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getNodeNotNull] END");
        }
        return unchecked;
    }

    /**
     * Gets the node status for a live node.
     * @param nodeRef the node reference
     * @return Returns the node status, which will not be <tt>null</tt> and will have a live node attached.
     * @throws InvalidNodeRefException if the node is deleted or never existed
     */
    public NodeStatus getNodeStatusNotNull(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getNodeStatusNotNull] BEGIN");
        NodeStatus nodeStatus = null;

        try {
            ParameterCheck.mandatory("nodeRef", nodeRef);

            nodeRef = tenantService.getName(nodeRef);
            nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false);
            if (nodeStatus == null || nodeStatus.getNode() == null) {
                throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getNodeStatusNotNull] END");
        }
        return nodeStatus;
    }

    public boolean exists(StoreRef storeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::exists] BEGIN");
        try {
            storeRef = tenantService.getName(storeRef);
            Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier());

            return (store != null);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::exists] END");
        }
    }

    public boolean exists(NodeRef nodeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::exists] BEGIN");
        try {
            ParameterCheck.mandatory("nodeRef", nodeRef);

            nodeRef = tenantService.getName(nodeRef);
            Node node = nodeDaoService.getNode(nodeRef);
            boolean result = (node != null);

            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::exists] Node \"" + nodeRef + "\" exists: " + result);
            }
            return result;
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::exists] END");
        }
    }

    public Status getNodeStatus(NodeRef nodeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::getNodeStatus] BEGIN");

        try {
            ParameterCheck.mandatory("nodeRef", nodeRef);

            nodeRef = tenantService.getName(nodeRef);
            NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false);
            if (nodeStatus == null) { // node never existed
                if (logger.isDebugEnabled()) {
                    logger.debug("[SplittingDbNodeServiceImpl::getNodeStatus] Node without status: " + nodeRef);
                }
                return null;
            } else {
                return new NodeRef.Status(nodeStatus.getTransaction().getChangeTxnId(), nodeStatus.isDeleted());
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getNodeStatus] END");
        }
    }

    /**
     * @see NodeDaoService#getStores()
     */
    public List<StoreRef> getStores() {
        logger.debug("[SplittingDbNodeServiceImpl::getStores] BEGIN");
        final List<Store> stores = nodeDaoService.getStores();
        final List<StoreRef> avmStores = avmNodeService.getStores();
        List<StoreRef> storeRefs = new ArrayList<StoreRef>(stores.size() + avmStores.size());

        for (Store store : stores) {
            StoreRef storeRef = store.getStoreRef();
            try {
                if (tenantService.isEnabled()) {
                    tenantService.checkDomain(storeRef.getIdentifier());
                    storeRef = tenantService.getBaseName(storeRef);
                }
                storeRefs.add(storeRef);
            } catch (RuntimeException re) {
                // deliberately ignore - stores in different domain will not be listed
            }
        }
        storeRefs.addAll(avmStores);

        logger.debug("[SplittingDbNodeServiceImpl::getStores] END");
        return storeRefs;
    }

    /**
     * Defers to the typed service
     * @see StoreDaoService#createWorkspace(String)
     */
    public StoreRef createStore(String protocol, String identifier) {
        logger.debug("[SplittingDbNodeServiceImpl::createStore] BEGIN");
        StoreRef storeRef = tenantService.getName(new StoreRef(protocol, identifier));
        identifier = storeRef.getIdentifier();

        try {
            // check that the store does not already exist
            Store store = nodeDaoService.getStore(protocol, identifier);
            if (store != null) {
                throw new StoreExistsException("Unable to create a store that already exists: " + storeRef,
                        storeRef);
            }

            // invoke policies
            invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, storeRef);

            // create a new one
            store = nodeDaoService.createStore(protocol, identifier);

            // get the root node
            Node rootNode = store.getRootNode();

            // assign the root aspect - this is expected of all roots, even store roots
            addAspect(rootNode.getNodeRef(), ContentModel.ASPECT_ROOT, Collections.<QName, Serializable>emptyMap());

            // invoke policies
            invokeOnCreateStore(rootNode.getNodeRef());

            // done
            if (!store.getStoreRef().equals(storeRef)) {
                throw new RuntimeException("Incorrect store reference");
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::createStore] END");
        }
        storeRef = tenantService.getBaseName(storeRef);
        return storeRef;
    }

    /**
     * @see NodeDaoService#deleteStore(String, String)
     */
    public void deleteStore(StoreRef storeRef) {
        storeRef = tenantService.getName(storeRef);

        String protocol = storeRef.getProtocol();
        String identifier = storeRef.getIdentifier();

        // check that the store does exist
        Store store = nodeDaoService.getStore(protocol, identifier);
        if (store == null) {
            throw new InvalidStoreRefException("Unable to delete a store that does not exist: " + storeRef,
                    storeRef);
        }

        Node rootNode = store.getRootNode();

        QName nodeTypeQName = rootNode.getTypeQName();
        Set<QName> nodeAspectQNames = rootNode.getAspects();
        ChildAssociationRef assocRef = new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNode.getNodeRef(),
                null, rootNode.getNodeRef());
        invokeOnDeleteNode(assocRef, nodeTypeQName, nodeAspectQNames, false);
        nodeDaoService.deleteStore(protocol, identifier);
        return;
    }

    public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getRootNode] BEGIN");
        Node node = null;
        NodeRef nodeRef = null;

        try {
            storeRef = tenantService.getName(storeRef);
            Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier());
            if (store == null) {
                throw new InvalidStoreRefException("Store does not exist: " + storeRef, storeRef);
            }

            // get the root
            node = store.getRootNode();
            if (node == null) {
                throw new InvalidStoreRefException("Store does not have a root node: " + storeRef, storeRef);
            }
            nodeRef = tenantService.getBaseName(node.getNodeRef());
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getRootNode] END");
        }
        return nodeRef;
    }

    /**
     * @see #createNode(NodeRef, QName, QName, QName, Map)
     */
    public ChildAssociationRef createNode(NodeRef parentRef, QName assocTypeQName, QName assocQName,
            QName nodeTypeQName) {
        return createNode(parentRef, assocTypeQName, assocQName, nodeTypeQName, null);
    }

    /**
     * @see org.alfresco.service.cmr.repository.NodeService#createNode(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.util.Map)
     */
    public ChildAssociationRef createNode(NodeRef parentRef, QName assocTypeQName, QName assocQName,
            QName nodeTypeQName, Map<QName, Serializable> properties) {
        logger.debug("[SplittingDbNodeServiceImpl::createNode] BEGIN");

        final long start = System.currentTimeMillis();
        ChildAssociationRef childAssocRef = null;

        Assert.notNull(parentRef);
        Assert.notNull(assocTypeQName);
        Assert.notNull(assocQName);

        try {
            parentRef = tenantService.getName(parentRef);
            // Se il metodo e` richiamato su un nodo parte -> bug
            Node parentNode = getNodeNotNull(parentRef);
            Assert.isTrue(!isPart(parentNode), "BUG: createNode() with part as parent");

            // get the store that the parent belongs to
            StoreRef storeRef = parentRef.getStoreRef();
            Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier());
            if (store == null) {
                throw new RuntimeException("No store found for parent node: " + parentRef);
            }

            // null property map is allowed
            properties = (properties == null) ? new HashMap<QName, Serializable>()
                    : new HashMap<QName, Serializable>(properties);

            // Invoke policy behaviour
            invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName);

            // check the node type
            TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName);
            if (nodeTypeDef == null) {
                throw new InvalidTypeException(nodeTypeQName);
            }

            // BEGIN GESTIONE SPLITTING
            Node destinationNode = null;

            if (isSplitted(parentNode)) {

                // Il nodo e` splittato in piu` parti
                if (logger.isDebugEnabled()) {
                    logger.debug("[SplittingDbNodeServiceImpl::createNode] Detected splitted node: " + parentRef);
                }
                destinationNode = getPartByChildAssocName(parentNode, assocQName.getLocalName());

                if (destinationNode == null) {

                    // La parte non e` stata trovata e deve essere creata
                    destinationNode = createMissingPart(parentNode, assocQName.getLocalName());
                }
            } else {
                destinationNode = parentNode;
            }

            Assert.notNull(destinationNode, "BUG: destinationNode is null in createNode()!");
            // END GESTIONE SPLITTING

            // get/generate an ID for the node
            final String newId = generateGuid(properties);

            // create the node instance
            Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName);
            NodeRef childNodeRef = childNode.getNodeRef();

            // We now have enough to declare the child association creation
            invokeBeforeCreateChildAssociation(parentRef, childNodeRef, assocTypeQName, assocQName, true);

            // Create the association
            ChildAssoc childAssoc = nodeDaoService.newChildAssoc(destinationNode, childNode, true, assocTypeQName,
                    assocQName);
            ChildAssociationRef internalChildAssocRef = childAssoc.getChildAssocRef();
            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::createNode] Internal ChildAssociationRef: "
                        + internalChildAssocRef);
            }

            // Set the default property values
            addDefaultPropertyValues(nodeTypeDef, properties);

            // Add the default aspects to the node
            addDefaultAspects(nodeTypeDef, childNode, properties);

            // set the properties - it is a new node so only set properties if there are any
            Map<QName, Serializable> propertiesBefore = getPropertiesImpl(childNode);
            Map<QName, Serializable> propertiesAfter = null;
            if (!properties.isEmpty()) {
                propertiesAfter = setPropertiesImpl(childNode, properties);
            }

            setChildUniqueName(childNode); // ensure uniqueness
            childAssocRef = new ChildAssociationRef(assocTypeQName, parentRef, assocQName, childNodeRef, true, // Nuovo nodo -> associazione primaria
                    childAssoc.getIndex());
            if (logger.isDebugEnabled()) {
                logger.debug(
                        "[SplittingDbNodeServiceImpl::createNode] External ChildAssociationRef: " + childAssocRef);
            }

            // update the node status
            nodeDaoService.recordChangeId(childNodeRef);

            // Invoke policy behavior
            invokeOnCreateNode(childAssocRef);
            invokeOnCreateChildAssociation(childAssocRef, true);
            if (propertiesAfter != null) {
                invokeOnUpdateProperties(childNodeRef, propertiesBefore, propertiesAfter);
            }
        } finally {
            final long stop = System.currentTimeMillis();
            logger.debug("[SplittingDbNodeServiceImpl::createNode] Elapsed: " + (stop - start) + " ms");
            logger.debug("[SplittingDbNodeServiceImpl::createNode] END");
        }
        return childAssocRef;
    }

    /**
     * Add the default aspects to a given node
     *
     * @param nodeTypeDef
     */
    private void addDefaultAspects(ClassDefinition classDefinition, Node node,
            Map<QName, Serializable> properties) {
        logger.debug("[SplittingDbNodeServiceImpl::addDefaultAspects] BEGIN");
        // Se il nodo e` una parte -> bug
        Assert.isTrue(!isPart(node), "BUG: addDefaultAspects() on part");

        try {
            final boolean splitted = isSplitted(node);
            NodeRef nodeRef = node.getNodeRef();

            // get the mandatory aspects for the node type
            List<AspectDefinition> defaultAspectDefs = classDefinition.getDefaultAspects();

            // add all the aspects to the node
            Set<QName> nodeAspects = node.getAspects();
            for (AspectDefinition defaultAspectDef : defaultAspectDefs) {
                QName aspectTypeQName = defaultAspectDef.getName();
                invokeBeforeAddAspect(nodeRef, aspectTypeQName);
                nodeAspects.add(aspectTypeQName);
                addDefaultPropertyValues(defaultAspectDef, properties);
                invokeOnAddAspect(nodeRef, aspectTypeQName);

                if (defaultAspectDef.getName().equals(EcmEngineModelConstants.ASPECT_SPLITTED) && !splitted) {
                    splitNode(node); // per gestire lo splitting via mandatory aspect
                }

                invokeOnAddAspect(nodeRef, defaultAspectDef.getName());

                // Now add any default aspects for this aspect
                addDefaultAspects(defaultAspectDef, node, properties);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::addDefaultAspects] END");
        }
    }

    /**
     * Drops the old primary association and creates a new one
     */
    public ChildAssociationRef moveNode(NodeRef nodeToMoveRef, NodeRef newParentRef, QName assocTypeQName,
            QName assocQName) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::moveNode] BEGIN");
        Assert.notNull(nodeToMoveRef);
        Assert.notNull(newParentRef);
        Assert.notNull(assocTypeQName);
        Assert.notNull(assocQName);

        ChildAssociationRef newAssocRef = null;

        try {
            // check the node references
            Node nodeToMove = getNodeNotNull(nodeToMoveRef);
            Node newParentNode = getNodeNotNull(newParentRef);

            Assert.isTrue(!isPart(nodeToMove) && !isPart(newParentNode),
                    "BUG: moveNode() on one or more part node!");

            // get the primary parent association
            ChildAssoc oldAssoc = nodeDaoService.getPrimaryParentAssoc(nodeToMove);

            final NodeRef oldParentRef = getPartsContainerNode(oldAssoc.getParent()).getNodeRef();

            ChildAssociationRef oldAssocRef = new ChildAssociationRef(oldAssoc.getTypeQName(), oldParentRef,
                    oldAssoc.getQname(), nodeToMoveRef, oldAssoc.getIsPrimary(), oldAssoc.getIndex());

            Node destinationNode = null;

            if (isSplitted(newParentNode)) {
                // BEGIN GESTIONE SPLITTING

                Store store = nodeDaoService.getStore(newParentRef.getStoreRef().getProtocol(),
                        newParentRef.getStoreRef().getIdentifier());
                if (store == null) {
                    throw new RuntimeException("No store found for parent node: " + newParentRef);
                }

                destinationNode = getPartByChildAssocName(newParentNode, assocQName.getLocalName());

                if (destinationNode == null) {
                    destinationNode = createMissingPart(newParentNode, assocQName.getLocalName());
                }

                // END GESTIONE SPLITTING
            } else {
                destinationNode = newParentNode;
            }

            Assert.notNull(destinationNode, "BUG: destinationNode null in moveNode()!");

            boolean movingStore = !nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef());

            // data needed for policy invocation
            QName nodeToMoveTypeQName = nodeToMove.getTypeQName();
            Set<QName> nodeToMoveAspects = nodeToMove.getAspects();

            // Invoke policy behavior
            if (movingStore) {
                invokeBeforeDeleteNode(nodeToMoveRef);
                invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName);
            } else {
                invokeBeforeDeleteChildAssociation(oldAssocRef);
                invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName, false);
            }

            // remove the child assoc from the old parent
            // don't cascade as we will still need the node afterwards
            nodeDaoService.deleteChildAssoc(oldAssoc, false);

            // create a new assoc
            ChildAssoc newAssoc = nodeDaoService.newChildAssoc(destinationNode, // Nodo o parte
                    nodeToMove, true, assocTypeQName, assocQName);

            setChildUniqueName(nodeToMove); // ensure uniqueness

            newAssocRef = new ChildAssociationRef(newAssoc.getTypeQName(), newParentRef, newAssoc.getQname(),
                    nodeToMoveRef, newAssoc.getIsPrimary(), newAssoc.getIndex());

            // If the node is moving stores, then drag the node hierarchy with it
            if (movingStore) {

                // do the move
                Store newStore = newParentNode.getStore();
                moveNodeToStore(nodeToMove, newStore);

                // the node reference will have changed too
                nodeToMoveRef = nodeToMove.getNodeRef();
            }

            // check that no cyclic relationships have been created
            getPaths(nodeToMoveRef, false);

            // invoke policy behavior
            if (movingStore) {
                /*
                 * TODO for now indicate that the node has been archived to prevent the version
                 * history from being removed in the future a onMove policy could be added and
                 * remove the need for onDelete and onCreate to be fired here
                 */
                invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects, true);
                invokeOnCreateNode(newAssocRef);
            } else {
                invokeOnCreateChildAssociation(newAssocRef, false);
                invokeOnDeleteChildAssociation(oldAssocRef);
            }
            invokeOnMoveNode(oldAssocRef, newAssocRef);

            // update the node status
            nodeDaoService.recordChangeId(nodeToMoveRef);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::moveNode] END");
        }

        // done
        return newAssocRef;
    }

    public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index) {
        logger.debug("[SplittingDbNodeServiceImpl::setChildAssociationIndex] BEGIN");

        try {
            // Se uno dei due nodi e` una parte -> bug
            Node parentNode = getNodeNotNull(childAssocRef.getParentRef());
            Node childNode = getNodeNotNull(childAssocRef.getChildRef());

            Assert.isTrue(!isPart(parentNode) && !isPart(childNode),
                    "BUG: ChildAssociationRef not correctly translated!");

            Node partNode = null;

            if (isSplitted(parentNode)) {
                final QName partQName = QName.createQName(EcmEngineModelConstants.ECMENGINE_SYS_MODEL_URI,
                        getPartName(childAssocRef.getQName().getLocalName()));

                Collection<ChildAssociationRef> partAssocs = nodeDaoService.getChildAssocRefs(parentNode,
                        partQName);

                for (ChildAssociationRef partAssoc : partAssocs) {
                    partNode = getNodeNotNull(partAssoc.getChildRef());
                    break; // Consideriamo un solo risultato - le parti devono avere nomi distinti
                }

                if (partNode == null) {
                    throw new InvalidChildAssociationRefException("Unable to find part for association "
                            + "while setting index: " + "\n   assoc: " + childAssocRef + "\n   index: " + index,
                            childAssocRef);
                }
            }

            ChildAssoc assoc = nodeDaoService.getChildAssoc((partNode == null) ? parentNode : partNode, childNode,
                    childAssocRef.getTypeQName(), childAssocRef.getQName());
            if (assoc == null) {
                throw new InvalidChildAssociationRefException("Unable to set child association index: "
                        + "\n   assoc: " + childAssocRef + "\n   index: " + index, childAssocRef);
            }

            // set the index
            assoc.setIndex(index);

            // flush
            nodeDaoService.flush();
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setChildAssociationIndex] END");
        }
    }

    public QName getType(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getType] BEGIN");
        QName typeQName = null;
        try {
            Node node = getNodeNotNull(nodeRef);

            Assert.isTrue(!isPart(node), "BUG: getType() on part node!");
            typeQName = node.getTypeQName();
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getType] END");
        }
        return typeQName;
    }

    /**
     * @see org.alfresco.service.cmr.repository.NodeService#setType(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void setType(NodeRef nodeRef, QName typeQName) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::setType] BEGIN");

        try {
            // check the node type
            TypeDefinition nodeTypeDef = dictionaryService.getType(typeQName);
            if (nodeTypeDef == null) {
                throw new InvalidTypeException(typeQName);
            }

            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(isPart(node), "BUG: setType() su nodo parte");

            // Invoke policies
            invokeBeforeUpdateNode(nodeRef);

            // Get the node and set the new type
            node.setTypeQName(typeQName);

            // Add the default aspects to the node (update the properties with any new default values)
            Map<QName, Serializable> properties = this.getPropertiesImpl(node);
            addDefaultAspects(nodeTypeDef, node, properties);
            this.setProperties(nodeRef, properties);

            // Invoke policies
            invokeOnUpdateNode(nodeRef);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setType] END");
        }
    }

    /**
     * @see Node#getAspects()
     */
    public void addAspect(NodeRef nodeRef, QName aspectTypeQName, Map<QName, Serializable> aspectProperties)
            throws InvalidNodeRefException, InvalidAspectException {
        logger.debug("[SplittingDbNodeServiceImpl::addAspect] BEGIN");

        try {
            nodeRef = tenantService.getName(nodeRef);
            // check that the aspect is legal
            AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
            if (aspectDef == null) {
                throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName);
            }

            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: addAspect() on part node");

            final boolean splitted = isSplitted(node);

            // Invoke policy behaviors
            invokeBeforeUpdateNode(nodeRef);
            invokeBeforeAddAspect(nodeRef, aspectTypeQName);

            // attach the properties to the current node properties
            Map<QName, Serializable> nodeProperties = getPropertiesImpl(node);

            if (aspectProperties != null) {
                nodeProperties.putAll(aspectProperties);
            }

            // Set any default property values that appear on the aspect
            addDefaultPropertyValues(aspectDef, nodeProperties);

            // Add any dependent aspect
            addDefaultAspects(aspectDef, node, nodeProperties);

            // Set the property values back on the node
            setProperties(nodeRef, nodeProperties);

            // Gestione splitting
            if (aspectTypeQName.equals(EcmEngineModelConstants.ASPECT_SPLITTED) && !splitted) {
                splitNode(node);
            }

            // physically attach the aspect to the node
            if (node.getAspects().add(aspectTypeQName) == true) {
                // Invoke policy behaviors
                invokeOnUpdateNode(nodeRef);
                invokeOnAddAspect(nodeRef, aspectTypeQName);

                // update the node status
                nodeDaoService.recordChangeId(nodeRef);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::addAspect] END");
        }
    }

    /**
     * @see Node#getAspects()
     */
    public void removeAspect(NodeRef nodeRef, QName aspectTypeQName)
            throws InvalidNodeRefException, InvalidAspectException {
        logger.debug("[SplittingDbNodeServiceImpl::removeAspect] BEGIN");

        try {
            // get the aspect
            AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
            if (aspectDef == null) {
                throw new InvalidAspectException(aspectTypeQName);
            }

            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: removeAspect() on part node");

            // Impedire la rimozione dell'aspect "ecm-sys:splitted"
            if (aspectTypeQName.equals(EcmEngineModelConstants.ASPECT_SPLITTED)) {
                logger.debug(
                        "[SplittingDbNodeServiceImpl::removeAspect] Ignored request to remove ecm-sys:splitted aspect");
                return;
            }

            Set<QName> nodeAspects = node.getAspects();

            if (!nodeAspects.contains(aspectTypeQName)) {

                // The aspect isn't present so just leave it
                return;
            }

            // Invoke policy behaviors
            invokeBeforeUpdateNode(nodeRef);
            invokeBeforeRemoveAspect(nodeRef, aspectTypeQName);

            // remove the aspect, if present
            node.getAspects().remove(aspectTypeQName);

            Map<QName, PropertyValue> nodeProperties = node.getProperties();
            Map<QName, PropertyDefinition> propertyDefs = aspectDef.getProperties();
            for (QName propertyName : propertyDefs.keySet()) {
                nodeProperties.remove(propertyName);
            }

            // Remove child associations
            Map<QName, ChildAssociationDefinition> childAssocDefs = aspectDef.getChildAssociations();

            List<Node> parts = null;

            if (!childAssocDefs.isEmpty()) {

                if (isSplitted(node)) {
                    parts = getPartNodes(node);
                } else {
                    parts = new ArrayList<Node>(1);
                    parts.add(node);
                }
            } else {
                parts = Collections.<Node>emptyList();
            }

            for (Node part : parts) {
                Collection<ChildAssoc> childAssocs = nodeDaoService.getChildAssocs(part);
                for (ChildAssoc childAssoc : childAssocs) {

                    // Ignore if the association type is not defined by the aspect
                    QName childAssocQName = childAssoc.getTypeQName();
                    if (!childAssocDefs.containsKey(childAssocQName)) {
                        continue;
                    }

                    // The association is of a type that should be removed
                    nodeDaoService.deleteChildAssoc(childAssoc, true);
                }
            }

            // Remove regular associations
            Map<QName, AssociationDefinition> assocDefs = aspectDef.getAssociations();

            if (!assocDefs.isEmpty()) {
                List<NodeAssoc> nodeAssocs = nodeDaoService.getTargetNodeAssocs(node);
                for (NodeAssoc nodeAssoc : nodeAssocs) {

                    // Ignore if the association type is not defined by the aspect
                    QName nodeAssocQName = nodeAssoc.getTypeQName();
                    if (!assocDefs.containsKey(nodeAssocQName)) {
                        continue;
                    }

                    // Delete the association
                    nodeDaoService.deleteNodeAssoc(nodeAssoc);
                }
            }

            // Invoke policy behaviors
            invokeOnUpdateNode(nodeRef);
            invokeOnRemoveAspect(nodeRef, aspectTypeQName);

            // update the node status
            nodeDaoService.recordChangeId(nodeRef);

        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::removeAspect] END");
        }
    }

    /**
     * Performs a check on the set of node aspects
     *
     * @see Node#getAspects()
     */
    public boolean hasAspect(NodeRef nodeRef, QName aspectRef)
            throws InvalidNodeRefException, InvalidAspectException {
        logger.debug("[SplittingDbNodeServiceImpl::hasAspect] BEGIN");
        try {
            // Se e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: hasAspect() on part node!");

            Set<QName> aspectQNames = node.getAspects();

            return aspectQNames.contains(aspectRef);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::hasAspect] END");
        }
    }

    public Set<QName> getAspects(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getAspects] BEGIN");
        Set<QName> ret = null;

        try {
            // Se e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getAspects() on part node!");

            Set<QName> aspectQNames = node.getAspects();

            // copy the set to ensure initialization
            ret = new HashSet<QName>(aspectQNames.size());
            ret.addAll(aspectQNames);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getAspects] END");
        }
        return ret;
    }

    public void deleteNode(NodeRef nodeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::deleteNode] BEGIN");

        try {
            nodeRef = tenantService.getName(nodeRef);
            // First get the node to ensure that it exists
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: deleteNode() on part node!");

            boolean requiresDelete = false;

            // Invoke policy behaviors
            invokeBeforeDeleteNode(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
            final QName nodeTypeQName = node.getTypeQName();
            final Set<QName> nodeAspectQNames = node.getAspects();

            // check if we need to archive the node
            StoreRef archiveStoreRef = null;
            if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY)
                    || nodeAspectQNames.contains(ContentModel.ASPECT_WORKING_COPY)) {
                // the node has the temporary aspect meaning
                // it can not be archived
                requiresDelete = true;
            } else {
                StoreRef storeRef = nodeRef.getStoreRef();

                // remove tenant domain - to retrieve archive store from map
                final StoreRef baseStoreRef = tenantService.getBaseName(storeRef);

                archiveStoreRef = storeArchiveMap.getArchiveMap().get(baseStoreRef);

                // get the type and check if we need archiving
                final TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName());

                requiresDelete = (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null);
                if (logger.isDebugEnabled()) {
                    logger.debug("[SplittingDbNodeServiceImpl::deleteNode] typeDef = " + typeDef);
                    if (typeDef != null) {
                        logger.debug("[SplittingDbNodeServiceImpl::deleteNode] typeDef.isArchive() = "
                                + typeDef.isArchive());
                    }
                    logger.debug("[SplittingDbNodeServiceImpl::deleteNode] archiveStoreRef = " + archiveStoreRef);
                    logger.debug("[SplittingDbNodeServiceImpl::deleteNode] requiresDelete = " + requiresDelete);
                }
            }

            if (requiresDelete) {

                // perform a normal deletion
                nodeDaoService.deleteNode(node, true);

                // Invoke policy behaviors
                invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, false);
            } else {
                archiveStoreRef = tenantService.getName(archiveStoreRef);

                // archive it
                archiveNode(nodeRef, archiveStoreRef);
                // The archive performs a move, which will fire the appropriate OnDeleteNode
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::deleteNode] END");
        }
    }

    public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName,
            QName assocQName) {
        logger.debug("[SplittingDbNodeServiceImpl::addChild] BEGIN");
        ChildAssociationRef assocRef = null;

        try {
            Node parentNode = getNodeNotNull(parentRef);
            Node childNode = getNodeNotNull(childRef);

            Assert.isTrue(!isPart(parentNode) && !isPart(childNode), "BUG: addChild() on one or more part nodes!");

            // Invoke policy behaviors
            invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false);

            Node destinationNode = null;

            if (isSplitted(parentNode)) {
                destinationNode = getPartByChildAssocName(parentNode, assocQName.getLocalName());

                if (destinationNode == null) {
                    destinationNode = createMissingPart(parentNode, assocQName.getLocalName());
                }
            } else {
                destinationNode = parentNode;
            }

            Assert.notNull(destinationNode, "BUG: null destination node in addChild()!");

            // make the association
            ChildAssoc assoc = nodeDaoService.newChildAssoc(destinationNode, // Nodo o parte
                    childNode, false, assocTypeQName, assocQName);
            // ensure name uniqueness
            setChildUniqueName(childNode);

            assocRef = new ChildAssociationRef(assoc.getTypeQName(), parentRef, assoc.getQname(), childRef,
                    assoc.getIsPrimary(), assoc.getIndex());

            // check that the child addition of the child has not created a cyclic relationship
            // this functionality is provided for free in getPath
            getPaths(childRef, false);

            // update the node status (backported for CSI from 20071004 HEAD)
            nodeDaoService.recordChangeId(childRef);

            // Invoke policy behaviors
            invokeOnCreateChildAssociation(assocRef, false);

        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::addChild] END");
        }
        return assocRef;
    }

    public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::removeChild] BEGIN");

        try {
            Node parentNode = getNodeNotNull(parentRef);
            Node childNode = getNodeNotNull(childRef);

            Assert.isTrue(!isPart(parentNode) && !isPart(childNode),
                    "BUG: removeChild() on one or more part nodes!");

            Long childNodeId = childNode.getId();

            List<Node> parts = null;

            if (isSplitted(parentNode)) {
                parts = getPartNodes(parentNode);
            } else {
                parts = new ArrayList<Node>(1);
                parts.add(parentNode);
            }

            ChildAssociationRef primaryAssocRef = null;

            for (Node part : parts) {
                // get all the child associations
                Collection<ChildAssoc> assocs = nodeDaoService.getChildAssocs(part);

                assocs = new HashSet<ChildAssoc>(assocs); // copy set as we will be modifying it
                for (ChildAssoc assoc : assocs) {

                    if (!assoc.getChild().getId().equals(childNodeId)) {
                        continue; // not a matching association
                    }

                    ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(), parentRef,
                            assoc.getQname(), childRef, assoc.getIsPrimary(), assoc.getIndex());

                    // Is this a primary association?
                    if (assoc.getIsPrimary()) {

                        // keep the primary association for last
                        primaryAssocRef = assocRef;
                    } else {

                        // delete the association instance - it is not primary
                        invokeBeforeDeleteChildAssociation(assocRef);
                        nodeDaoService.deleteChildAssoc(assoc, true); // cascade
                        invokeOnDeleteChildAssociation(assocRef);
                    }
                }
            }

            // remove the child if the primary association was a match
            if (primaryAssocRef != null) {
                deleteNode(primaryAssocRef.getChildRef());
            } else {
                /*
                 * The cascade delete will update the node status, but just a plain
                 * association deletion will not.
                 * Update the node status (backported for CSI from 20071004 HEAD)
                 */
                nodeDaoService.recordChangeId(childRef);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::removeChild] END");
        }
    }

    public boolean removeChildAssociation(ChildAssociationRef childAssocRef) {
        logger.debug("[SplittingDbNodeServiceImpl::removeChildAssociation] BEGIN");
        boolean deleted = false;

        try {
            Node parentNode = getNodeNotNull(childAssocRef.getParentRef());
            Node childNode = getNodeNotNull(childAssocRef.getChildRef());

            Assert.isTrue(!isPart(parentNode) && !isPart(childNode),
                    "BUG: removeChildAssociation() on one or more part nodes!");

            final QName typeQName = childAssocRef.getTypeQName();
            final QName qname = childAssocRef.getQName();

            Node realParentNode = (isSplitted(parentNode))
                    ? getPartByChildAssocName(parentNode, qname.getLocalName())
                    : parentNode;

            Assert.notNull(realParentNode, "BUG: part of child to remove not found!");

            // Delete the association
            invokeBeforeDeleteChildAssociation(childAssocRef);
            deleted = nodeDaoService.deleteChildAssoc(realParentNode, childNode, typeQName, qname);

            if (deleted) {

                invokeOnDeleteChildAssociation(childAssocRef);

                // Update the node status (backported for CSI from 20071004 HEAD)
                nodeDaoService.recordChangeId(childNode.getNodeRef());
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::removeChildAssociation] END");
        }
        return deleted;
    }

    public boolean removeSeconaryChildAssociation(ChildAssociationRef childAssocRef) {
        logger.debug("[SplittingDbNodeServiceImpl::removeSeconaryChildAssociation] BEGIN");

        try {
            Node parentNode = getNodeNotNull(childAssocRef.getParentRef());
            Node childNode = getNodeNotNull(childAssocRef.getChildRef());

            Assert.isTrue(!isPart(parentNode) && !isPart(childNode),
                    "BUG: removeSeconaryChildAssociation() on one or more part nodes!");

            final QName typeQName = childAssocRef.getTypeQName();
            final QName qname = childAssocRef.getQName();

            Node realParentNode = (isSplitted(parentNode))
                    ? getPartByChildAssocName(parentNode, qname.getLocalName())
                    : parentNode;

            Assert.notNull(realParentNode, "BUG: part of child to remove not found!");

            ChildAssoc assoc = nodeDaoService.getChildAssoc(realParentNode, childNode, typeQName, qname);
            if (assoc == null) {

                // No association exists
                return false;
            }

            if (assoc.getIsPrimary()) {
                throw new IllegalArgumentException(
                        "removeSeconaryChildAssociation can not be applied to a primary association: \n"
                                + "   Child Assoc: " + assoc);
            }

            // Delete the secondary association
            nodeDaoService.deleteChildAssoc(assoc, false);
            invokeOnDeleteChildAssociation(childAssocRef);

            // Update the node status (backported for CSI from 20071004 HEAD)
            nodeDaoService.recordChangeId(childNode.getNodeRef());
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::removeSeconaryChildAssociation] END");
        }
        return true;
    }

    /**
     * Remove properties that should not be persisted as general properties.  Where necessary, the
     * properties are set on the node.
     *
     * @param node the node to set properties on
     * @param properties properties to change
     */
    private void extractIntrinsicProperties(Node node, Map<QName, Serializable> properties) {
        logger.debug("[SplittingDbNodeServiceImpl::extractIntrinsicProperties] BEGIN");
        properties.remove(ContentModel.PROP_STORE_PROTOCOL);
        properties.remove(ContentModel.PROP_STORE_IDENTIFIER);
        properties.remove(ContentModel.PROP_NODE_UUID);
        properties.remove(ContentModel.PROP_NODE_DBID);
        logger.debug("[SplittingDbNodeServiceImpl::extractIntrinsicProperties] END");
    }

    /**
     * Adds all properties used by the
     * {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}.
     * <p>
     * This method can be used to ensure that the values used by the aspect
     * are present as node properties.
     * <p>
     * This method also ensures that the {@link ContentModel#PROP_NAME name property}
     * is always present as a property on a node.
     *
     * @param node the node with the values
     * @param nodeRef the node reference containing the values required
     * @param properties the node properties
     */
    private void addIntrinsicProperties(Node node, Map<QName, Serializable> properties) {
        logger.debug("[SplittingDbNodeServiceImpl::addIntrinsicProperties] BEGIN");

        final NodeRef nodeRef = tenantService.getBaseName(node.getNodeRef());

        properties.put(ContentModel.PROP_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol());
        properties.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier());
        properties.put(ContentModel.PROP_NODE_UUID, nodeRef.getId());
        properties.put(ContentModel.PROP_NODE_DBID, node.getId());

        // add the ID as the name, if required
        if (properties.get(ContentModel.PROP_NAME) == null) {
            properties.put(ContentModel.PROP_NAME, nodeRef.getId());
        }
        logger.debug("[SplittingDbNodeServiceImpl::addIntrinsicProperties] END");
    }

    public Map<QName, Serializable> getProperties(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getProperties] BEGIN");

        try {
            nodeRef = tenantService.getName(nodeRef);
            Node node = getNodeNotNull(nodeRef);

            return getPropertiesImpl(node);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getProperties] END");
        }
    }

    private Map<QName, Serializable> getPropertiesImpl(Node node) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getPropertiesImpl] BEGIN");
        Map<QName, Serializable> ret = null;

        try {
            // Se il nodo e` una parte -> bug
            Assert.isTrue(!isPart(node), "BUG: getPropertiesImpl() on part node!");

            Map<QName, PropertyDefinition> propDefs = dictionaryService.getPropertyDefs(node.getTypeQName());

            Map<QName, PropertyValue> nodeProperties = node.getProperties();
            ret = new HashMap<QName, Serializable>(nodeProperties.size());

            // copy values
            for (Map.Entry<QName, PropertyValue> entry : nodeProperties.entrySet()) {
                QName propertyQName = entry.getKey();
                PropertyValue propertyValue = entry.getValue();

                if (propDefs != null) {
                    // get the property definition
                    PropertyDefinition propertyDef = propDefs.get(propertyQName);

                    if ((propertyDef != null)
                            && (propertyDef.getDataType().getName().equals(DataTypeDefinition.NODE_REF))
                            && (propertyValue != null) && (propertyValue.getStringValue() != null)) {
                        propertyValue.setStringValue(
                                tenantService.getBaseName(new NodeRef(propertyValue.getStringValue())).toString());
                    }

                    // convert to the correct type
                    Serializable value = makeSerializableValue(propertyDef, propertyValue);

                    // copy across
                    ret.put(propertyQName, value);
                }
            }

            // spoof referenceable properties
            addIntrinsicProperties(node, ret);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getPropertiesImpl] END");
        }
        return ret;
    }

    public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getProperty] BEGIN");

        try {
            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getProperty() on part node!");

            // spoof referencable properties
            if (qname.equals(ContentModel.PROP_STORE_PROTOCOL)) {
                return nodeRef.getStoreRef().getProtocol();
            } else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER)) {
                return nodeRef.getStoreRef().getIdentifier();
            } else if (qname.equals(ContentModel.PROP_NODE_UUID)) {
                return nodeRef.getId();
            } else if (qname.equals(ContentModel.PROP_NODE_DBID)) {
                return node.getId();
            }

            Map<QName, PropertyValue> properties = node.getProperties();
            PropertyValue propertyValue = properties.get(qname);

            // check if we need to provide a spoofed name
            if (propertyValue == null && qname.equals(ContentModel.PROP_NAME)) {
                return nodeRef.getId();
            }

            // get the property definition
            PropertyDefinition propertyDef = dictionaryService.getProperty(qname);

            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.NODE_REF))
                    && (propertyValue != null) && (propertyValue.getStringValue() != null)) {
                propertyValue.setStringValue(
                        tenantService.getBaseName(new NodeRef(propertyValue.getStringValue())).toString());
            }

            // convert to the correct type and return
            return makeSerializableValue(propertyDef, propertyValue);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getProperty] END");
        }
    }

    /**
     * Ensures that all required properties are present on the node and copies the
     * property values to the <code>Node</code>.
     * <p>
     * To remove a property, <b>remove it from the map</b> before calling this method.
     * Null-valued properties are allowed.
     * <p>
     * If any of the values are null, a marker object is put in to mimic nulls.  They will be turned back into
     * a real nulls when the properties are requested again.
     *
     * @see Node#getProperties()
     */
    public void setProperties(NodeRef nodeRef, Map<QName, Serializable> properties) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::setProperties] BEGIN");

        try {
            Node node = getNodeNotNull(nodeRef);

            // Invoke policy behaviours
            invokeBeforeUpdateNode(nodeRef);

            // Do the set properties
            Map<QName, Serializable> propertiesBefore = getPropertiesImpl(node);
            Map<QName, Serializable> propertiesAfter = setPropertiesImpl(node, properties);

            setChildUniqueName(node); // ensure uniqueness

            // Invoke policy behaviours
            invokeOnUpdateNode(nodeRef);
            invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setProperties] END");
        }
    }

    /**
     * Does the work of setting the property values.  Returns a map containing the state of the properties after the set
     * operation is complete.
     *
     * @param node              the node
     * @param properties        the map of property values
     * @return                  the map of property values after the set operation is complete
     * @throws InvalidNodeRefException
     */
    private Map<QName, Serializable> setPropertiesImpl(Node node, Map<QName, Serializable> properties)
            throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::setPropertiesImpl] BEGIN");

        try {
            ParameterCheck.mandatory("properties", properties);

            // Se il nodo e` una parte -> bug
            Assert.isTrue(!isPart(node), "BUG: setPropertiesImpl() on part node!");

            // remove referencable properties
            extractIntrinsicProperties(node, properties);

            // copy properties onto node
            Map<QName, PropertyValue> nodeProperties = node.getProperties();
            nodeProperties.clear();

            // check the property type and copy the values across
            for (Map.Entry<QName, Serializable> property : properties.entrySet()) {
                PropertyDefinition propertyDef = dictionaryService.getProperty(property.getKey());

                // get a persistable value
                PropertyValue propertyValue = makePropertyValue(propertyDef, property.getValue());
                nodeProperties.put(property.getKey(), propertyValue);
            }

            // update the node status
            NodeRef nodeRef = node.getNodeRef();
            nodeDaoService.recordChangeId(nodeRef);

            // Return the properties after
            return Collections.unmodifiableMap(properties);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setPropertiesImpl] END");
        }
    }

    /**
     * Gets the properties map, sets the value (null is allowed) and checks that the new set
     * of properties is valid.
     *
     * @see DbNodeServiceImpl.NullPropertyValue
     */
    public void setProperty(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::setProperty] BEGIN");

        try {
            Assert.notNull(qname);

            nodeRef = tenantService.getName(nodeRef);
            Node node = getNodeNotNull(nodeRef);

            // Invoke policy behaviors
            invokeBeforeUpdateNode(nodeRef);

            // Do the set operation
            Map<QName, Serializable> propertiesBefore = getPropertiesImpl(node);
            Map<QName, Serializable> propertiesAfter = setPropertyImpl(node, qname, value);

            if (qname.equals(ContentModel.PROP_NAME)) {
                setChildUniqueName(node); // ensure uniqueness
            }

            // Invoke policy behaviors
            invokeOnUpdateNode(nodeRef);
            invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setProperty] END");
        }
    }

    /**
     * Does the work of setting a property value.  Returns the values of the properties after the set operation is
     * complete.
     *
     * @param node          the node
     * @param qname         the qname of the property
     * @param value         the value of the property
     * @return              the values of the properties after the set operation is complete
     * @throws InvalidNodeRefException
     */
    private Map<QName, Serializable> setPropertyImpl(Node node, QName qname, Serializable value)
            throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::setPropertyImpl] BEGIN");

        try {
            // Se il nodo e` una parte -> bug
            Assert.isTrue(!isPart(node), "BUG: setPropertyImpl() on part node!");

            NodeRef nodeRef = node.getNodeRef();

            Map<QName, PropertyValue> properties = node.getProperties();
            PropertyDefinition propertyDef = dictionaryService.getProperty(qname);

            // get a persistable value
            PropertyValue propertyValue = makePropertyValue(propertyDef, value);
            properties.put(qname, propertyValue);

            // update the node status
            nodeDaoService.recordChangeId(nodeRef);

            return getPropertiesImpl(node);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setPropertyImpl] END");
        }
    }

    public void removeProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::removeProperty] BEGIN");

        try {
            if (qname.equals(ContentModel.PROP_NAME)) {
                throw new UnsupportedOperationException(
                        "The property " + qname + " may not be removed individually");
            }

            nodeRef = tenantService.getName(nodeRef);
            Node node = getNodeNotNull(nodeRef);

            // Invoke policy behaviors
            invokeBeforeUpdateNode(nodeRef);

            // Get the values before
            Map<QName, Serializable> propertiesBefore = getPropertiesImpl(node);

            // Remove the property
            Map<QName, PropertyValue> properties = node.getProperties();
            properties.remove(qname);

            // Get the values afterwards
            Map<QName, Serializable> propertiesAfter = getPropertiesImpl(node);

            // Invoke policy behaviours
            invokeOnUpdateNode(nodeRef);
            invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::removeProperty] END");
        }
    }

    /**
     * Transforms {@link Node#getParentAssocs()} to a new collection
     */
    public Collection<NodeRef> getParents(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getParents] BEGIN");
        Collection<NodeRef> results = null;

        try {
            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getParents() on part node!");

            // get the assocs pointing to it
            Collection<ChildAssoc> parentAssocs = nodeDaoService.getParentAssocs(node);

            // list of results
            results = new ArrayList<NodeRef>(parentAssocs.size());
            for (ChildAssoc assoc : parentAssocs) {

                // get the parent
                results.add(tenantService.getBaseName(assoc.getParent().getNodeRef()));
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getParents] END");
        }
        return results;
    }

    /**
     * Restituisce le associazioni ai padri del nodo dato filtrandole per tipo e nome completo.
     *
     * @param nodeRef Il riferimento al nodo di cui richiedere le associazioni.
     * @param typeQNamePattern Il pattern dei nomi completi di tipo di associazione da accettare.
     * @param qnamePattern Il pattern dei nomi completi di associazione da accettare.
     *
     * @return Una lista di riferimenti alle associazioni che soddisfano i filtri.
     */
    public List<ChildAssociationRef> getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern,
            QNamePattern qnamePattern) {
        logger.debug("[SplittingDbNodeServiceImpl::getParentAssocs] BEGIN");
        List<ChildAssociationRef> results = null;

        try {
            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getParentAssocs() on part node!");

            // get the associations pointing to it
            Collection<ChildAssoc> parentAssocs = nodeDaoService.getParentAssocs(node);

            // shortcut if there are no associations
            if (parentAssocs.size() == 0) {
                return Collections.<ChildAssociationRef>emptyList();
            }

            // list of results
            results = new ArrayList<ChildAssociationRef>(parentAssocs.size());
            for (ChildAssoc assoc : parentAssocs) {

                // does the qname match the pattern?
                if (!qnamePattern.isMatch(assoc.getQname()) || !typeQNamePattern.isMatch(assoc.getTypeQName())) {

                    // no match - ignore
                    continue;
                }

                if (isPart(assoc.getParent())) {

                    // Il parent e` una parte, e` necessario risalire al contenitore...
                    Node realParent = getPartsContainerNode(assoc.getParent());

                    // Aggiunta dell'associazione tradotta ai risultati
                    results.add(new ChildAssociationRef(assoc.getTypeQName(),
                            tenantService.getBaseName(realParent.getNodeRef()), assoc.getQname(),
                            tenantService.getBaseName(nodeRef), assoc.getIsPrimary(), assoc.getIndex()));
                } else {
                    ChildAssociationRef childAssocRef = new ChildAssociationRef(
                            assoc.getChildAssocRef().getTypeQName(),
                            tenantService.getBaseName(assoc.getChildAssocRef().getParentRef()),
                            assoc.getChildAssocRef().getQName(),
                            tenantService.getBaseName(assoc.getChildAssocRef().getChildRef()),
                            assoc.getChildAssocRef().isPrimary(), assoc.getChildAssocRef().getNthSibling());
                    results.add(childAssocRef);
                }
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getParentAssocs] END");
        }
        return results;
    }

    /**
     * Restituisce le associazioni ai figli del nodo dato filtrandole per tipo e nome completo.
     *
     * @param nodeRef Il riferimento al nodo di cui richiedere le associazioni.
     * @param typeQNamePattern Il pattern dei nomi completi di tipo di associazione da accettare.
     * @param qnamePattern Il pattern dei nomi completi di associazione da accettare.
     *
     * @return Una lista di riferimenti alle associazioni che soddisfano i filtri.
     */
    public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern,
            QNamePattern qnamePattern) {
        logger.debug("[SplittingDbNodeServiceImpl::getChildAssocs] BEGIN");

        try {
            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getChildAssocs() on part node!");

            Collection<ChildAssociationRef> childAssocRefs = null;

            // if the type is the wildcard type, and the qname is not a search, then use a shortcut query
            if (typeQNamePattern.equals(RegexQNamePattern.MATCH_ALL) && (qnamePattern instanceof QName)) {

                // Ricerca per un figlio di un nome preciso -> ottimizzazione sulla parte associata
                final QName childQName = (QName) qnamePattern;
                Node part = null;
                if (isSplitted(node)) {
                    part = getPartByChildAssocName(node, childQName.getLocalName());

                    if (part == null) { // Non c'e` la parte che dovrebbe contenere il figlio cercato
                        return Collections.<ChildAssociationRef>emptyList();
                    }
                } else {
                    part = node;
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("[SplittingDbNodeServiceImpl::getChildAssocs] " + "Ricerca limitata alla parte "
                            + part.getNodeRef());
                }

                // get all child associations with the specific qualified name
                childAssocRefs = nodeDaoService.getChildAssocRefs(part, childQName);
            } else {

                List<Node> parts;

                if (isSplitted(node)) {
                    parts = getPartNodes(node);
                } else {
                    parts = new ArrayList<Node>(1);
                    parts.add(node);
                }

                childAssocRefs = new ArrayList<ChildAssociationRef>();

                for (Node part : parts) {
                    // get all child associations
                    Collection<ChildAssociationRef> partialResult = nodeDaoService.getChildAssocRefs(part);
                    if (logger.isDebugEnabled()) {
                        logger.debug("[SplittingDbNodeServiceImpl::getChildAssocs] " + "Found "
                                + partialResult.size() + " children in part: " + part.getNodeRef());
                    }

                    // remove non-matching associations
                    Iterator<ChildAssociationRef> iter = partialResult.iterator();
                    while (iter.hasNext()) {

                        ChildAssociationRef child = iter.next();

                        // does the qname match the pattern?
                        if (!qnamePattern.isMatch(child.getQName())
                                || !typeQNamePattern.isMatch(child.getTypeQName())) {

                            // no match - remove
                            iter.remove();
                        }
                    }

                    // add to results
                    childAssocRefs.addAll(partialResult);
                }
            }

            // sort the results and return
            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::getChildAssocs] Found " + childAssocRefs.size()
                        + " children.");
            }
            return reorderChildAssocs(childAssocRefs);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getChildAssocs] END");
        }
    }

    private List<ChildAssociationRef> reorderChildAssocs(Collection<ChildAssociationRef> childAssocRefs) {
        logger.debug("[SplittingDbNodeServiceImpl::reorderChildAssocs] BEGIN");
        ArrayList<ChildAssociationRef> orderedList = null;
        try {
            // shortcut if there are no associations
            if (childAssocRefs.isEmpty()) {
                return Collections.<ChildAssociationRef>emptyList();
            }

            // sort results
            orderedList = new ArrayList<ChildAssociationRef>(childAssocRefs);
            Collections.sort(orderedList);

            // list of results
            int nthSibling = 0;
            for (ChildAssociationRef child : orderedList) {
                child.setNthSibling(nthSibling);
                nthSibling++;
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::reorderChildAssocs] END");
        }

        return orderedList;
    }

    public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) {
        logger.debug("[SplittingDbNodeServiceImpl::getChildByName] BEGIN");
        NodeRef result = null;

        try {
            // Se il nodo e` una parte -> bug
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getChildByName() on part node!");

            List<Node> parts = null;

            // Nome dell'associazione e childName possono essere diversi, quindi cerchiamo su
            // tutte le parti
            if (isSplitted(node)) {
                parts = getPartNodes(node);
            } else {
                parts = new ArrayList<Node>(1);
                parts.add(node);
            }

            for (Node part : parts) {
                ChildAssoc childAssoc = nodeDaoService.getChildAssoc(part, assocTypeQName, childName);

                if (childAssoc != null) {
                    result = tenantService.getBaseName(childAssoc.getChild().getNodeRef());
                }
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getChildByName] END");
        }
        return result;
    }

    public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getPrimaryParent] BEGIN");
        ChildAssociationRef assocRef = null;

        try {
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getPrimaryParent() on part node!");

            // get the primary parent association
            ChildAssoc assoc = nodeDaoService.getPrimaryParentAssoc(node);

            // done - the association may be null for a root node
            if (assoc == null) {
                assocRef = new ChildAssociationRef(null, null, null, nodeRef);
            } else {

                // Il padre trovato potrebbe essere un nodo parte - necessario risalire al
                // nodo contenitore
                Node parentNode = assoc.getParent();

                assocRef = new ChildAssociationRef(assoc.getTypeQName(),
                        tenantService.getBaseName(getPartsContainerNode(parentNode).getNodeRef()), assoc.getQname(),
                        tenantService.getBaseName(nodeRef), assoc.getIsPrimary(), assoc.getIndex());
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getPrimaryParent] END");
        }
        return assocRef;
    }

    public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName)
            throws InvalidNodeRefException, AssociationExistsException {
        logger.debug("[SplittingDbNodeServiceImpl::createAssociation] BEGIN");
        AssociationRef assocRef = null;

        try {
            Node sourceNode = getNodeNotNull(sourceRef);
            Node targetNode = getNodeNotNull(targetRef);

            Assert.isTrue(!isPart(sourceNode) && !isPart(targetNode),
                    "BUG: createAssociation() on one or more part nodes!");

            // see if it exists
            NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName);
            if (assoc != null) {
                throw new AssociationExistsException(sourceRef, targetRef, assocTypeQName);
            }

            // we are sure that the association doesn't exist - make it
            assoc = nodeDaoService.newNodeAssoc(sourceNode, targetNode, assocTypeQName);
            assocRef = assoc.getNodeAssocRef();

            // Invoke policy behavious
            invokeOnCreateAssociation(assocRef);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::createAssociation] END");
        }
        return assocRef;
    }

    public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName)
            throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::removeAssociation] BEGIN");

        try {
            Node sourceNode = getNodeNotNull(sourceRef);
            Node targetNode = getNodeNotNull(targetRef);

            Assert.isTrue(!isPart(sourceNode) && !isPart(targetNode),
                    "BUG: removeAssociation() on one or more part nodes!");

            // get the association
            NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName);
            if (assoc == null) {
                // nothing to remove
                return;
            }
            AssociationRef assocRef = assoc.getNodeAssocRef();

            // delete it
            nodeDaoService.deleteNodeAssoc(assoc);

            // Invoke policy behaviors
            invokeOnDeleteAssociation(assocRef);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::removeAssociation] END");
        }
    }

    public List<AssociationRef> getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) {
        logger.debug("[SplittingDbNodeServiceImpl::getTargetAssocs] BEGIN");
        List<AssociationRef> nodeAssocRefs = null;

        try {
            Node sourceNode = getNodeNotNull(sourceRef);

            Assert.isTrue(!isPart(sourceNode), "BUG: getTargetAssocs() on part node!");

            // get all associations to target
            Collection<NodeAssoc> assocs = nodeDaoService.getTargetNodeAssocs(sourceNode);
            nodeAssocRefs = new ArrayList<AssociationRef>(assocs.size());
            for (NodeAssoc assoc : assocs) {

                // check qname pattern
                if (!qnamePattern.isMatch(assoc.getTypeQName())) {
                    continue; // the association name doesn't match the pattern given
                }
                nodeAssocRefs.add(assoc.getNodeAssocRef());
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getTargetAssocs] END");
        }
        return nodeAssocRefs;
    }

    public List<AssociationRef> getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) {
        logger.debug("[SplittingDbNodeServiceImpl::getSourceAssocs] BEGIN");
        List<AssociationRef> nodeAssocRefs = null;

        try {
            Node targetNode = getNodeNotNull(targetRef);

            Assert.isTrue(!isPart(targetNode), "BUG: getSourceAssocs() on part node!");

            // get all associations to source
            Collection<NodeAssoc> assocs = nodeDaoService.getSourceNodeAssocs(targetNode);
            nodeAssocRefs = new ArrayList<AssociationRef>(assocs.size());
            for (NodeAssoc assoc : assocs) {

                // check qname pattern
                if (!qnamePattern.isMatch(assoc.getTypeQName())) {
                    continue; // the association name doesn't match the pattern given
                }
                nodeAssocRefs.add(assoc.getNodeAssocRef());
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getSourceAssocs] END");
        }
        return nodeAssocRefs;
    }

    /**
     * Recursive method used to build up paths from a given node to the root.
     * <p>
     * Whilst walking up the hierarchy to the root, some nodes may have a <b>root</b> aspect.
     * Everytime one of these is encountered, a new path is farmed off, but the method
     * continues to walk up the hierarchy.
     *
     * @param currentNode the node to start from, i.e. the child node to work upwards from
     * @param currentPath the path from the current node to the descendent that we started from
     * @param completedPaths paths that have reached the root are added to this collection
     * @param assocStack the parent-child relationships traversed whilst building the path.
     *      Used to detected cyclic relationships.
     * @param primaryOnly true if only the primary parent association must be traversed.
     *      If this is true, then the only root is the top level node having no parents.
     * @throws CyclicChildRelationshipException
     */
    private void prependPaths(final Node currentNode, final Path currentPath, Collection<Path> completedPaths,
            Stack<ChildAssoc> assocStack, boolean primaryOnly) throws CyclicChildRelationshipException {
        logger.debug("[SplittingDbNodeServiceImpl::prependPaths] BEGIN");

        try {
            Assert.isTrue(!isPart(currentNode), "BUG: prependPaths() on part node!");
            NodeRef currentNodeRef = currentNode.getNodeRef();

            // get the parent associations of the given node
            Collection<ChildAssoc> parentAssocs = nodeDaoService.getParentAssocs(currentNode);

            // does the node have parents
            boolean hasParents = !parentAssocs.isEmpty();

            // does the current node have a root aspect?
            boolean isRoot = currentNode.getAspects().contains(ContentModel.ASPECT_ROOT);
            boolean isStoreRoot = currentNode.getTypeQName().equals(ContentModel.TYPE_STOREROOT);

            // look for a root.  If we only want the primary root, then ignore all but the top-level root.
            if (isRoot && !(primaryOnly && hasParents)) { // exclude primary search with parents present

                // create a one-sided association reference for the root node and prepend to the stack
                // this effectively spoofs the fact that the current node is not below the root
                // - we put this association in as the first association in the path must be a one-sided
                //   reference pointing to the root node
                ChildAssociationRef assocRef = new ChildAssociationRef(null, null, null, // Type, parent and QName
                        getRootNode(currentNode.getNodeRef().getStoreRef()));

                // create a path to save and add the 'root' association
                Path pathToSave = new Path();
                Path.ChildAssocElement first = null;

                for (Path.Element element : currentPath) {
                    if (first == null) {
                        first = (Path.ChildAssocElement) element;
                    } else {
                        pathToSave.append(element);
                    }
                }

                if (first != null) {

                    // mimic an association that would appear if the current node was below
                    // the root node
                    // or if first beneath the root node it will make the real thing
                    ChildAssociationRef updateAssocRef = new ChildAssociationRef(
                            isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(),
                            getRootNode(currentNode.getNodeRef().getStoreRef()), first.getRef().getQName(),
                            first.getRef().getChildRef());
                    Path.Element newFirst = new Path.ChildAssocElement(updateAssocRef);
                    pathToSave.prepend(newFirst);
                }

                Path.Element element = new Path.ChildAssocElement(assocRef);
                pathToSave.prepend(element);

                // store the path just built
                completedPaths.add(pathToSave);
            }

            if (!hasParents && !isRoot) {
                throw new RuntimeException("Node without parents does not have root aspect: " + currentNodeRef);
            }

            // walk up each parent association
            for (ChildAssoc assoc : parentAssocs) {

                // does the association already exist in the stack
                if (assocStack.contains(assoc)) {
                    // the association was present already
                    throw new CyclicChildRelationshipException(
                            "Cyclic parent-child relationship detected: \n" + "   current node: " + currentNode
                                    + "\n" + "   current path: " + currentPath + "\n" + "   next assoc: " + assoc,
                            assoc);
                }

                // do we consider only primary associations?
                if (primaryOnly && !assoc.getIsPrimary()) {
                    continue;
                }

                Node realParent = getPartsContainerNode(assoc.getParent());

                // build a real association reference
                ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(),
                        tenantService.getBaseName(realParent.getNodeRef()), assoc.getQname(),
                        tenantService.getBaseName(assoc.getChild().getNodeRef()), assoc.getIsPrimary(), -1);

                // Ordering is not important here: We are building distinct paths upwards
                Path.Element element = new Path.ChildAssocElement(assocRef);

                // create a new path that builds on the current path
                Path path = new Path();
                path.append(currentPath);

                // prepend element
                path.prepend(element);

                // push the associations stack, recurse and pop
                assocStack.push(assoc);
                prependPaths(realParent, path, completedPaths, assocStack, primaryOnly);
                assocStack.pop();
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::prependPaths] END");
        }
    }

    /**
     * @see #getPaths(NodeRef, boolean)
     * @see #prependPaths(Node, Path, Collection, Stack, boolean)
     */
    public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getPath] BEGIN");

        try {
            List<Path> paths = getPaths(nodeRef, true); // checks primary path count
            Assert.isTrue(paths.size() == 1, "BUG: Primary path count not checked!");

            return paths.get(0); // we know there is only one
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getPath] END");
        }
    }

    /**
     * When searching for <code>primaryOnly == true</code>, checks that there is exactly
     * one path.
     * @see #prependPaths(Node, Path, Collection, Stack, boolean)
     */
    public List<Path> getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException {
        logger.debug("[SplittingDbNodeServiceImpl::getPaths] BEGIN");
        final long start = System.currentTimeMillis();
        List<Path> paths = null;

        try {
            // get the starting node
            Node node = getNodeNotNull(nodeRef);
            Assert.isTrue(!isPart(node), "BUG: getPaths() on a part node!");

            // create storage for the paths - only need 1 bucket if we are looking for the primary path
            paths = new ArrayList<Path>(primaryOnly ? 1 : 10);

            // create an empty current path to start from
            Path currentPath = new Path();

            // create storage for touched associations
            Stack<ChildAssoc> assocStack = new Stack<ChildAssoc>();

            // call recursive method to sort it out
            prependPaths(node, currentPath, paths, assocStack, primaryOnly);

            // check that for the primary only case we have exactly one path
            if (primaryOnly && paths.size() != 1) {
                throw new RuntimeException("Node has " + paths.size() + " primary paths: " + nodeRef);
            }

            if (loggerPaths.isDebugEnabled()) {
                StringBuilder sb = new StringBuilder(256);

                sb.append((primaryOnly) ? "Primary path for node " : "Paths for node ").append(nodeRef);

                for (Path path : paths) {
                    sb.append("\n   ").append(path);
                }

                loggerPaths.debug("[SplittingDbNodeServiceImpl::getPaths] " + sb);
            }
        } finally {
            final long stop = System.currentTimeMillis();
            logger.debug("[SplittingDbNodeServiceImpl::getPaths] Elapsed: " + (stop - start) + " ms");
            logger.debug("[SplittingDbNodeServiceImpl::getPaths] END");
        }
        return paths;
    }

    private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) {
        logger.debug("[SplittingDbNodeServiceImpl::archiveNode] BEGIN");

        try {
            NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false);
            Node node = nodeStatus.getNode();
            Assert.isTrue(!isPart(node), "BUG: archiveNode() on part node!");

            ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node);

            // add the aspect
            Set<QName> aspects = node.getAspects();
            aspects.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);

            ChildAssociationRef origParentAssocRef = new ChildAssociationRef(primaryParentAssoc.getTypeQName(),
                    getPartsContainerNode(primaryParentAssoc.getParent()).getNodeRef(),
                    primaryParentAssoc.getQname(), nodeRef, true, primaryParentAssoc.getIndex());
            PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue(
                    dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC),
                    origParentAssocRef);
            properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty);

            PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_OWNER);
            PropertyValue originalCreatorProperty = properties.get(ContentModel.PROP_CREATOR);

            if (originalOwnerProperty != null || originalCreatorProperty != null) {
                properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER,
                        originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty);
            }

            // change the node ownership
            aspects.add(ContentModel.ASPECT_OWNABLE);
            PropertyValue newOwnerProperty = makePropertyValue(
                    dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER),
                    AuthenticationUtil.getCurrentUserName());
            properties.put(ContentModel.PROP_OWNER, newOwnerProperty);

            // move the node
            NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef);
            moveNode(nodeRef, archiveStoreRootNodeRef, ContentModel.ASSOC_CHILDREN,
                    QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem"));

            // the node reference has changed due to the store move
            nodeRef = node.getNodeRef();

            // as has the node status
            nodeStatus = nodeDaoService.getNodeStatus(nodeRef, true);

            // get the IDs of all the node's primary children, including its own
            Map<Long, NodeStatus> nodeStatusesById = getNodeHierarchy(nodeStatus, null);

            // Archive all the associations between the archived nodes and non-archived nodes
            for (NodeStatus nodeStatusToArchive : nodeStatusesById.values()) {
                Node nodeToArchive = nodeStatusToArchive.getNode();
                if (nodeToArchive == null) {
                    continue;
                }

                archiveAssocs(nodeToArchive, nodeStatusesById);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::archiveNode] END");
        }
    }

    /**
     * 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) {
        logger.debug("[SplittingDbNodeServiceImpl::moveNodeToStore] BEGIN");

        try {
            NodeRef nodeRef = node.getNodeRef();
            NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, true);

            // get the IDs of all the node's primary children, including its own
            Map<Long, NodeStatus> nodeStatusesById = getNodeHierarchy(nodeStatus, null);

            // move each node into the archive store
            for (NodeStatus oldNodeStatus : nodeStatusesById.values()) {

                // Backported for CSI from 20071004 HEAD
                // Check if the target node (node in the store) is already there
                NodeRef targetStoreNodeRef = new NodeRef(store.getStoreRef(), oldNodeStatus.getKey().getGuid());

                if (exists(targetStoreNodeRef)) {

                    // It is there already. It must be an archive of an earlier version, so just wipe it out
                    Node archivedNode = getNodeNotNull(targetStoreNodeRef);
                    nodeDaoService.deleteNode(archivedNode, true);

                    // We need to flush here as the node deletion may not take effect before the node creation
                    // is done. As this will only occur during a clash, it is not going to add extra overhead
                    // to the general system performance.
                    nodeDaoService.flush();
                }

                Node nodeToMove = oldNodeStatus.getNode();

                if (isSplitted(nodeToMove)) {
                    for (Node partNode : getPartNodes(nodeToMove)) {
                        partNode.setStore(store);
                    }
                }

                NodeRef oldNodeRef = nodeToMove.getNodeRef();
                nodeToMove.setStore(store);
                NodeRef newNodeRef = nodeToMove.getNodeRef();

                // update old status
                oldNodeStatus.setNode(null);

                // create the new status
                NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true);
                newNodeStatus.setNode(nodeToMove);

                // Record change IDs
                nodeDaoService.recordChangeId(oldNodeRef);
                nodeDaoService.recordChangeId(newNodeRef);

                invokeOnUpdateNode(newNodeRef);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::moveNodeToStore] END");
        }
    }

    /**
     * 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 nodeStatus the status of the node at the top of the hierarchy
     * @param nodeStatusesById a map of node statuses that will be reused as the return value
     * @return Returns a map of nodes in the hierarchy keyed by their IDs
     */
    private Map<Long, NodeStatus> getNodeHierarchy(NodeStatus nodeStatus, Map<Long, NodeStatus> nodeStatusesById) {
        logger.debug("[SplittingDbNodeServiceImpl::getNodeHierarchy] BEGIN");

        try {
            if (nodeStatusesById == null) {
                nodeStatusesById = new LinkedHashMap<Long, NodeStatus>(23);

                // this is the entry into the hierarchy - flush to ensure we are not stale
                nodeDaoService.flush();
            }

            Node node = nodeStatus.getNode();
            if (node == null) {

                // the node has already been deleted
                return nodeStatusesById;
            }

            Long nodeId = node.getId();
            if (nodeStatusesById.containsKey(nodeId)) {

                // this ID was already added - circular reference
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "[SplittingDbNodeServiceImpl::getNodeHierarchy] Circular hierarchy found including node: "
                                    + nodeId);
                }
                return nodeStatusesById;
            }

            // add the node to the map
            nodeStatusesById.put(nodeId, nodeStatus);

            // recurse into the primary children
            Collection<NodeStatus> primaryChildNodeStatuses = null;

            if (isSplitted(node)) {
                primaryChildNodeStatuses = new ArrayList<NodeStatus>();
                for (Node part : getPartNodes(node)) {
                    primaryChildNodeStatuses.addAll(nodeDaoService.getPrimaryChildNodeStatuses(part));
                }
            } else {
                primaryChildNodeStatuses = nodeDaoService.getPrimaryChildNodeStatuses(node);
            }

            for (NodeStatus primaryChildNodeStatus : primaryChildNodeStatuses) {

                // cascade into primary associations
                nodeStatusesById = getNodeHierarchy(primaryChildNodeStatus, nodeStatusesById);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getNodeHierarchy] END");
        }
        return nodeStatusesById;
    }

    /**
     * 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, NodeStatus> nodeStatusesById) {
        logger.debug("[SplittingDbNodeServiceImpl::archiveAssocs] BEGIN");

        try {
            Assert.isTrue(!isPart(node), "BUG: archiveAssocs() on part node!");

            List<ChildAssoc> childAssocsToDelete = new ArrayList<ChildAssoc>(5);

            // child associations
            ArrayList<ChildAssociationRef> archivedChildAssocRefs = new ArrayList<ChildAssociationRef>(25);

            if (isSplitted(node)) {
                for (ChildAssoc part : getPartAssocs(node)) {
                    for (ChildAssoc assoc : nodeDaoService.getChildAssocs(part.getChild())) {

                        Long relatedNodeId = assoc.getChild().getId();

                        if (nodeStatusesById.containsKey(relatedNodeId)) {
                            // a sibling in the archive process
                            continue;
                        }

                        childAssocsToDelete.add(assoc);

                        // Salvo l'associazione tradotta, il ripristino dello splitting avverra` nella fase di restore
                        ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(),
                                node.getNodeRef(), assoc.getQname(), assoc.getChild().getNodeRef(),
                                assoc.getIsPrimary(), assoc.getIndex());

                        archivedChildAssocRefs.add(assocRef);
                    }
                }
            } else {
                for (ChildAssoc assoc : nodeDaoService.getChildAssocs(node)) {

                    Long relatedNodeId = assoc.getChild().getId();

                    if (nodeStatusesById.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 : nodeDaoService.getParentAssocs(node)) {

                Long relatedNodeId = assoc.getParent().getId();

                if (nodeStatusesById.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);

                // Salvo l'associazione tradotta, il ripristino dello splitting avverra` nella
                // fase di restore
                Node realParent = getPartsContainerNode(assoc.getParent());
                ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(),
                        realParent.getNodeRef(), assoc.getQname(), node.getNodeRef(), assoc.getIsPrimary(),
                        assoc.getIndex());

                archivedParentAssocRefs.add(assocRef);
            }
            List<NodeAssoc> nodeAssocsToDelete = new ArrayList<NodeAssoc>(5);

            // source associations
            ArrayList<AssociationRef> archivedSourceAssocRefs = new ArrayList<AssociationRef>(5);
            for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) {

                Long relatedNodeId = assoc.getSource().getId();

                if (nodeStatusesById.containsKey(relatedNodeId)) {
                    continue; // a sibling in the archive process
                }
                nodeAssocsToDelete.add(assoc);
                archivedSourceAssocRefs.add(assoc.getNodeAssocRef());
            }

            // target associations
            ArrayList<AssociationRef> archivedTargetAssocRefs = new ArrayList<AssociationRef>(5);
            for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) {

                Long relatedNodeId = assoc.getTarget().getId();

                if (nodeStatusesById.containsKey(relatedNodeId)) {
                    continue; // a sibling in the archive process
                }
                nodeAssocsToDelete.add(assoc);
                archivedTargetAssocRefs.add(assoc.getNodeAssocRef());
            }

            // delete child associations
            for (ChildAssoc assoc : childAssocsToDelete) {
                nodeDaoService.deleteChildAssoc(assoc, false);
            }

            // delete node associations
            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.isEmpty()) {
                PropertyDefinition propertyDef = dictionaryService
                        .getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS);
                PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs);
                properties.put(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS, propertyValue);
            }

            if (!archivedChildAssocRefs.isEmpty()) {
                PropertyDefinition propertyDef = dictionaryService
                        .getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS);
                PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs);
                properties.put(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS, propertyValue);
            }

            if (!archivedSourceAssocRefs.isEmpty()) {
                PropertyDefinition propertyDef = dictionaryService
                        .getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS);
                PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs);
                properties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, propertyValue);
            }

            if (!archivedTargetAssocRefs.isEmpty()) {
                PropertyDefinition propertyDef = dictionaryService
                        .getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS);
                PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs);
                properties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, propertyValue);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::archiveAssocs] END");
        }
    }

    public NodeRef getStoreArchiveNode(StoreRef storeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::getStoreArchiveNode] BEGIN");

        try {
            storeRef = tenantService.getBaseName(storeRef);
            final StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef);

            return (archiveStoreRef == null) ? null : getRootNode(archiveStoreRef);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::getStoreArchiveNode] END");
        }
    }

    public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName,
            QName assocQName) {
        logger.debug("[SplittingDbNodeServiceImpl::restoreNode] BEGIN");
        NodeRef restoredNodeRef = null;

        try {
            NodeStatus archivedNodeStatus = getNodeStatusNotNull(archivedNodeRef);
            Node archivedNode = archivedNodeStatus.getNode();

            Assert.isTrue(!isPart(archivedNode), "BUG: restoreNode() on a part node!");

            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));
            PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);

            // remove the aspect archived aspect

            // remove the archived aspect
            removeAspect(archivedNodeRef, ContentModel.ASPECT_ARCHIVED); // allow policy to fire, e.g. for DictionaryModelType

            properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
            properties.remove(ContentModel.PROP_ARCHIVED_BY);
            properties.remove(ContentModel.PROP_ARCHIVED_DATE);
            properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);

            // restore the original ownership
            if (originalOwnerProperty != null) {
                aspects.add(ContentModel.ASPECT_OWNABLE);
                properties.put(ContentModel.PROP_OWNER, originalOwnerProperty);
            }

            if (destinationParentNodeRef == null) {

                // we must restore to the original location
                destinationParentNodeRef = originalPrimaryParentAssocRef.getParentRef();
            }

            // check the associations
            if (assocTypeQName == null) {
                assocTypeQName = originalPrimaryParentAssocRef.getTypeQName();
            }

            if (assocQName == null) {
                assocQName = originalPrimaryParentAssocRef.getQName();
            }

            // La gestione dello splitting e` a carico della moveNode()
            ChildAssociationRef newChildAssocRef = moveNode(archivedNodeRef, destinationParentNodeRef,
                    assocTypeQName, assocQName);

            archivedNodeRef = newChildAssocRef.getChildRef();
            archivedNodeStatus = nodeDaoService.getNodeStatus(archivedNodeRef, false);

            // get the IDs of all the node's primary children, including its own
            Map<Long, NodeStatus> restoreNodeStatusesById = getNodeHierarchy(archivedNodeStatus, null);

            // Restore the archived associations, if required
            for (NodeStatus restoreNodeStatus : restoreNodeStatusesById.values()) {
                Node restoreNode = restoreNodeStatus.getNode();
                restoreAssocs(restoreNode);
            }

            // the node reference has changed due to the store move
            restoredNodeRef = archivedNode.getNodeRef();

            if (logger.isDebugEnabled()) {
                logger.debug(
                        "Restored node: " + "\n   original noderef: " + archivedNodeRef + "\n   restored noderef: "
                                + restoredNodeRef + "\n   new parent: " + destinationParentNodeRef);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::restoreNode] END");
        }
        return restoredNodeRef;
    }

    @SuppressWarnings("unchecked")
    private void restoreAssocs(Node node) {
        logger.debug("[SplittingDbNodeServiceImpl::restoreAssocs] BEGIN");

        try {
            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; // Il nodo padre non esiste piu`
                    }
                    Node parentNode = getNodeNotNull(parentNodeRef);
                    QName assocQName = assocRef.getQName();

                    Node destinationNode = null;

                    destinationNode = (isSplitted(parentNode))
                            ? getPartByChildAssocName(parentNode, assocQName.getLocalName())
                            : parentNode;

                    if (destinationNode == null) { // Il nodo e` splittato e la parte di destinazione non e` stata trovata
                        destinationNode = createMissingPart(parentNode, assocQName.getLocalName());
                    }

                    // get the name to use for the unique child check
                    QName assocTypeQName = assocRef.getTypeQName();
                    nodeDaoService.newChildAssoc(destinationNode, node, assocRef.isPrimary(), assocTypeQName,
                            assocQName);
                }
                properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS);
            }

            // make sure that the node name uniqueness is enforced
            setChildUniqueName(node);

            // 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; // Il nodo figlio non esiste piu`
                    }
                    Node childNode = getNodeNotNull(childNodeRef);

                    // get the name to use for the unique child check
                    QName assocTypeQName = assocRef.getTypeQName();
                    QName assocQName = assocRef.getQName();

                    Node destinationNode = null;

                    destinationNode = (isSplitted(node)) ? getPartByChildAssocName(node, assocQName.getLocalName())
                            : node;

                    if (destinationNode == null) { // Il nodo e` splittato e la parte di destinazione non e` stata trovata
                        destinationNode = createMissingPart(node, assocQName.getLocalName());
                    }

                    nodeDaoService.newChildAssoc(destinationNode, childNode, assocRef.isPrimary(), assocTypeQName,
                            assocQName);

                    // ensure that the name uniqueness is enforced for the child node
                    setChildUniqueName(childNode);
                }
                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);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::restoreAssocs] END");
        }
    }

    /**
     * Checks the dictionary's definition of the association to assign a unique name to the child node.
     *
     * @param assocTypeQName the type of the child association
     * @param childNode the child node being added.  The name will be extracted from it, if necessary.
     * @return Returns the value to be put on the child association for uniqueness, or null if
     */
    private void setChildUniqueName(Node childNode) {
        logger.debug("[SplittingDbNodeServiceImpl::setChildUniqueName] BEGIN");

        try {
            Map<QName, PropertyValue> properties = childNode.getProperties();
            PropertyValue nameValue = properties.get(ContentModel.PROP_NAME);
            String useName = null;

            useName = (nameValue == null) ? childNode.getUuid()
                    : (String) nameValue.getValue(DataTypeDefinition.TEXT);

            // get all the parent assocs
            Collection<ChildAssoc> parentAssocs = nodeDaoService.getParentAssocs(childNode);
            for (ChildAssoc assoc : parentAssocs) {
                /*
                 * TODO: se il nodo padre e` splittato il controllo dovrebbe essere propagato anche alle
                 * altre parti per evitare che nel complesso ci possano essere figli duplicati.
                 *
                 * Questa modifica potrebbe richiedere una modifica alla API del nodeServiceDao.
                 */
                QName assocTypeQName = assoc.getTypeQName();
                AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName);
                if (!assocDef.isChild()) {
                    throw new DataIntegrityViolationException(
                            "Child association has non-child type: " + assoc.getId());
                }

                ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef;

                nodeDaoService.setChildNameUnique(assoc,
                        (childAssocDef.getDuplicateChildNamesAllowed()) ? null : useName);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::setChildUniqueName] Unique name set for all "
                        + parentAssocs.size() + " parent associations: \n" + "   name: " + useName);
            }
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::setChildUniqueName] END");
        }
    }

    private Node getPartsContainerNode(Node node) {
        long start = System.currentTimeMillis();

        Node realNode = (isPart(node)) ? nodeDaoService.getPrimaryParentAssoc(node).getParent() : node;

        if (logger.isDebugEnabled()) {
            long end = System.currentTimeMillis();
            logger.debug("[SplittingDbNodeServiceImpl::getPartsContainerNode] Get container:" + "\n\t\tI: " + node
                    + "\n\t\tO: " + realNode);
            logger.debug("[SplittingDbNodeServiceImpl::getPartsContainerNode] Elapsed: " + (end - start) + " ms");
        }
        return realNode;
    }

    private boolean isSplitted(Node node) {
        logger.debug("[SplittingDbNodeServiceImpl::isSplitted] BEGIN");

        try {
            return node.getAspects().contains(EcmEngineModelConstants.ASPECT_SPLITTED);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::isSplitted] END");
        }
    }

    private boolean isPart(Node node) {
        logger.debug("[SplittingDbNodeServiceImpl::isPart] BEGIN");

        try {
            return node.getAspects().contains(EcmEngineModelConstants.ASPECT_PART);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::isPart] END");
        }
    }

    private String getPartName(String contentName) {

        long start = System.currentTimeMillis();
        char[] chars = contentName.toCharArray();
        int sum = 0;

        for (int i = 0; i < (chars.length / 2); i++) {
            sum += chars[i];
        }
        sum = sum << 8;
        for (int j = (chars.length / 2); j < chars.length; j++) {
            sum += (j % 2 == 0) ? (chars[j] / 2) : chars[j];
        }

        final String partName = "part" + (sum % partsCount);
        long end = System.currentTimeMillis();

        if (logger.isDebugEnabled()) {
            logger.debug("[SplittingDbNodeServiceImpl::getPartName] " + "I: " + contentName + " -> O: " + partName);
            logger.debug("[SplittingDbNodeServiceImpl::getPartName] Elapsed: " + (end - start) + " ms");
        }

        return partName;
    }

    private List<Node> getPartNodes(Node container) {
        // Questo metodo non deve essere richiamato su nodi che non siano splittati
        Assert.isTrue(isSplitted(container), "BUG: getPartNodes() on a non-splitted node!");

        final long start = System.currentTimeMillis();
        final Collection<ChildAssoc> parts = nodeDaoService.getChildAssocs(container);
        List<Node> results = new ArrayList<Node>(parts.size());

        // Tutti i figli di un nodo splittato sono "parti"
        for (ChildAssoc part : parts) {
            results.add(part.getChild());
        }
        final long stop = System.currentTimeMillis();

        if (logger.isDebugEnabled()) {
            logger.debug("[SplittingDbNodeServiceImpl::getPartNodes] N: " + container.getNodeRef() + " Parts: "
                    + results.size());
            logger.debug("[SplittingDbNodeServiceImpl::getPartNodes] Elapsed: " + (stop - start) + " ms");
        }

        return results;
    }

    private List<ChildAssoc> getPartAssocs(Node container) {
        // Questo metodo non deve essere richiamato su nodi che non siano splittati
        Assert.isTrue(isSplitted(container), "BUG: getPartAssocs() on a non-splitted node!");

        final long start = System.currentTimeMillis();
        final Collection<ChildAssoc> parts = nodeDaoService.getChildAssocs(container);
        List<ChildAssoc> results = new ArrayList<ChildAssoc>(parts.size());

        // Tutti i figli di un nodo splittato sono "parti"
        results.addAll(parts);

        final long stop = System.currentTimeMillis();

        if (logger.isDebugEnabled()) {
            logger.debug("[SplittingDbNodeServiceImpl::getPartAssocs] Container: " + container.getNodeRef()
                    + " Parts: " + results.size());
            logger.debug("[SplittingDbNodeServiceImpl::getPartAssocs] Elapsed: " + (stop - start) + " ms");
        }

        return results;
    }

    private Node getPartByChildAssocName(Node container, String localName) {
        logger.debug("[SplittingDbNodeServiceImpl::getPartByChildName] BEGIN");
        final long start = System.currentTimeMillis();
        final QName partQName = QName.createQName(EcmEngineModelConstants.ECMENGINE_SYS_MODEL_URI,
                getPartName(localName));
        Node part = null;

        try {
            Collection<ChildAssociationRef> partAssocs = nodeDaoService.getChildAssocRefs(container, partQName);

            for (ChildAssociationRef partAssoc : partAssocs) {
                part = getNodeNotNull(partAssoc.getChildRef());
                break; // Consideriamo un solo risultato - le parti devono avere nomi distinti
            }
        } catch (InvalidNodeRefException e) {
            // Ignore
        } finally {
            if (logger.isDebugEnabled()) {
                final long stop = System.currentTimeMillis();
                logger.debug("[SplittingDbNodeServiceImpl::getPartByChildName] Name: " + localName + " Ref: "
                        + ((part != null) ? part.getNodeRef() : part));
                logger.debug("[SplittingDbNodeServiceImpl::getPartByChildName] Elapsed: " + (stop - start) + " ms");
                logger.debug("[SplittingDbNodeServiceImpl::getPartByChildName] END");
            }
        }
        return part; // Se la parte non e` stata trovata e` null
    }

    private Node createMissingPart(Node parent, String childLocalName) {
        logger.debug("[SplittingDbNodeServiceImpl::createMissingPart] BEGIN");
        final long start = System.currentTimeMillis();
        final QName partQName = QName.createQName(EcmEngineModelConstants.ECMENGINE_SYS_MODEL_URI,
                getPartName(childLocalName));
        ChildAssoc part = null;
        Node child = null;
        Store store = null;

        try {
            store = nodeDaoService.getStore(parent.getNodeRef().getStoreRef().getProtocol(),
                    parent.getNodeRef().getStoreRef().getIdentifier());

            HashMap<QName, Serializable> props = new HashMap<QName, Serializable>(1);

            PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_NAME);
            PropertyValue nameValue = makePropertyValue(propertyDef, partQName.getLocalName());

            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::createMissingPart] Creating missing part " + partQName);
            }

            String newId = generateGuid(props);

            child = nodeDaoService.newNode(store, newId, parent.getTypeQName());

            child.getAspects().add(EcmEngineModelConstants.ASPECT_PART);
            child.getProperties().put(ContentModel.PROP_NAME, nameValue);

            // Create the association
            part = nodeDaoService.newChildAssoc(parent, child, true, EcmEngineModelConstants.ASSOC_PARTS,
                    partQName);

        } finally {
            final long stop = System.currentTimeMillis();
            logger.debug("[SplittingDbNodeServiceImpl::createMissingPart] Elapsed: " + (stop - start) + " ms");
            logger.debug("[SplittingDbNodeServiceImpl::createMissingPart] END");
        }

        return part.getChild();
    }

    private void splitNode(Node node) {
        logger.debug("[SplittingDbNodeServiceImpl::splitNode] BEGIN");
        Assert.isTrue(!isPart(node), "BUG: splitNode() on part node!");

        final long start = System.currentTimeMillis();
        int moved = 0;
        int parts = 0;

        try {
            Collection<ChildAssoc> curChildren = nodeDaoService.getChildAssocs(node);

            for (ChildAssoc child : curChildren) {

                ChildAssociationRef oldAssocRef = child.getChildAssocRef();
                QName assocTypeQName = child.getTypeQName();
                QName assocQName = child.getQname();
                NodeRef childRef = child.getChild().getNodeRef();
                Node childNode = child.getChild();
                Node part = getPartByChildAssocName(node, assocQName.getLocalName());

                if (part == null) {
                    part = createMissingPart(node, assocQName.getLocalName());
                    parts++;
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("[SplittingDbNodeServiceImpl::splitNode] Moving child " + childRef + " to part "
                            + part.getNodeRef());
                }

                // BEGIN SPOSTAMENTO

                invokeBeforeDeleteChildAssociation(oldAssocRef);
                invokeBeforeCreateChildAssociation(node.getNodeRef(), childRef, assocTypeQName, assocQName, false);

                // remove the child association from the old parent
                // don't cascade as we will still need the node afterwards
                nodeDaoService.deleteChildAssoc(child, false);

                // create a new association
                ChildAssoc newAssoc = nodeDaoService.newChildAssoc(part, // Nodo o parte
                        childNode, true, assocTypeQName, assocQName);
                setChildUniqueName(childNode); // ensure uniqueness

                ChildAssociationRef newAssocRef = new ChildAssociationRef(newAssoc.getTypeQName(),
                        node.getNodeRef(), assocQName, childRef, newAssoc.getIsPrimary(), newAssoc.getIndex());

                // check that no cyclic relationships have been created
                getPaths(childRef, false);

                // invoke policy behavior
                invokeOnCreateChildAssociation(newAssocRef, false);
                invokeOnDeleteChildAssociation(oldAssocRef);

                invokeOnMoveNode(oldAssocRef, newAssocRef);

                // update the node status
                nodeDaoService.recordChangeId(childRef);

                // END SPOSTAMENTO

                moved++;

                if (logger.isDebugEnabled()) {
                    logger.debug("[SplittingDbNodeServiceImpl::splitNode] Move completed.");
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::splitNode] Moved " + moved + " nodes to " + parts
                        + " parts.");
            }
        } finally {
            final long stop = System.currentTimeMillis();
            logger.debug("[SplittingDbNodeServiceImpl::splitNode] Elapsed: " + (stop - start) + " ms");
            logger.debug("[SplittingDbNodeServiceImpl::splitNode] END");
        }
    }

    public boolean isPartRef(NodeRef nodeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::isPartRef] BEGIN");

        try {
            final Node node = getNodeNotNull(nodeRef);
            return isPart(node);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::isPartRef] END");
        }
    }

    public boolean isSplittedRef(NodeRef nodeRef) {
        logger.debug("[SplittingDbNodeServiceImpl::isSplittedRef] BEGIN");

        try {
            final Node node = getNodeNotNull(nodeRef);
            return isSplitted(node);
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::isSplittedRef] END");
        }
    }

    public ChildAssociationRef translateChildAssociationRef(ChildAssociationRef internalChildAssoc) {
        logger.debug("[SplittingDbNodeServiceImpl::translateChildAssociationRef] BEGIN");
        ChildAssociationRef externalChildAssoc = null;
        NodeRef translatedChild = null;

        final long start = System.currentTimeMillis();
        try {
            try {
                translatedChild = translateNodeRef(internalChildAssoc.getChildRef());
            } catch (InvalidNodeRefException e) {
                /*
                 * Se questo succede probabilmente stiamo gestendo una delete. A questo punto
                 * quindi il nodo figlio non esiste piu` ma possiamo assumere che il nodeRef originale
                 * sia utilizzabile.
                 */
                translatedChild = internalChildAssoc.getChildRef();
                /*
                 * MB: Se sono su un tenant, occorre tradurre il child.
                 *     La modifica e' stata introdotta, dopo la verifica che, la DELETE su
                 *     tenant, cadeva in questo ramo, creando, di fatto, un NodeRef puntato al
                 *     repository principale e non al tenant. Questo e' sbagliato e provocava
                 *     un'aggiornamento degli indici Lucene con un percorso sbagliato
                 *     ex
                 *       workspace://SpacesStore/1381fb1b-081e-11de-84be-134d9b875fad
                 *     al posto di
                 *       workspace://@mactaicindex@SpacesStore/1381fb1b-081e-11de-84be-134d9b875fad
                 */
                translatedChild = tenantService.getName(translatedChild);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::translateChildAssociationRef] " + "Input: "
                        + internalChildAssoc);
            }
            externalChildAssoc = new ChildAssociationRef(internalChildAssoc.getTypeQName(),
                    translateNodeRef(internalChildAssoc.getParentRef()), internalChildAssoc.getQName(),
                    translatedChild, internalChildAssoc.isPrimary(), internalChildAssoc.getNthSibling());

            if (logger.isDebugEnabled()) {
                logger.debug("[SplittingDbNodeServiceImpl::translateChildAssociationRef] " + "Output: "
                        + externalChildAssoc);
            }
        } finally {
            final long stop = System.currentTimeMillis();
            logger.debug("[SplittingDbNodeServiceImpl::translateChildAssociationRef] END" + " [" + (stop - start)
                    + " ms]");
        }

        return externalChildAssoc;
    }

    public NodeRef translateNodeRef(NodeRef internalNode) {
        logger.debug("[SplittingDbNodeServiceImpl::translateNodeRef] BEGIN");

        try {
            final Node node = getNodeNotNull(internalNode);
            return getPartsContainerNode(node).getNodeRef();
        } finally {
            logger.debug("[SplittingDbNodeServiceImpl::translateNodeRef] END");
        }
    }
}