/* 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"); } } }