From 3227355279448088b63c44ded40f770bc1e47fbc Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Thu, 4 Sep 2008 00:11:13 +0000 Subject: [PATCH] Merged V2.9 to HEAD 10561: Merged V2.2 to V2.9 9882: Node DAO separation 10580: Merged V2.2 to V2.9 10576: Missing onContentDelete firing 10577: More policies: beforeCreateNode and beforeDeleteNode when archiving nodes in hierarchy - Updated UsageService and TenantService to conform to the new node DAO (more separation) - TODO: Tenant node interceptor not present. This must be added if Multi-Tentant features are required. - NodeMonitor event processing now checks that the nodes are still valid before processing. - onMove firing was breaking NodeMonitor. Changed onMove to not fire when nodes are moved between stores. - Raised ALFCOM-1912: ClassCastException when accessing property of type ver2:versionNumber - Pull setFixedAcls fully into Node DAO for simpler and speedier execution git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@10709 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/core-services-context.xml | 5 + config/alfresco/hibernate-context.xml | 28 +- .../messages/patch-service.properties | 2 +- config/alfresco/model/systemModel.xml | 9 + config/alfresco/node-services-context.xml | 34 +- .../alfresco/patch/patch-services-context.xml | 20 +- config/alfresco/repository.properties | 5 + config/alfresco/scheduled-jobs-context.xml | 34 +- config/alfresco/usage-services-context.xml | 2 +- .../alfresco/filesys/repo/NodeMonitor.java | 24 +- .../java/org/alfresco/model/ContentModel.java | 3 + .../patch/impl/NoLongerSupportedPatch.java | 68 + .../patch/impl/UniqueChildNamePatch.java | 337 -- .../alfresco/repo/audit/AuditableAspect.java | 5 - .../org/alfresco/repo/avm/AVMNodeService.java | 17 +- .../content/cleanup/ContentStoreCleaner.java | 5 +- .../ContentStoreCleanerScalabilityRunner.java | 13 +- .../repo/domain/AccessControlListDAO.java | 13 +- .../org/alfresco/repo/domain/StoreKey.java | 6 + .../repo/{usage => domain}/UsageDeltaDAO.java | 2 +- .../hibernate/AVMAccessControlListDAO.java | 5 + .../AbstractPermissionsDaoComponentImpl.java | 2 - .../repo/domain/hibernate/ChildAssocImpl.java | 2 +- .../hibernate/DMAccessControlListDAO.java | 135 +- .../hibernate/DirtySessionAnnotation.java | 59 + .../DirtySessionMethodInterceptor.java | 343 ++ .../domain/hibernate/HibernateNodeTest.java | 44 + .../hibernate/HibernateUsageDeltaDAO.java | 22 +- .../repo/domain/hibernate/Node.hbm.xml | 298 +- .../hibernate/NodeAccessControlListDAO.java | 87 +- .../OldADMPermissionsDaoComponentImpl.java | 2 +- .../hibernate/SessionSizeResourceManager.java | 2 +- .../domain/hibernate/TransactionImpl.java | 2 +- .../repo/domain/hibernate/UsageDelta.hbm.xml | 4 +- .../repo/node/AbstractNodeServiceImpl.java | 80 +- .../repo/node/BaseNodeServiceTest.java | 5 +- .../repo/node/FullNodeServiceTest.java | 5 + .../node/archive/ArchiveAndRestoreTest.java | 25 +- .../repo/node/db/DbNodeServiceImpl.java | 3000 +++++++++-------- .../repo/node/db/DbNodeServiceImplTest.java | 60 +- .../alfresco/repo/node/db/NodeDaoService.java | 355 +- .../repo/node/db/NodeServiceCleanupJob.java | 57 + .../HibernateNodeDaoServiceImpl.java | 1944 ++++++++--- .../alfresco/repo/node/index/NodeIndexer.java | 95 +- .../repo/node/index/NodeIndexerTest.java | 181 - .../repo/search/IndexerComponent.java | 22 +- .../impl/PermissionServiceTest.java | 4 +- .../impl/PermissionsDaoComponent.java | 33 - .../repo/tenant/MultiTAdminServiceImpl.java | 3 +- .../AlfrescoTransactionSupport.java | 3 + .../alfresco/repo/usage/ContentUsageImpl.java | 97 - .../alfresco/repo/usage/UsageServiceImpl.java | 15 +- .../usage/UserUsageTrackingComponent.java | 159 +- .../repo/version/NodeServiceImpl.java | 17 + .../repo/version/VersionServiceImplTest.java | 3 +- .../CyclicChildRelationshipException.java | 12 +- .../service/cmr/repository/NodeService.java | 25 +- .../alfresco/service/cmr/repository/Path.java | 1 - 58 files changed, 4610 insertions(+), 3230 deletions(-) create mode 100644 source/java/org/alfresco/repo/admin/patch/impl/NoLongerSupportedPatch.java delete mode 100644 source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java rename source/java/org/alfresco/repo/{usage => domain}/UsageDeltaDAO.java (98%) create mode 100644 source/java/org/alfresco/repo/domain/hibernate/DirtySessionAnnotation.java create mode 100644 source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java create mode 100644 source/java/org/alfresco/repo/node/db/NodeServiceCleanupJob.java delete mode 100644 source/java/org/alfresco/repo/node/index/NodeIndexerTest.java diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 8972ba2b74..bb19c2e542 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -415,9 +415,14 @@ + + + + diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index e36923cff6..3e953ad854 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -319,10 +319,10 @@ - + - + @@ -331,22 +331,22 @@ - + + + + + + + - - - - - - @@ -355,15 +355,19 @@ - - - org.alfresco.repo.node.db.NodeDaoService + + + + + + + dbNodeDaoServiceDirtySessionInterceptor dbNodeDaoServiceTxnRegistration diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index af9551e90c..334a968419 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -8,7 +8,7 @@ patch.executer.system_readonly=Patches cannot be applied to a read-only system. patch.executer.not_executed =\n=== Recorded patch (not executed) === \nID: {0}\nRESULT: \n{1}\n===================================== patch.executer.executed =\n=== Applied patch === \nID: {0}\nRESULT: \n{1}\n===================================== patch.executer.failed =\n=== Failed to apply patch === \nID: {0}\nRESULT: \n{1}\n===================================== - +patch.noLongerSupportedPatch.err.use_incremental_upgrade = \nPatch ''{0}'' was last supported on version {1}.\n Please follow an incremental upgrade using version {2}. # General patch messages patch.genericBootstrap.result.exists=Bootstrap location already exists: {0} diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index fbd6f98adc..053854ab59 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -208,6 +208,15 @@ + + + Cascade Index Children + + \ No newline at end of file diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 40fd3f4508..5b694efc20 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -93,19 +93,6 @@ - - - - - - - - - - - - - @@ -154,6 +141,13 @@ + + + + + + + @@ -169,12 +163,12 @@ - - - + + + @@ -187,11 +181,11 @@ - - + + - - + + ${system.cascadeDeleteInTransaction} diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 2d64088976..13a77812f2 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -465,28 +465,14 @@ classpath:alfresco/dbscripts/upgrade/1.4/${db.script.dialect}/AlfrescoSchemaUpdate-1.4-1.sql - + patch.uniqueChildName patch.uniqueChildName.description 0 19 20 - - - - - - - - - - - - - - - - + + 2.1.4 diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index ae8d3b861b..360c80ab84 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -87,6 +87,11 @@ system.maximumStringLength=-1 # events may not group if there are post action listener registered (this is not the case with the default distribution) system.hibernateMaxExecutions=20000 +# +# Determine if document deletion and archival must cascade delete in the same +# transaction that triggers the operation. +system.cascadeDeleteInTransaction=true + # #################### # # Lucene configuration # # #################### # diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index 7c4d303865..fae447a632 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -133,6 +133,31 @@ + + + org.alfresco.repo.node.db.NodeServiceCleanupJob + + + + + + + + + + + + + + + + + + + 0 15 * * * ? + + + org.alfresco.util.OpenOfficeConnectionTester$OpenOfficeConnectionTesterJob @@ -257,12 +282,6 @@ - - - - - - @@ -275,6 +294,9 @@ + + + diff --git a/config/alfresco/usage-services-context.xml b/config/alfresco/usage-services-context.xml index f45bbe32d2..dad5f80d28 100644 --- a/config/alfresco/usage-services-context.xml +++ b/config/alfresco/usage-services-context.xml @@ -5,7 +5,7 @@ - + diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java index 139fb5e3c1..a11972ebba 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitor.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -349,19 +349,19 @@ public class NodeMonitor extends TransactionListenerAdapter // Check if the node is a file/folder, and for our store - NodeRef nodeRef = oldChildAssocRef.getChildRef(); - if ( nodeRef.getStoreRef().equals( m_storeRef) == false) + NodeRef oldNodeRef = oldChildAssocRef.getChildRef(); + if ( oldNodeRef.getStoreRef().equals( m_storeRef) == false) return; - QName nodeType = m_nodeService.getType( nodeRef); + QName nodeType = m_nodeService.getType( oldNodeRef); FileFolderServiceType fType = m_fileFolderService.getType( nodeType); if ( fType != FileFolderServiceType.INVALID) { // Get the full path to the file/folder node - Path nodePath = m_nodeService.getPath( nodeRef); - String fName = (String) m_nodeService.getProperty( nodeRef, ContentModel.PROP_NAME); + Path nodePath = m_nodeService.getPath( oldNodeRef); + String fName = (String) m_nodeService.getProperty( oldNodeRef, ContentModel.PROP_NAME); // Build the share relative path to the node @@ -376,7 +376,7 @@ public class NodeMonitor extends TransactionListenerAdapter // DEBUG if ( logger.isDebugEnabled()) - logger.debug("OnMoveNode: nodeRef=" + nodeRef + ", relPath=" + relPath); + logger.debug("OnMoveNode: nodeRef=" + oldNodeRef + ", relPath=" + relPath); // Queue an event to process the node move @@ -384,7 +384,7 @@ public class NodeMonitor extends TransactionListenerAdapter // Create a move event - NodeEvent nodeEvent = new MoveNodeEvent( fType, nodeRef, relPath, newChildAssocRef.getChildRef()); + NodeEvent nodeEvent = new MoveNodeEvent( fType, oldNodeRef, relPath, newChildAssocRef.getChildRef()); // Store the event in the transaction until committed, and register the transaction listener @@ -550,7 +550,15 @@ public class NodeMonitor extends TransactionListenerAdapter { return null; } - else if ( nodeEvent instanceof CreateNodeEvent) { + + // Check that the node is still valid + + if (!m_nodeService.exists(nodeEvent.getNodeRef())) + { + return null; + } + + if ( nodeEvent instanceof CreateNodeEvent) { // Node created diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 5f56610078..3c94de0946 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -55,6 +55,9 @@ public interface ContentModel static final QName ASPECT_LOCALIZED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "localized"); static final QName PROP_LOCALE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "locale"); + // cascade index + static final QName ASPECT_INDEX_CHILDREN = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "indexChildren"); + // archived nodes aspect constants static final QName ASPECT_ARCHIVED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived"); static final QName PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalParentAssoc"); diff --git a/source/java/org/alfresco/repo/admin/patch/impl/NoLongerSupportedPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/NoLongerSupportedPatch.java new file mode 100644 index 0000000000..a7d6a29d2f --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/NoLongerSupportedPatch.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * 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 + * of the License, 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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.service.cmr.admin.PatchException; + +/** + * Notifies the user that the patch about to be run is no longer supported and an incremental upgrade + * path must be followed. + * + * @author Derek Hulley + * @since 2.1.5 + */ +public class NoLongerSupportedPatch extends AbstractPatch +{ + private static final String ERR_USE_INCREMENTAL_UPGRADE = "patch.NoLongerSupportedPatch.err.use_incremental_upgrade"; + + private String lastSupportedVersion; + + public NoLongerSupportedPatch() + { + } + + public void setLastSupportedVersion(String lastSupportedVersion) + { + this.lastSupportedVersion = lastSupportedVersion; + } + + @Override + protected void checkProperties() + { + super.checkProperties(); + checkPropertyNotNull(lastSupportedVersion, "lastSupportedVersion"); + } + + @Override + protected String applyInternal() throws Exception + { + throw new PatchException( + ERR_USE_INCREMENTAL_UPGRADE, + super.getId(), + lastSupportedVersion, + lastSupportedVersion); + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java deleted file mode 100644 index 825496de91..0000000000 --- a/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * 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 - * of the License, 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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.admin.patch.impl; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.Collection; -import java.util.Date; -import java.util.List; - -import org.alfresco.i18n.I18NUtil; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.domain.ChildAssoc; -import org.alfresco.repo.domain.Node; -import org.alfresco.repo.node.db.NodeDaoService; -import org.alfresco.service.cmr.admin.PatchException; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.namespace.QName; -import org.hibernate.Query; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.springframework.orm.hibernate3.HibernateCallback; -import org.springframework.orm.hibernate3.support.HibernateDaoSupport; - -/** - * Checks that all child node names are unique for the associations that require it. - * - * @author Derek Hulley - */ -public class UniqueChildNamePatch extends AbstractPatch -{ - private static final String MSG_SUCCESS = "patch.uniqueChildName.result"; - private static final String ERR_UNABLE_TO_FIX = "patch.uniqueChildName.err.unable_to_fix"; - private static final String MSG_COPY_OF = "patch.uniqueChildName.copyOf"; - /** the number of associations to process at a time */ - private static final int MAX_RESULTS = 1000; - - private SessionFactory sessionFactory; - private DictionaryService dictionaryService; - private NodeDaoService nodeDaoService; - - public UniqueChildNamePatch() - { - } - - public void setSessionFactory(SessionFactory sessionFactory) - { - this.sessionFactory = sessionFactory; - } - - /** - * @param dictionaryService The service used to sort out the associations - * that require duplicate checks - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @param nodeDaoService The service that generates the CRC values - */ - public void setNodeDaoService(NodeDaoService nodeDaoService) - { - this.nodeDaoService = nodeDaoService; - } - - @Override - protected void checkProperties() - { - super.checkProperties(); - checkPropertyNotNull(sessionFactory, "sessionFactory"); - checkPropertyNotNull(dictionaryService, "dictionaryService"); - checkPropertyNotNull(nodeDaoService, "nodeDaoService"); - } - - @Override - protected String applyInternal() throws Exception - { - // initialise the helper - HibernateHelper helper = new HibernateHelper(); - helper.setSessionFactory(sessionFactory); - - try - { - String msg = helper.assignCrc(); - // done - return msg; - } - finally - { - helper.closeWriter(); - } - } - - private class HibernateHelper extends HibernateDaoSupport - { - private File logFile; - private FileChannel channel; - - private HibernateHelper() throws IOException - { - logFile = new File("./UniqueChildNamePatch.log"); - // open the file for appending - RandomAccessFile outputFile = new RandomAccessFile(logFile, "rw"); - channel = outputFile.getChannel(); - // move to the end of the file - channel.position(channel.size()); - // add a newline and it's ready - writeLine("").writeLine(""); - writeLine("UniqueChildNamePatch executing on " + new Date()); - } - - private HibernateHelper write(Object obj) throws IOException - { - channel.write(ByteBuffer.wrap(obj.toString().getBytes())); - return this; - } - private HibernateHelper writeLine(Object obj) throws IOException - { - write(obj); - write("\n"); - return this; - } - private void closeWriter() - { - try { channel.close(); } catch (Throwable e) {} - } - - public String assignCrc() throws Exception - { - // get the association types to check - @SuppressWarnings("unused") - List assocTypeQNames = getUsedAssocQNames(); - - boolean unableToFix = false; - int fixed = 0; - int processed = 0; - // check loop through all associations, looking for duplicates - for (QName assocTypeQName : assocTypeQNames) - { - AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); - if (!(assocDef instanceof ChildAssociationDefinition)) - { - String msg = "WARNING: Non-child association used to link a child node: " + assocTypeQName; - writeLine(msg); - logger.warn(msg); - continue; - } - ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; - if (childAssocDef.getDuplicateChildNamesAllowed()) - { - continue; - } - write("Checking for name duplicates on association type ").writeLine(assocTypeQName); - - // get all child associations until there are no more results - long lastAssocId = Long.MIN_VALUE; - int lastResultCount = 1; - while(lastResultCount > 0) - { - writeLine(String.format("...Processed %7d associations with %3d duplicates found...", processed, fixed)); - - List results = getAssociations(assocTypeQName, lastAssocId) ; - lastResultCount = results.size(); - for (Object[] objects : results) - { - ChildAssoc childAssoc = (ChildAssoc) objects[0]; - Node childNode = (Node) objects[1]; - NodeRef childNodeRef = childNode.getNodeRef(); - - // get the current name - String childName = (String) nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME); - - lastAssocId = childAssoc.getId(); - String usedChildName = childName; - processed++; - boolean duplicate = false; - int duplicateNumber = 0; - while(true) - { - duplicateNumber++; - try - { - // push the name back to the node - nodeService.setProperty(childNodeRef, ContentModel.PROP_NAME, usedChildName); - break; // no issues - no duplicate - } - catch (DuplicateChildNodeNameException e) - { - if (duplicateNumber == 10) - { - // Try removing the secondary parent associations - writeLine(" Removing secondary parents of node " + childNode.getId()); - Collection parentAssocs = nodeDaoService.getParentAssocs(childNode); - for (ChildAssoc parentAssoc : parentAssocs) - { - if (!parentAssoc.getIsPrimary()) - { - write(" - ").writeLine(parentAssoc); - // remove it - getSession().delete(parentAssoc); - } - } - // flush to ensure the database gets the changes - getSession().flush(); - // try again to be sure - continue; - } - else if (duplicateNumber > 10) - { - // after 10 attempts, we have to admit defeat. Perhaps there is a larger issue. - Collection parentAssocs = nodeDaoService.getParentAssocs(childNode); - write(" Unable to set child name '" + usedChildName + "' for node " + childNode.getId()); - writeLine(" with parent associations:"); - for (ChildAssoc parentAssoc : parentAssocs) - { - write(" - ").writeLine(parentAssoc); - } - duplicate = false; - unableToFix = true; - break; - } - else - { - // there was a duplicate, so adjust the name and change the node property - duplicate = true; - // assign a new name - usedChildName = childName + I18NUtil.getMessage(MSG_COPY_OF, processed, duplicateNumber); - } - } - } - // if duplicated, report it - if (duplicate) - { - fixed++; - // get the node path - NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); - Path path = nodeService.getPath(parentNodeRef); - writeLine(" Changed duplicated child name:"); - writeLine(" Parent: " + parentNodeRef); - writeLine(" Parent path: " + path); - writeLine(" Duplicate name: " + childName); - writeLine(" Replaced with: " + usedChildName); - } - } - // clear the session to preserve memory - getSession().flush(); - getSession().clear(); - } - } - - // check if it was successful or not - if (unableToFix) - { - throw new PatchException(ERR_UNABLE_TO_FIX, logFile); - } - else - { - // build the result message - String msg = I18NUtil.getMessage(MSG_SUCCESS, processed, fixed, logFile); - return msg; - } - } - - @SuppressWarnings("unchecked") - private List getUsedAssocQNames() - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .createQuery( - "select distinct assoc.typeQName " + - "from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc"); - return query.list(); - } - }; - List results = (List) getHibernateTemplate().execute(callback); - return results; - } - - /** - * @return Returns a list of ChildAssoc and String instances - */ - @SuppressWarnings("unchecked") - private List getAssociations(final QName assocTypeQName, final long lastAssocId) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery("node.patch.GetAssocsAndChildNames") - .setLong("lastAssocId", lastAssocId) - .setParameter("assocTypeQName", assocTypeQName) - .setMaxResults(MAX_RESULTS); - return query.list(); - } - }; - List results = (List) getHibernateTemplate().execute(callback); - return results; - } - } -} diff --git a/source/java/org/alfresco/repo/audit/AuditableAspect.java b/source/java/org/alfresco/repo/audit/AuditableAspect.java index 8a8ae02e6f..ab1e1dc9a5 100644 --- a/source/java/org/alfresco/repo/audit/AuditableAspect.java +++ b/source/java/org/alfresco/repo/audit/AuditableAspect.java @@ -272,13 +272,8 @@ public class AuditableAspect this.properties = properties; } - /* (non-Javadoc) - * @see org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork#doWork() - */ public Boolean doWork() throws Exception { - // Callback here must be done on a per-property basis to avoid the cm:name updates to the child association - for (Map.Entry entry : properties.entrySet()) { QName propertyQName = entry.getKey(); diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 85aaa1a527..c4a4abb9fd 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -153,6 +153,14 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi } } + /** + * @throws UnsupportedOperationException Always + */ + public void deleteStore(StoreRef storeRef) throws InvalidStoreRefException + { + throw new UnsupportedOperationException(); + } + /** * Does the indicated store exist? * @param storeRef a reference to the store to look for @@ -303,7 +311,7 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { throw new InvalidTypeException("Invalid node type for AVM.", nodeTypeQName); } - addDefaultPropertyValues(nodeTypeDef, properties); + properties.putAll(getDefaultProperties(nodeTypeDef)); addDefaultAspects(nodeTypeDef, avmPath, properties); } catch (AVMNotFoundException e) @@ -316,7 +324,7 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi } String newAVMPath = AVMNodeConverter.ExtendAVMPath(avmPath, nodeName); NodeRef childRef = AVMNodeConverter.ToNodeRef(-1, newAVMPath); - addDefaultPropertyValues(nodeTypeDef, properties); + properties.putAll(getDefaultProperties(nodeTypeDef)); addDefaultAspects(nodeTypeDef, newAVMPath, properties); Map props = new HashMap(); for (Map.Entry entry : properties.entrySet()) @@ -578,7 +586,8 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi properties.putAll(aspectProperties); } // Now set any unspecified default properties for the aspect. - addDefaultPropertyValues(aspectDef, properties); + Map defaultProperties = getDefaultProperties(aspectDef); + properties.putAll(defaultProperties); // Now add any cascading aspects. addDefaultAspects(aspectDef, avmPath, properties); // Set the property values on the AVM Node. @@ -637,7 +646,7 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { // invokeBeforeAddAspect(nodeRef, def.getName()); addAspect(nodeRef, def.getName(), Collections.emptyMap()); - addDefaultPropertyValues(def, properties); + properties.putAll(getDefaultProperties(def)); // invokeOnAddAspect(nodeRef, def.getName()); // recurse addDefaultAspects(def, path, properties); diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java index 5ef6a0b23e..5364567113 100644 --- a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java @@ -34,7 +34,6 @@ import org.alfresco.repo.avm.AVMNodeDAO; import org.alfresco.repo.avm.AVMNodeDAO.ContentUrlHandler; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.domain.ContentUrlDAO; -import org.alfresco.repo.domain.Node; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.node.db.NodeDaoService.NodePropertyHandler; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -42,7 +41,9 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; @@ -180,7 +181,7 @@ public class ContentStoreCleaner // Handler that records the URL final NodePropertyHandler nodePropertyHandler = new NodePropertyHandler() { - public void handle(Node node, Serializable value) + public void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value) { if (vmShutdownListener.isVmShuttingDown()) { diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java index 17b5dea477..28596d0ef7 100644 --- a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java @@ -40,7 +40,6 @@ import org.alfresco.repo.content.filestore.FileContentStore; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.Store; import org.alfresco.repo.domain.hibernate.NodeImpl; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.node.db.NodeDaoService.NodePropertyHandler; @@ -52,6 +51,7 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -186,7 +186,7 @@ public class ContentStoreCleanerScalabilityRunner extends Repository final NodePropertyHandler nodePropertyHandler = new NodePropertyHandler() { int count = 0; - public void handle(Node node, Serializable value) + public void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value) { count++; if (count % 1000 == 0) @@ -331,12 +331,9 @@ public class ContentStoreCleanerScalabilityRunner extends Repository public void makeNode(ContentData contentData) { throw new UnsupportedOperationException("Fix this method up"); -// Store store = nodeDaoService.getStore(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); -// Node node = new NodeImpl(); -// // set other required properties -// node.setStore(store); -// node.setUuid(GUID.generate()); -// node.setTypeQName(ContentModel.TYPE_CONTENT); +// StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); +// Long nodeId = nodeDaoService.newNode(storeRef, GUID.generate(), ContentModel.TYPE_CONTENT).getFirst(); +// Node node = (Node) getHibernateTemplate().get(NodeImpl.class, nodeId); // // PropertyValue propertyValue = new PropertyValue(dataTypeDefContent, contentData); // node.getProperties().put(contentQName, propertyValue); diff --git a/source/java/org/alfresco/repo/domain/AccessControlListDAO.java b/source/java/org/alfresco/repo/domain/AccessControlListDAO.java index 6c487729ab..96b31479ab 100644 --- a/source/java/org/alfresco/repo/domain/AccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/AccessControlListDAO.java @@ -25,10 +25,10 @@ package org.alfresco.repo.domain; import java.util.List; import java.util.Map; -import java.util.Set; import org.alfresco.repo.security.permissions.ACLType; import org.alfresco.repo.security.permissions.impl.AclChange; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -60,6 +60,17 @@ public interface AccessControlListDAO */ public void setAccessControlList(NodeRef nodeRef, DbAccessControlList acl); + /** + * Set the ACL on a node. + * + * @param nodeRef + * The reference to the node. + * @param aclId + * The ID of the ACL entity. + * @throws InvalidNodeRefException if the noderef is invalid + */ + public void setAccessControlList(NodeRef nodeRef, Long aclId); + /** * Update any associated ACLs * diff --git a/source/java/org/alfresco/repo/domain/StoreKey.java b/source/java/org/alfresco/repo/domain/StoreKey.java index 9d1caf6770..6eb839a3fa 100644 --- a/source/java/org/alfresco/repo/domain/StoreKey.java +++ b/source/java/org/alfresco/repo/domain/StoreKey.java @@ -26,6 +26,7 @@ package org.alfresco.repo.domain; import java.io.Serializable; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.util.EqualsHelper; /** @@ -50,6 +51,11 @@ public class StoreKey implements Serializable setIdentifier(identifier); } + public StoreKey(StoreRef storeRef) + { + this(storeRef.getProtocol(), storeRef.getIdentifier()); + } + public String toString() { return ("StoreKey[" + diff --git a/source/java/org/alfresco/repo/usage/UsageDeltaDAO.java b/source/java/org/alfresco/repo/domain/UsageDeltaDAO.java similarity index 98% rename from source/java/org/alfresco/repo/usage/UsageDeltaDAO.java rename to source/java/org/alfresco/repo/domain/UsageDeltaDAO.java index d392531fa5..431cc04533 100644 --- a/source/java/org/alfresco/repo/usage/UsageDeltaDAO.java +++ b/source/java/org/alfresco/repo/domain/UsageDeltaDAO.java @@ -22,7 +22,7 @@ * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ -package org.alfresco.repo.usage; +package org.alfresco.repo.domain; import java.util.Set; diff --git a/source/java/org/alfresco/repo/domain/hibernate/AVMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/hibernate/AVMAccessControlListDAO.java index e0bb14810d..b1bf0967bd 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/AVMAccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/hibernate/AVMAccessControlListDAO.java @@ -253,6 +253,11 @@ public class AVMAccessControlListDAO implements AccessControlListDAO } } + public void setAccessControlList(NodeRef nodeRef, Long aclId) + { + throw new UnsupportedOperationException("Not implemented for AVM: setAccessControlList(NodeRef nodeRef, Long aclId)"); + } + public void updateChangedAcls(NodeRef startingPoint, List changes) { // If their are no actual changes there is nothing to do (the changes are all in TX and have already COWed so they can just change) diff --git a/source/java/org/alfresco/repo/domain/hibernate/AbstractPermissionsDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/hibernate/AbstractPermissionsDaoComponentImpl.java index 96e8a5026b..e832187bd1 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/AbstractPermissionsDaoComponentImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/AbstractPermissionsDaoComponentImpl.java @@ -641,8 +641,6 @@ public abstract class AbstractPermissionsDaoComponentImpl implements Permissions return npe; } - - public AccessControlListProperties getAccessControlListProperties(NodeRef nodeRef) { DbAccessControlList acl = getACLDAO(nodeRef).getAccessControlList(nodeRef); diff --git a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java index ec1095342d..098bfb0781 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java @@ -67,7 +67,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable refReadLock = lock.readLock(); refWriteLock = lock.writeLock(); - setIndex(Integer.MAX_VALUE); // comes last + index = -1; // The index is irrelevant } public void buildAssociation(Node parentNode, Node childNode) diff --git a/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java index f056530169..8ebc99dcb3 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java @@ -29,9 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.alfresco.repo.avm.AVMNodeService; import org.alfresco.repo.domain.AccessControlListDAO; -import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.DbAccessControlList; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.hibernate.AVMAccessControlListDAO.CounterSet; @@ -47,6 +45,7 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.Pair; /** * DAO layer for the improved ACL implemtentation. This layer is responsible for setting ACLs and any cascade behaviour @@ -111,14 +110,21 @@ public class DMAccessControlListDAO implements AccessControlListDAO { // Nothing to do } - - public DbAccessControlList getAccessControlList(NodeRef nodeRef) + + private Node getNodeNotNull(NodeRef nodeRef) { - Node node = nodeDaoService.getNode(nodeRef); - if (node == null) + Pair nodePair = nodeDaoService.getNodePair(nodeRef); + if (nodePair == null) { throw new InvalidNodeRefException(nodeRef); } + Node node = (Node) hibernateSessionHelper.getHibernateTemplate().get(NodeImpl.class, nodePair.getFirst()); + return node; + } + + public DbAccessControlList getAccessControlList(NodeRef nodeRef) + { + Node node = getNodeNotNull(nodeRef); return node.getAccessControlList(); } @@ -134,19 +140,20 @@ public class DMAccessControlListDAO implements AccessControlListDAO public Long getInheritedAcl(NodeRef nodeRef) { - Node node = nodeDaoService.getNode(nodeRef); - ChildAssoc ca = nodeDaoService.getPrimaryParentAssoc(node); - if ((ca != null) && (ca.getParent() != null)) + Pair nodePair = nodeDaoService.getNodePair(nodeRef); + if (nodePair == null) { - DbAccessControlList acl = getAccessControlList(ca.getParent().getNodeRef()); - if (acl != null) - { - return acl.getId(); - } - else - { - return null; - } + return null; + } + Pair parentAssocRefPair = nodeDaoService.getPrimaryParentAssoc(nodePair.getFirst()); + if (parentAssocRefPair == null || parentAssocRefPair.getSecond().getParentRef() == null) + { + return null; + } + DbAccessControlList acl = getAccessControlList(parentAssocRefPair.getSecond().getParentRef()); + if (acl != null) + { + return acl.getId(); } else { @@ -273,13 +280,20 @@ public class DMAccessControlListDAO implements AccessControlListDAO return result; } + public void setAccessControlList(NodeRef nodeRef, Long aclId) + { + Node node = getNodeNotNull(nodeRef); + DbAccessControlList acl = aclDaoComponent.getDbAccessControlList(aclId); + if (acl == null) + { + throw new IllegalArgumentException("The ACL ID provided is invalid: " + aclId); + } + node.setAccessControlList(acl); + } + public void setAccessControlList(NodeRef nodeRef, DbAccessControlList acl) { - Node node = nodeDaoService.getNode(nodeRef); - if (node == null) - { - throw new InvalidNodeRefException(nodeRef); - } + Node node = getNodeNotNull(nodeRef); node.setAccessControlList(acl); } @@ -368,80 +382,5 @@ public class DMAccessControlListDAO implements AccessControlListDAO } } } - } - - /** - * Static support to set ACLs - required for use by the dbNodeService - * - * @param nodeRef - * @param mergeFrom - * @param set - * @param nodeService - * @param aclDaoComponent - * @param nodeDaoService - */ - public static void setFixedAcls(NodeRef nodeRef, Long mergeFrom, boolean set, NodeService nodeService, AclDaoComponent aclDaoComponent, NodeDaoService nodeDaoService) - { - if (nodeRef == null) - { - return; - } - else - { - if (set) - { - setAccessControlList(nodeRef, aclDaoComponent.getDbAccessControlList(mergeFrom), nodeDaoService); - } - - List children = nodeService.getChildAssocs(nodeRef); - - for (ChildAssociationRef child : children) - { - if (child.isPrimary()) - { - DbAccessControlList acl = getAccessControlList(child.getChildRef(), nodeDaoService); - - if (acl == null) - { - setFixedAcls(child.getChildRef(), mergeFrom, true, nodeService, aclDaoComponent, nodeDaoService); - } - else if (acl.getAclType() == ACLType.LAYERED) - { - throw new UnsupportedOperationException(); - } - else if (acl.getAclType() == ACLType.DEFINING) - { - @SuppressWarnings("unused") - List newChanges = aclDaoComponent.mergeInheritedAccessControlList(mergeFrom, acl.getId()); - } - else - { - setFixedAcls(child.getChildRef(), mergeFrom, true, nodeService, aclDaoComponent, nodeDaoService); - } - } - } - } - } - - private static DbAccessControlList getAccessControlList(NodeRef nodeRef, NodeDaoService nodeDaoService) - { - Node node = nodeDaoService.getNode(nodeRef); - if (node == null) - { - throw new InvalidNodeRefException(nodeRef); - } - return node.getAccessControlList(); - } - - private static void setAccessControlList(NodeRef nodeRef, DbAccessControlList acl, NodeDaoService nodeDaoService) - { - Node node = nodeDaoService.getNode(nodeRef); - if (node == null) - { - throw new InvalidNodeRefException(nodeRef); - } - node.setAccessControlList(acl); - } - } diff --git a/source/java/org/alfresco/repo/domain/hibernate/DirtySessionAnnotation.java b/source/java/org/alfresco/repo/domain/hibernate/DirtySessionAnnotation.java new file mode 100644 index 0000000000..b53bbcd2b5 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/DirtySessionAnnotation.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * 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 + * of the License, 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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain.hibernate; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation defining Hibernate session flushing and dirty marking + * + * @since 2.1.5 + * @author Derek Hulley + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DirtySessionAnnotation +{ + /** + * Method must flush before execution.
+ * Default: false + */ + boolean flushBefore() default false; + + /** + * Method must flush after execution.
+ * Default: false + */ + boolean flushAfter() default false; + + /** + * The session must be flagged as dirty after execution.
+ * Default: false + */ + boolean markDirty() default false; +} diff --git a/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java b/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java new file mode 100644 index 0000000000..51fc7d01c9 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2005-20078 Alfresco Software Limited. + * + * 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 + * of the License, 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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.repo.domain.hibernate; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.Stack; + +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.util.Pair; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.FlushMode; +import org.hibernate.Query; +import org.hibernate.Session; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * This method interceptor determines if a Hibernate flush is required and performs the + * flush if necessary. The primary purpose is to avoid the Hibernate "flush if required" checks + * that occur every time a query is made to the database - whether or not any actual modifications + * have been made to the session. + *

+ * Write methods (methods that modify the Hibernate Session) will flag the transaction as dirty. + * Methods that query the database can {@link #setQueryFlushMode(Session, Query) set the flush mode} + * without knowing whether the session is dirty or not. + *

+ * The interceptor uses the {@link DirtySessionAnnotation}. If the annotation is not used, then + * no session dirty checks will be done but a WARN message will be output. + *

+ * The flush data is kept as a transaction-local resource. For this reason, all calls must be made + * in the context of a transaction. For the same reason, the methods on the FlushData are + * not synchronized as access is only available by one thread. + *

+ * It is also possible to {@link #flushSession(Session) flush the session} manually. Using this method + * allows the dirty count to be updated properly, thus avoiding unecessary flushing. + * + * @see #setQueryFlushMode(Session, Query) + * @see #flushSession(Session) + * + * @author Derek Hulley + * @since 2.1.5 + */ +public class DirtySessionMethodInterceptor extends HibernateDaoSupport implements MethodInterceptor +{ + private static final String KEY_FLUSH_DATA = "FlushIfRequiredMethodInterceptor.FlushData"; + + private static Log logger = LogFactory.getLog(DirtySessionMethodInterceptor.class); + + /** + * Keep track of methods that have been warned about, i.e. methods that are not annotated. + */ + private static Set unannotatedMethodNames; + static + { + unannotatedMethodNames = Collections.synchronizedSet(new HashSet(0)); + } + + /** + * Data on whether the session is dirty or not. + * + * @author Derek Hulley + */ + private static class FlushData + { + private int dirtyCount; + private Stack> methodStack; + private FlushData() + { + dirtyCount = 0; + methodStack = new Stack>(); + } + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(64); + sb.append("FlushData") + .append("[dirtyCount=").append(dirtyCount) + .append(", methodStack=").append(methodStack) + .append("]"); + return sb.toString(); + } + public void incrementDirtyCount() + { + dirtyCount++; + } + public boolean isDirty() + { + return dirtyCount > 0; + } + public void resetDirtyCount() + { + dirtyCount = 0; + } + public void pushMethod(String methodName, boolean isAnnotated) + { + methodStack.push(new Pair(methodName, Boolean.valueOf(isAnnotated))); + } + public Pair popMethod() + { + return methodStack.pop(); + } + public Pair currentMethod() + { + return methodStack.peek(); + } + /** + * @return Returns true if all the methods in the method stack are annotated, + * otherwise false + */ + public boolean isStackAnnotated() + { + for (Pair stackElement : methodStack) + { + if (stackElement.getSecond().equals(Boolean.FALSE)) + { + // Found one that was not annotated + return false; + } + } + // All were annotated + return true; + } + } + + /** + * @return Returns the transaction-local flush data + */ + private static FlushData getFlushData() + { + FlushData flushData = (FlushData) AlfrescoTransactionSupport.getResource(KEY_FLUSH_DATA); + if (flushData == null) + { + flushData = new FlushData(); + AlfrescoTransactionSupport.bindResource(KEY_FLUSH_DATA, flushData); + } + return flushData; + } + + /** + * Set the query flush mode according to whether the session is dirty or not. + * + * @param session the Hibernate session + * @param query the Hibernate query that will be issued + */ + public static void setQueryFlushMode(Session session, Query query) + { + FlushData flushData = DirtySessionMethodInterceptor.getFlushData(); + + // If all the methods in the method stack are annotated, then we can adjust the query and + // play with the session + if (!flushData.isStackAnnotated()) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Method stack is not annotated. Not setting query flush mode: \n" + + " Flush Data: " + flushData); + } + return; + } + + // The stack is fully annotated, so flush if required and set the flush mode on the query + if (logger.isDebugEnabled()) + { + logger.debug( + "Setting query flush mode: \n" + + " Query: " + query.getQueryString() + "\n" + + " Dirty: " + flushData); + } + + if (flushData.isDirty()) + { + // Flush the session + session.flush(); + // Reset the dirty state + flushData.resetDirtyCount(); + } + // Adjust the query flush mode + query.setFlushMode(FlushMode.MANUAL); + } + + /** + * Flush and reset the dirty count for the current transaction. The session is + * only flushed if it currently dirty. + * + * @param session the Hibernate session + */ + public static void flushSession(Session session) + { + flushSession(session, false); + } + + /** + * Flush and reset the dirty count for the current transaction. + * Use this one if you know that the session has changeds that might not + * have been recorded by the DAO interceptors. + * + * @param session the Hibernate session + * @param force true to force a flush. + */ + public static void flushSession(Session session, boolean force) + { + FlushData flushData = DirtySessionMethodInterceptor.getFlushData(); + if (force) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Flushing session forcefully: \n" + + " Dirty: " + flushData); + } + session.flush(); + flushData.resetDirtyCount(); + } + else + { + if (flushData.isDirty()) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Flushing dirty session: \n" + + " Dirty: " + flushData); + } + session.flush(); + flushData.resetDirtyCount(); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Session is not dirty - no flush: \n" + + " Dirty: " + flushData); + } + } + } + } + + /** Default constructor */ + public DirtySessionMethodInterceptor() + { + } + + public Object invoke(MethodInvocation invocation) throws Throwable + { + Method method = invocation.getMethod(); + String methodName = method.getName(); + + // Get the flush and dirty mark requirements for the call + DirtySessionAnnotation annotation = method.getAnnotation(DirtySessionAnnotation.class); + boolean flushBefore = false; + boolean flushAfter = false; + boolean markDirty = false; + if (annotation != null) + { + flushBefore = annotation.flushBefore(); + flushAfter = annotation.flushAfter(); + markDirty = annotation.markDirty(); + } + else if (unannotatedMethodNames.add(methodName)) + { + logger.warn("Method has not been annotated with the DirtySessionAnnotation: " + method); + } + + FlushData flushData = DirtySessionMethodInterceptor.getFlushData(); + + Session session = null; + if (flushBefore || flushAfter) + { + session = getSession(false); + } + + if (flushBefore) + { + DirtySessionMethodInterceptor.flushSession(session); + } + + boolean isAnnotated = (annotation != null); + Object ret = null; + try + { + // Push the method onto the stack + flushData.pushMethod(methodName, isAnnotated); + + if (logger.isDebugEnabled()) + { + logger.debug( + "Flush state and parameters for DirtySessionInterceptor: \n" + + " Method: " + methodName + "\n" + + " Annotated: BEFORE=" + flushBefore + ", AFTER=" + flushAfter + ", MARK-DIRTY=" + markDirty + "\n" + + " Session State: " + flushData); + } + + // Do the call + ret = invocation.proceed(); + + if (flushAfter) + { + DirtySessionMethodInterceptor.flushSession(session); + } + else if (markDirty) + { + flushData.incrementDirtyCount(); + } + } + finally + { + // Restore the dirty session awareness state + flushData.popMethod(); + } + + // Done + return ret; + } +} diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java index 26d5d75a78..87db219475 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java @@ -700,4 +700,48 @@ public class HibernateNodeTest extends BaseSpringTest count++; } } + + private static final String GET_NODE = + " select"+ + " node" + + " from" + + " org.alfresco.repo.domain.hibernate.NodeImpl as node" + + " where" + + " node.id in (:nodeIds)"; + @SuppressWarnings("unchecked") + public void testPropertiesViaJoin() throws Exception + { + getSession().setCacheMode(CacheMode.IGNORE); + + List nodeIds = new ArrayList(10); + + for (int i = 0; i < 100; i++) + { + // make a container node + Node node = new NodeImpl(); + node.setStore(store); + node.setUuid(GUID.generate()); + node.setTypeQName(containerQNameEntity); + node.getProperties().put(propAuthorQNameEntity.getId(), new PropertyValue(DataTypeDefinition.TEXT, "ABC")); + node.getProperties().put(propArchivedByQNameEntity.getId(), new PropertyValue(DataTypeDefinition.TEXT, "ABC")); + Long nodeId = (Long) getSession().save(node); + // Keep the ID + nodeIds.add(nodeId); + } + getSession().flush(); + getSession().clear(); + + // Now select it + Query query = getSession() + .createQuery(GET_NODE) + .setParameterList("nodeIds", nodeIds) + .setCacheMode(CacheMode.IGNORE); + List queryList = (List) query.list(); + + for (Node node : queryList) + { + // Get the node properties - this should not execute a query to retrieve + node.getProperties().size(); + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateUsageDeltaDAO.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateUsageDeltaDAO.java index ba66aa706f..f5973fbbbb 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/HibernateUsageDeltaDAO.java +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateUsageDeltaDAO.java @@ -30,12 +30,13 @@ import java.util.Set; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.UsageDelta; +import org.alfresco.repo.domain.UsageDeltaDAO; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.transaction.TransactionalDao; -import org.alfresco.repo.usage.UsageDeltaDAO; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.GUID; +import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.hibernate.Query; import org.hibernate.Session; @@ -130,22 +131,22 @@ public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements Usage getSession().flush(); } - private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException + private Long getNodeIdNotNull(NodeRef nodeRef) throws InvalidNodeRefException { ParameterCheck.mandatory("nodeRef", nodeRef); - Node unchecked = nodeDaoService.getNode(nodeRef); - if (unchecked == null) + Pair nodePair = nodeDaoService.getNodePair(nodeRef); + if (nodePair == null) { throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); } - return unchecked; + return nodePair.getFirst(); } public int deleteDeltas(NodeRef nodeRef) { - Node node = getNodeNotNull(nodeRef); - return deleteDeltas(node.getId()); + Long nodeId = getNodeIdNotNull(nodeRef); + return deleteDeltas(nodeId); } @SuppressWarnings("unchecked") @@ -170,13 +171,13 @@ public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements Usage @SuppressWarnings("unchecked") public long getTotalDeltaSize(NodeRef nodeRef) { - final Node node = getNodeNotNull(nodeRef); + final Long nodeId = getNodeIdNotNull(nodeRef); HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { Query query = session.getNamedQuery(QUERY_GET_TOTAL_DELTA_SIZE); - query.setParameter("node", node); + query.setParameter("nodeId", nodeId); query.setReadOnly(true); return query.uniqueResult(); } @@ -189,7 +190,8 @@ public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements Usage public void insertDelta(NodeRef usageNodeRef, long deltaSize) { - Node node = getNodeNotNull(usageNodeRef); + Long nodeId = getNodeIdNotNull(usageNodeRef); + Node node = (Node) getHibernateTemplate().get(NodeImpl.class, nodeId); UsageDelta delta = new UsageDeltaImpl(); // delta properties diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index c9bee9c0a9..9fa2a14a65 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -28,7 +28,7 @@ name="store" class="org.alfresco.repo.domain.hibernate.StoreImpl" not-null="true" - lazy="proxy" + lazy="proxy" foreign-key="fk_alf_n_store" optimistic-lock="true" fetch="join"> @@ -157,20 +157,34 @@ - - + + not-null="true" > + + + + + + + - - - - @@ -243,17 +243,17 @@ not-null="true" > - - + + @@ -282,12 +282,37 @@ from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc where - assoc.child = :child + assoc.child.id = :childId order by assoc.index, assoc.id + + delete + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + where + assoc.child.id = :childId + + + + delete + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + where + assoc.parent.id = :parentId + + + + delete + from + org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc + where + assoc.source.id = :nodeId or + assoc.target.id = :nodeId + + select status @@ -296,9 +321,21 @@ org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.child as child where - assoc.parent = :parent and + assoc.parent.id = :parentId and assoc.isPrimary = true and - status.node = child + status.node.id = childId + + + + select + child.id + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.child as child + where + assoc.parent.id = :parentId + order by + child.id @@ -307,8 +344,8 @@ from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc where - assoc.parent = :parent and - assoc.child = :child and + assoc.parent.id = :parentId and + assoc.child.id = :childId and assoc.typeQName = :typeQName and assoc.qnameNamespace = :qnameNamespace and assoc.qnameLocalName = :qnameLocalName @@ -323,7 +360,7 @@ from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc where - assoc.parent = :parent + assoc.parent.id = :parentId order by assoc.index, assoc.id @@ -335,7 +372,7 @@ from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc where - assoc.parent = :parent and + assoc.parent.id = :parentId and assoc.typeQName = :typeQName and assoc.childNodeName = :childNodeName and assoc.childNodeNameCrc = :childNodeNameCrc @@ -347,7 +384,7 @@ from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc where - assoc.parent = :parent and + assoc.parent.id = :parentId and assoc.typeQName = :typeQName and assoc.childNodeName = :childNodeName and assoc.childNodeNameCrc = :childNodeNameCrc @@ -358,6 +395,7 @@ select + assoc.id, assoc.typeQName, assoc.qnameNamespace, assoc.qnameLocalName, @@ -372,7 +410,7 @@ join assoc.parent as parent join assoc.child as child where - assoc.parent = :parent + assoc.parent.id = :parentId order by assoc.index, assoc.id @@ -380,6 +418,7 @@ select + assoc.id, assoc.typeQName, assoc.qnameNamespace, assoc.qnameLocalName, @@ -394,7 +433,7 @@ join assoc.parent as parent join assoc.child as child where - assoc.parent = :parent and + assoc.parent.id = :parentId and assoc.qnameNamespace = :qnameNamespace and assoc.qnameLocalName = :qnameLocalName order by @@ -402,14 +441,152 @@ assoc.id + + select + assoc.id, + assoc.typeQName, + assoc.qnameNamespace, + assoc.qnameLocalName, + assoc.isPrimary, + assoc.index, + child.id, + child.store.key.protocol, + child.store.key.identifier, + child.uuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + where + assoc.parent.id = :parentId and + assoc.typeQName.id in (:childAssocTypeQNameIds) + order by + assoc.index, + assoc.id + + + + select + assoc.id, + assoc.typeQName, + assoc.qnameNamespace, + assoc.qnameLocalName, + assoc.isPrimary, + assoc.index, + child.id, + child.store.key.protocol, + child.store.key.identifier, + child.uuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + where + assoc.parent.id = :parentId and + assoc.typeQName = :typeQName and + assoc.qnameNamespace = :qnameNamespace and + assoc.qnameLocalName = :qnameLocalName + order by + assoc.index, + assoc.id + + + + select + assoc.id, + assoc.typeQName, + assoc.qnameNamespace, + assoc.qnameLocalName, + assoc.isPrimary, + assoc.index, + child.id, + child.store.key.protocol, + child.store.key.identifier, + child.uuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + where + assoc.parent.id = :parentId and + assoc.isPrimary = true + order by + assoc.index, + assoc.id + + + + select + assoc.id, + assoc.typeQName, + assoc.qnameNamespace, + assoc.qnameLocalName, + assoc.isPrimary, + assoc.index, + child.id, + child.store.key.protocol, + child.store.key.identifier, + child.uuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + where + assoc.parent.id = :parentId and + assoc.isPrimary = true and + ( + child.store.key.protocol != parent.store.key.protocol or + child.store.key.identifier != parent.store.key.identifier + ) + order by + assoc.index, + assoc.id + + + + select + parent.id, + parent.store.key.protocol, + parent.store.key.identifier, + parent.uuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + where + parent.id > :minNodeId and + assoc.isPrimary = true and + ( + child.store.key.protocol != parent.store.key.protocol or + child.store.key.identifier != parent.store.key.identifier + ) + order by + parent.id + + + + select + node.id, + node.store.key.protocol, + node.store.key.identifier, + node.uuid + from + org.alfresco.repo.domain.hibernate.NodeImpl as node + where + node.id > :minNodeId and + node.aspects.id = :aspectQName + order by + node.id + + select assoc from org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc where - assoc.source = :source and - assoc.target = :target and + assoc.source.id = :sourceId and + assoc.target.id = :targetId and assoc.typeQName = :assocTypeQName @@ -419,8 +596,8 @@ from org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc where - assoc.source = :node or - assoc.target = :node + assoc.source.id = :nodeId or + assoc.target.id = :nodeId @@ -431,7 +608,7 @@ join assoc.source as source join assoc.target as target where - assoc.source = :source + assoc.source.id = :sourceId @@ -442,7 +619,21 @@ join assoc.source as source join assoc.target as target where - assoc.target = :target + assoc.target.id = :targetId + + + + select + node, + node.typeQName + from + org.alfresco.repo.domain.hibernate.NodeImpl as node + join node.properties prop + where + node.store.key.protocol = :protocol and + node.store.key.identifier = :identifier and + index(prop) = :propQNameId and + prop.stringValue = :propStringValue @@ -545,17 +736,4 @@ node.store.key.identifier = :identifier - - select - node - from - org.alfresco.repo.domain.hibernate.NodeImpl as node - join node.properties prop - where - node.store.key.protocol = :protocol and - node.store.key.identifier = :identifier and - index(prop) = :propQNameId and - prop.stringValue = :propStringValue - - diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/hibernate/NodeAccessControlListDAO.java index f25f94acf4..a1355854e3 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeAccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeAccessControlListDAO.java @@ -28,22 +28,23 @@ import java.util.List; import java.util.Map; import org.alfresco.repo.domain.AccessControlListDAO; -import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.DbAccessControlList; -import org.alfresco.repo.domain.Node; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.security.permissions.ACLType; import org.alfresco.repo.security.permissions.impl.AclChange; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.Pair; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** * The Node implementation for getting and setting ACLs. * * @author britt */ -public class NodeAccessControlListDAO implements AccessControlListDAO +public class NodeAccessControlListDAO extends HibernateDaoSupport implements AccessControlListDAO { /** * The DAO for Nodes. @@ -62,41 +63,38 @@ public class NodeAccessControlListDAO implements AccessControlListDAO fNodeDAOService = nodeDAOService; } - /** - * Get the ACL from a node. - * - * @param nodeRef - * The reference to the node. - * @return The ACL. - * @throws InvalidNodeRefException - */ + private Pair getNodePairNotNull(NodeRef nodeRef) + { + Pair nodePair = fNodeDAOService.getNodePair(nodeRef); + if (nodePair == null) + { + throw new InvalidNodeRefException(nodeRef); + } + return nodePair; + } + public DbAccessControlList getAccessControlList(NodeRef nodeRef) { - Node node = fNodeDAOService.getNode(nodeRef); - if (node == null) + Pair nodePair = getNodePairNotNull(nodeRef); + Long aclId = fNodeDAOService.getNodeAccessControlList(nodePair.getFirst()); + // Now get the entity + DbAccessControlList acl; + if (aclId == null) { - throw new InvalidNodeRefException(nodeRef); + return null; } - return node.getAccessControlList(); + else + { + acl = (DbAccessControlList) getHibernateTemplate().get(DbAccessControlListImpl.class, aclId); + } + return acl; } - /** - * Set the ACL on a node. - * - * @param nodeRef - * The reference to the node. - * @param acl - * The ACL. - * @throws InvalidNodeRefException - */ public void setAccessControlList(NodeRef nodeRef, DbAccessControlList acl) { - Node node = fNodeDAOService.getNode(nodeRef); - if (node == null) - { - throw new InvalidNodeRefException(nodeRef); - } - node.setAccessControlList(acl); + Pair nodePair = getNodePairNotNull(nodeRef); + Long aclId = (acl == null) ? null : acl.getId(); + fNodeDAOService.setNodeAccessControlList(nodePair.getFirst(), aclId); } public void updateChangedAcls(NodeRef startingPoint, List changes) @@ -112,24 +110,22 @@ public class NodeAccessControlListDAO implements AccessControlListDAO public Long getIndirectAcl(NodeRef nodeRef) { - return getAccessControlList(nodeRef).getId(); + DbAccessControlList acl = getAccessControlList(nodeRef); + return (acl == null) ? null : acl.getId(); } public Long getInheritedAcl(NodeRef nodeRef) { - Node node = fNodeDAOService.getNode(nodeRef); - ChildAssoc ca = fNodeDAOService.getPrimaryParentAssoc(node); - if ((ca != null) && (ca.getParent() != null)) + Pair nodePair = fNodeDAOService.getNodePair(nodeRef); + if (nodePair == null) { - DbAccessControlList acl = getAccessControlList(ca.getParent().getNodeRef()); - if (acl != null) - { - return acl.getId(); - } - else - { - return null; - } + throw new InvalidNodeRefException(nodeRef); + } + Pair caPair = fNodeDAOService.getPrimaryParentAssoc(nodePair.getFirst()); + if ((caPair != null) && (caPair.getSecond().getParentRef() != null)) + { + Long aclId = fNodeDAOService.getNodeAccessControlList(caPair.getFirst()); + return aclId; } else { @@ -157,5 +153,8 @@ public class NodeAccessControlListDAO implements AccessControlListDAO throw new UnsupportedOperationException(); } - + public void setAccessControlList(NodeRef nodeRef, Long aclId) + { + throw new UnsupportedOperationException(); + } } diff --git a/source/java/org/alfresco/repo/domain/hibernate/OldADMPermissionsDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/hibernate/OldADMPermissionsDaoComponentImpl.java index 751818699d..1455b00522 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/OldADMPermissionsDaoComponentImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/OldADMPermissionsDaoComponentImpl.java @@ -93,7 +93,7 @@ public class OldADMPermissionsDaoComponentImpl extends AbstractPermissionsDaoCom if (acl != null) { // maintain referencial integrity - getACLDAO(nodeRef).setAccessControlList(nodeRef, null); + getACLDAO(nodeRef).setAccessControlList(nodeRef, (Long) null); aclDaoComponent.deleteAccessControlList(acl.getId()); } } diff --git a/source/java/org/alfresco/repo/domain/hibernate/SessionSizeResourceManager.java b/source/java/org/alfresco/repo/domain/hibernate/SessionSizeResourceManager.java index c7bf26100c..6829593164 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/SessionSizeResourceManager.java +++ b/source/java/org/alfresco/repo/domain/hibernate/SessionSizeResourceManager.java @@ -144,7 +144,7 @@ public class SessionSizeResourceManager extends HibernateDaoSupport implements M int collectionCount = stats.getCollectionCount(); if ((entityCount + collectionCount) > threshold) { - session.flush(); + DirtySessionMethodInterceptor.flushSession(session, true); selectivelyClear(session, stats); // session.clear(); if (logger.isDebugEnabled()) diff --git a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java index d9ca8b0f78..a0c83f2d6a 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java @@ -59,7 +59,7 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se StringBuilder sb = new StringBuilder(50); sb.append("Transaction") .append("[id=").append(id) - .append(", txnTimeMs=").append(new Date(commitTimeMs)) + .append(", txnTimeMs=").append(commitTimeMs == null ? "---" : new Date(commitTimeMs)) .append(", changeTxnId=").append(changeTxnId) .append("]"); return sb.toString(); diff --git a/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml index e332309765..4a24851750 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml @@ -54,7 +54,7 @@ from org.alfresco.repo.domain.hibernate.UsageDeltaImpl as usage_delta where - usage_delta.node = :node + usage_delta.node.id = :nodeId @@ -73,7 +73,7 @@ from org.alfresco.repo.domain.hibernate.UsageDeltaImpl as usage_delta where - usage_delta.node = :node + usage_delta.node.id = :nodeId diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 69fac54219..86ee356f9d 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.PropertyValue; @@ -60,6 +61,8 @@ import org.alfresco.repo.policy.AssociationPolicyDelegate; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; @@ -75,7 +78,9 @@ import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; +import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -102,6 +107,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService /** controls policy delegates */ private PolicyComponent policyComponent; protected DictionaryService dictionaryService; + protected TransactionService transactionService; /* * Policy delegates @@ -147,6 +153,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService this.dictionaryService = dictionaryService; } + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + /** * Checks equality by type and uuid */ @@ -564,8 +575,10 @@ public abstract class AbstractNodeServiceImpl implements NodeService } else { - // remove the property as we don't want to persist it - preCreationProperties.remove(ContentModel.PROP_NODE_UUID); + if (uuid.length() > 50) + { + throw new IllegalArgumentException("Explicit UUID may not be greater than 50 characters: " + uuid); + } } // done return uuid; @@ -701,21 +714,28 @@ public abstract class AbstractNodeServiceImpl implements NodeService e); } } + + protected Map getDefaultProperties(QName typeQName) + { + ClassDefinition classDefinition = this.dictionaryService.getClass(typeQName); + if (classDefinition == null) + { + return Collections.emptyMap(); + } + return getDefaultProperties(classDefinition); + } + /** * Sets the default property values * * @param classDefinition the model type definition for which to get defaults * @param properties the properties of the node */ - protected void addDefaultPropertyValues(ClassDefinition classDefinition, Map properties) + protected Map getDefaultProperties(ClassDefinition classDefinition) { + PropertyMap properties = new PropertyMap(); for (Map.Entry entry : classDefinition.getDefaultValues().entrySet()) { - if (properties.containsKey(entry.getKey())) - { - // property is present - continue; - } Serializable value = entry.getValue(); // Check the type of the default property @@ -747,5 +767,49 @@ public abstract class AbstractNodeServiceImpl implements NodeService // Set the default value of the property properties.put(entry.getKey(), value); } + return properties; + } + + /** + * Override to implement cleanup processes. The default does nothing. + *

+ * This method will be called as the system user but without any + * additional transactions. + */ + protected List cleanupImpl() + { + // No operation + return Collections.emptyList(); + } + + /** Prevent multiple executions of the implementation method */ + private ReentrantLock cleanupLock = new ReentrantLock(); + public final List cleanup() + { + boolean locked = cleanupLock.tryLock(); + if (locked) + { + try + { + // Authenticate as system + RunAsWork> cleanupWork = new RunAsWork>() + { + public List doWork() throws Exception + { + // The current thread got the lock + return cleanupImpl(); + } + }; + return AuthenticationUtil.runAs(cleanupWork, AuthenticationUtil.SYSTEM_USER_NAME); + } + finally + { + cleanupLock.unlock(); + } + } + else + { + return Collections.emptyList(); + } } } diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 1150f7869a..81227b6f05 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -248,6 +248,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest *

      *      return (NodeService) applicationContext.getBean("dbNodeService");
      * 
+ * The NodeService returned must support cascade deletion. * * @return Returns the implementation of NodeService to be * used for this test. It must have transaction demarcation. @@ -674,9 +675,9 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest nodeService.removeAspect(sourceNodeRef, ASPECT_WITH_ASSOCIATIONS); // Check that the associations were removed - assertEquals("Expected exactly one child", + assertEquals("Expected exactly zero child", 0, nodeService.getChildAssocs(sourceNodeRef).size()); - assertEquals("Expected exactly one target", + assertEquals("Expected exactly zero target", 0, nodeService.getTargetAssocs(sourceNodeRef, RegexQNamePattern.MATCH_ALL).size()); } diff --git a/source/java/org/alfresco/repo/node/FullNodeServiceTest.java b/source/java/org/alfresco/repo/node/FullNodeServiceTest.java index 2258edd0b2..8b7986f358 100644 --- a/source/java/org/alfresco/repo/node/FullNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/FullNodeServiceTest.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.util.Locale; import java.util.Map; +import org.alfresco.repo.node.db.DbNodeServiceImpl; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; @@ -45,6 +46,10 @@ public class FullNodeServiceTest extends BaseNodeServiceTest { protected NodeService getNodeService() { + // Force cascading + DbNodeServiceImpl dbNodeServiceImpl = (DbNodeServiceImpl) applicationContext.getBean("dbNodeServiceImpl"); + dbNodeServiceImpl.setCascadeInTransaction(true); + return (NodeService) applicationContext.getBean("NodeService"); } diff --git a/source/java/org/alfresco/repo/node/archive/ArchiveAndRestoreTest.java b/source/java/org/alfresco/repo/node/archive/ArchiveAndRestoreTest.java index a30192e821..46b4b3d7d9 100644 --- a/source/java/org/alfresco/repo/node/archive/ArchiveAndRestoreTest.java +++ b/source/java/org/alfresco/repo/node/archive/ArchiveAndRestoreTest.java @@ -38,6 +38,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager; import org.alfresco.repo.node.StoreArchiveMap; import org.alfresco.repo.node.archive.RestoreNodeReport.RestoreStatus; +import org.alfresco.repo.node.db.DbNodeServiceImpl; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.ServiceRegistry; @@ -109,6 +110,10 @@ public class ArchiveAndRestoreTest extends TestCase @Override public void setUp() throws Exception { + // Force cascading + DbNodeServiceImpl dbNodeServiceImpl = (DbNodeServiceImpl) ctx.getBean("dbNodeServiceImpl"); + dbNodeServiceImpl.setCascadeInTransaction(true); + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); nodeArchiveService = (NodeArchiveService) ctx.getBean("nodeArchiveService"); nodeService = serviceRegistry.getNodeService(); @@ -349,8 +354,8 @@ public class ArchiveAndRestoreTest extends TestCase // check verifyNodeExistence(b, true); verifyNodeExistence(bb, false); - verifyChildAssocExistence(childAssocAtoBB, false); - verifyChildAssocExistence(childAssocBtoBB, false); +// verifyChildAssocExistence(childAssocAtoBB, false); +// verifyChildAssocExistence(childAssocBtoBB, false); verifyNodeExistence(b_, false); verifyNodeExistence(bb_, true); @@ -375,12 +380,12 @@ public class ArchiveAndRestoreTest extends TestCase // check verifyNodeExistence(b, false); verifyNodeExistence(bb, false); - verifyChildAssocExistence(childAssocAtoBB, false); - verifyTargetAssocExistence(assocAtoB, false); - verifyTargetAssocExistence(assocAAtoBB, false); +// verifyChildAssocExistence(childAssocAtoBB, false); +// verifyTargetAssocExistence(assocAtoB, false); +// verifyTargetAssocExistence(assocAAtoBB, false); verifyNodeExistence(b_, true); verifyNodeExistence(bb_, true); - verifyChildAssocExistence(childAssocBtoBB_, true); +// verifyChildAssocExistence(childAssocBtoBB_, true); // flush //AlfrescoTransactionSupport.flush(); @@ -443,10 +448,10 @@ public class ArchiveAndRestoreTest extends TestCase verifyNodeExistence(bb, true); verifyChildAssocExistence(childAssocAtoAA, true); verifyChildAssocExistence(childAssocBtoBB, true); - verifyChildAssocExistence(childAssocAtoBB, false); - verifyChildAssocExistence(childAssocBtoAA, false); - verifyTargetAssocExistence(assocAtoB, false); - verifyTargetAssocExistence(assocAAtoBB, false); +// verifyChildAssocExistence(childAssocAtoBB, false); +// verifyChildAssocExistence(childAssocBtoAA, false); +// verifyTargetAssocExistence(assocAtoB, false); +// verifyTargetAssocExistence(assocAAtoBB, false); verifyNodeExistence(a_, false); verifyNodeExistence(b_, false); verifyNodeExistence(aa_, false); diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 0be8ddfc8b..31020c5e1b 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -39,26 +39,15 @@ 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.DbAccessControlList; import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.QNameDAO; -import org.alfresco.repo.domain.QNameEntity; -import org.alfresco.repo.domain.Store; -import org.alfresco.repo.domain.hibernate.DMAccessControlListDAO; -import org.alfresco.repo.domain.hibernate.DMPermissionsDaoComponentImpl; import org.alfresco.repo.node.AbstractNodeServiceImpl; import org.alfresco.repo.node.StoreArchiveMap; +import org.alfresco.repo.node.db.NodeDaoService.NodeRefQueryCallback; +import org.alfresco.repo.node.index.NodeIndexer; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.ACLType; -import org.alfresco.repo.security.permissions.AccessControlListProperties; -import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties; -import org.alfresco.repo.security.permissions.impl.AclDaoComponent; -import org.alfresco.repo.security.permissions.impl.PermissionsDaoComponent; -import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; @@ -78,17 +67,19 @@ 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.cmr.repository.datatype.DefaultTypeConverter; 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.EqualsHelper; +import org.alfresco.util.GUID; +import org.alfresco.util.Pair; 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; /** @@ -99,32 +90,18 @@ import org.springframework.util.Assert; public class DbNodeServiceImpl extends AbstractNodeServiceImpl { private static Log logger = LogFactory.getLog(DbNodeServiceImpl.class); - private static Log loggerPaths = LogFactory.getLog(DbNodeServiceImpl.class.getName() + ".paths"); - - private QNameDAO qnameDAO; - + private NodeDaoService nodeDaoService; - private StoreArchiveMap storeArchiveMap; - private NodeService avmNodeService; - - private TenantService tenantService; - - private AclDaoComponent aclDaoComponent; - + private NodeIndexer nodeIndexer; + private boolean cascadeInTransaction; + public DbNodeServiceImpl() { - storeArchiveMap = new StoreArchiveMap(); // in case it is not set - } - - /** - * Set the component for creating QName entities. - */ - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; + storeArchiveMap = new StoreArchiveMap(); // in case it is not set + cascadeInTransaction = true; } public void setNodeDaoService(NodeDaoService nodeDaoService) @@ -142,89 +119,66 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl this.avmNodeService = avmNodeService; } - public void setTenantService(TenantService tenantService) + /** + * @param nodeIndexer the indexer that will be notified of node additions, + * modifications and deletions + */ + public void setNodeIndexer(NodeIndexer nodeIndexer) { - this.tenantService = tenantService; + this.nodeIndexer = nodeIndexer; } - public void setAclDaoComponent(AclDaoComponent aclDaoComponent) + /** + * Set whether store delete and archive operations must cascade to all children + * in the same transaction. + * + * @param cascadeInTransaction true (default) to cascade during + * delete and archive + */ + public void setCascadeInTransaction(boolean cascadeInTransaction) { - this.aclDaoComponent = aclDaoComponent; + this.cascadeInTransaction = cascadeInTransaction; } /** * Performs a null-safe get of the node * - * @param nodeRef - * the node to retrieve + * @param nodeRef the node to retrieve * @return Returns the node entity (never null) - * @throws InvalidNodeRefException - * if the referenced node could not be found + * @throws InvalidNodeRefException if the referenced node could not be found */ - private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException + private Pair getNodePairNotNull(NodeRef nodeRef) throws InvalidNodeRefException { ParameterCheck.mandatory("nodeRef", nodeRef); - - Node unchecked = nodeDaoService.getNode(nodeRef); + + Pair unchecked = nodeDaoService.getNodePair(nodeRef); if (unchecked == null) { throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); } return unchecked; } - - /** - * Gets the node status for a live node. - * - * @param nodeRef - * the node reference - * @return Returns the node status, which will not be null and will have a live node attached. - * @throws InvalidNodeRefException - * if the node is deleted or never existed - */ - public NodeStatus getNodeStatusNotNull(NodeRef nodeRef) throws InvalidNodeRefException - { - ParameterCheck.mandatory("nodeRef", nodeRef); - - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); - if (nodeStatus == null || nodeStatus.getNode() == null) - { - throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); - } - return nodeStatus; - } - + public boolean exists(StoreRef storeRef) { - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - boolean exists = (store != null); - // done - return exists; + return (nodeDaoService.getRootNode(storeRef) != null); } - + public boolean exists(NodeRef nodeRef) { ParameterCheck.mandatory("nodeRef", nodeRef); - - Node node = nodeDaoService.getNode(nodeRef); - boolean exists = (node != null); + + Pair nodePair = nodeDaoService.getNodePair(nodeRef); + boolean exists = (nodePair != null); // done return exists; } - + public Status getNodeStatus(NodeRef nodeRef) { ParameterCheck.mandatory("nodeRef", nodeRef); - - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); - if (nodeStatus == null) // node never existed - { - return null; - } - else - { - return new NodeRef.Status(nodeStatus.getTransaction().getChangeTxnId(), nodeStatus.isDeleted()); - } + NodeRef.Status status = nodeDaoService.getNodeRefStatus(nodeRef); + return status; } /** @@ -232,433 +186,318 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl */ public List getStores() { - List stores = nodeDaoService.getStores(); - List storeRefs = new ArrayList(stores.size()); - for (Store store : stores) - { - StoreRef storeRef = store.getStoreRef(); - try - { - if (tenantService.isEnabled()) - { - String currentUser = AuthenticationUtil.getCurrentUserName(); - - // MT: return tenant stores only (although for super System return all stores - as used by - // ConfigurationChecker, IndexRecovery, IndexBackup etc) - if ((currentUser == null) || (!currentUser.equals(AuthenticationUtil.getSystemUserName()))) - { - tenantService.checkDomain(storeRef.getIdentifier()); - storeRef = tenantService.getBaseName(storeRef); - } - } - - storeRefs.add(storeRef); - } - catch (RuntimeException re) - { - // deliberately ignore - stores in different domain will not be listed - } - } + // Get the ADM stores + List storeRefs = nodeDaoService.getStoreRefs(); // Now get the AVMStores. List avmStores = avmNodeService.getStores(); storeRefs.addAll(avmStores); // Return them all. return storeRefs; } - + /** * Defers to the typed service - * * @see StoreDaoService#createWorkspace(String) */ public StoreRef createStore(String protocol, String identifier) { StoreRef storeRef = new StoreRef(protocol, identifier); - - // 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(); - NodeRef rootNodeRef = tenantService.getBaseName(rootNode.getNodeRef()); - - // assign the root aspect - this is expected of all roots, even store roots - addAspect(rootNodeRef, ContentModel.ASPECT_ROOT, Collections. emptyMap()); - - // Bind root permission - - if (aclDaoComponent != null) - { - SimpleAccessControlListProperties properties = DMPermissionsDaoComponentImpl.getDefaultProperties(); - Long id = aclDaoComponent.createAccessControlList(properties); - DbAccessControlList acl = aclDaoComponent.getDbAccessControlList(id); - rootNode.setAccessControlList(acl); - } - + Pair rootNodePair = nodeDaoService.createStore(storeRef); + NodeRef rootNodeRef = rootNodePair.getSecond(); + // invoke policies invokeOnCreateStore(rootNodeRef); - - // done - if (!store.getStoreRef().equals(storeRef)) - { - throw new RuntimeException("Incorrect store reference"); - } - + + // Index + ChildAssociationRef assocRef = new ChildAssociationRef(null, null, null, rootNodeRef); + nodeIndexer.indexCreateNode(assocRef); + + // Done return storeRef; } - + /** - * @see NodeDaoService#deleteStore(String, String) + * @throws UnsupportedOperationException Always */ - public void deleteStore(StoreRef storeRef) + public void deleteStore(StoreRef storeRef) throws InvalidStoreRefException { - 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); - } - - // TODO invoke policies - e.g. tell indexer to delete index - // invokeBeforeDeleteStore(ContentModel.TYPE_STOREROOT, storeRef); - - // (hard) delete store - nodeDaoService.deleteStore(protocol, identifier); - - // done - return; + throw new UnsupportedOperationException(); } public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException { - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - if (store == null) + Pair rootNodePair = nodeDaoService.getRootNode(storeRef); + if (rootNodePair == null) { throw new InvalidStoreRefException("Store does not exist", storeRef); } - // get the root - Node node = store.getRootNode(); - if (node == null) - { - throw new InvalidStoreRefException("Store does not have a root node: " + storeRef, storeRef); - } // done - return tenantService.getBaseName(node.getNodeRef()); + return rootNodePair.getSecond(); } /** * @see #createNode(NodeRef, QName, QName, QName, Map) */ - public ChildAssociationRef createNode(NodeRef parentRef, QName assocTypeQName, QName assocQName, QName nodeTypeQName) + public ChildAssociationRef createNode( + NodeRef parentRef, + QName assocTypeQName, + QName assocQName, + QName nodeTypeQName) { return this.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) + * @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 properties) + public ChildAssociationRef createNode( + NodeRef parentRef, + QName assocTypeQName, + QName assocQName, + QName nodeTypeQName, + Map properties) { Assert.notNull(parentRef); Assert.notNull(assocTypeQName); Assert.notNull(assocQName); - + // Get the parent node - Node parentNode = getNodeNotNull(parentRef); + Pair parentNodePair = getNodePairNotNull(parentRef); + StoreRef parentStoreRef = parentRef.getStoreRef(); // null property map is allowed if (properties == null) - { - properties = new HashMap(); - } - else - { - // Copy the incomming property map since we may need to modify it later - properties = new HashMap(properties); + { + properties = Collections.emptyMap(); } + // get/generate an ID for the node + String newUuid = generateGuid(properties); + + // Remove any system properties + extractIntrinsicProperties(properties); + // Invoke policy behaviour invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName); - - // get the store that the parent belongs to - Store store = nodeDaoService.getStore(parentRef.getStoreRef().getProtocol(), parentRef.getStoreRef().getIdentifier()); - if (store == null) - { - throw new RuntimeException("No store found for parent node: " + parentRef); - } - + // check the node type TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName); if (nodeTypeDef == null) { throw new InvalidTypeException(nodeTypeQName); } - - // get/generate an ID for the node - String newId = generateGuid(properties); - + // create the node instance - Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName); - NodeRef childNodeRef = tenantService.getBaseName(childNode.getNodeRef()); + Pair childNodePair = nodeDaoService.newNode(parentStoreRef, newUuid, nodeTypeQName); // We now have enough to declare the child association creation - invokeBeforeCreateChildAssociation(parentRef, childNodeRef, assocTypeQName, assocQName, true); - + invokeBeforeCreateChildAssociation(parentRef, childNodePair.getSecond(), assocTypeQName, assocQName, true); + // Create the association - ChildAssoc childAssoc = nodeDaoService.newChildAssoc(parentNode, childNode, true, assocTypeQName, assocQName); + Pair childAssocPair = nodeDaoService.newChildAssoc( + parentNodePair.getFirst(), + childNodePair.getFirst(), + true, + assocTypeQName, + assocQName); + ChildAssociationRef childAssocRef = childAssocPair.getSecond(); - // 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 propertiesBefore = getPropertiesImpl(childNode); - Map propertiesAfter = null; + // Add defaults + addDefaults(childNodePair, nodeTypeQName); + + // set the properties passed in if (properties.size() > 0) { - propertiesAfter = setPropertiesImpl(childNode, properties); + Map propertiesConverted = convertProperties(properties); + nodeDaoService.addNodeProperties(childNodePair.getFirst(), propertiesConverted); } + + Map propertiesAfterValues = nodeDaoService.getNodeProperties(childNodePair.getFirst()); // Ensure child uniqueness - setChildUniqueName(childNode); // ensure uniqueness - ChildAssociationRef childAssocRef = tenantService.getBaseName(childAssoc.getChildAssocRef()); - - // permissions behaviour - if (aclDaoComponent != null) - { - DbAccessControlList inherited = parentNode.getAccessControlList(); - if (inherited == null) - { - // not fixde up yet or unset - } - else - { - childNode.setAccessControlList(aclDaoComponent.getDbAccessControlList(aclDaoComponent.getInheritedAccessControlList(inherited.getId()))); - } - } - + String newName = extractNameProperty(propertiesAfterValues); + // Ensure uniqueness. Note that the cm:name may be null, in which case the uniqueness is still + setChildNameUnique(childAssocPair, newName, null); // ensure uniqueness + // Invoke policy behaviour invokeOnCreateNode(childAssocRef); invokeOnCreateChildAssociation(childAssocRef, true); - if (propertiesAfter != null) - { - invokeOnUpdateProperties(childAssocRef.getChildRef(), propertiesBefore, propertiesAfter); - } - + Map propertiesAfter = convertPropertyValues(propertiesAfterValues); + addIntrinsicProperties(childNodePair, propertiesAfter); + invokeOnUpdateProperties( + childAssocRef.getChildRef(), + Collections.emptyMap(), + propertiesAfter); + + // Index + nodeIndexer.indexCreateNode(childAssocRef); + // done return childAssocRef; } /** - * Add the default aspects to a given node - * - * @param nodeTypeDef + * Adds all the default aspects and properties required for the given type. + * Existing values will not be overridden. */ - private void addDefaultAspects(ClassDefinition classDefinition, Node node, Map properties) + private void addDefaults(Pair nodePair, QName typeQName) { - NodeRef nodeRef = node.getNodeRef(); - + addDefaultProperties(nodePair, typeQName); + addDefaultAspects(nodePair, typeQName); + } + + /** + * Add the default aspects to a given node + * @return Returns true if any aspects were added + */ + private boolean addDefaultAspects(Pair nodePair, QName typeQName) + { + ClassDefinition classDefinition = dictionaryService.getClass(typeQName); + if (classDefinition == null) + { + return false; + } + // Get the existing values + Long nodeId = nodePair.getFirst(); + Map existingPropertyValues = nodeDaoService.getNodeProperties(nodeId); + Set existingAspects = nodeDaoService.getNodeAspects(nodeId); + return addDefaultAspects(nodePair, existingAspects, existingPropertyValues, typeQName); + } + + /** + * Add the default aspects to a given node + * @return Returns true if any aspects were added + */ + private boolean addDefaultAspects(Pair nodePair, Set existingAspects, Map existingPropertyValues, QName typeQName) + { + ClassDefinition classDefinition = dictionaryService.getClass(typeQName); + if (classDefinition == null) + { + return false; + } + + Long nodeId = nodePair.getFirst(); + NodeRef nodeRef = nodePair.getSecond(); + // get the mandatory aspects for the node type List defaultAspectDefs = classDefinition.getDefaultAspects(); - + // add all the aspects to the node - Set nodeAspects = node.getAspects(); - for (AspectDefinition defaultAspectDef : defaultAspectDefs) + boolean added = false; + for (AspectDefinition typeDefinition : defaultAspectDefs) { - QName defaultAspectQName = defaultAspectDef.getName(); - QNameEntity defaultAspectQNameEntity = qnameDAO.getOrCreateQNameEntity(defaultAspectDef.getName()); - invokeBeforeAddAspect(nodeRef, defaultAspectQName); - nodeAspects.add(defaultAspectQNameEntity.getId()); - addDefaultPropertyValues(defaultAspectDef, properties); - invokeOnAddAspect(nodeRef, defaultAspectQName); - + QName aspectQName = typeDefinition.getName(); + boolean existingAspect = existingAspects.contains(aspectQName); + // Only add the aspect if it isn't there + if (!existingAspect) + { + invokeBeforeAddAspect(nodeRef, aspectQName); + nodeDaoService.addNodeAspects(nodeId, Collections.singleton(aspectQName)); + added = true; + } + // Set default properties for the aspect + addDefaultProperties(nodePair, aspectQName); + if (!existingAspect) + { + // Fire policy + invokeOnAddAspect(nodeRef, aspectQName); + } + // Now add any default aspects for this aspect - addDefaultAspects(defaultAspectDef, node, properties); + boolean moreAdded = addDefaultAspects(nodePair, aspectQName); + added = (added || moreAdded); } + // Done + return added; } - + /** - * Drops the old primary association and creates a new one + * @return Returns true if any properties were added */ - public ChildAssociationRef moveNode(NodeRef nodeToMoveRef, NodeRef newParentRef, QName assocTypeQName, QName assocQName) throws InvalidNodeRefException + private boolean addDefaultProperties(Pair nodePair, QName typeQName) { - Assert.notNull(nodeToMoveRef); - Assert.notNull(newParentRef); - Assert.notNull(assocTypeQName); - Assert.notNull(assocQName); - - // check the node references - Node nodeToMove = getNodeNotNull(nodeToMoveRef); - Node newParentNode = getNodeNotNull(newParentRef); - // get the primary parent assoc - ChildAssoc oldAssoc = nodeDaoService.getPrimaryParentAssoc(nodeToMove); - ChildAssociationRef oldAssocRef = tenantService.getBaseName(oldAssoc.getChildAssocRef()); - - boolean movingStore = !nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef()); - - // data needed for policy invocation - QName nodeToMoveTypeQName = nodeToMove.getTypeQName().getQName(); - Set nodeToMoveAspects = nodeToMove.getAspects(); - - // Invoke policy behaviour - if (movingStore) + ClassDefinition classDefinition = dictionaryService.getClass(typeQName); + if (classDefinition == null) { - invokeBeforeDeleteNode(nodeToMoveRef); - invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName); + return false; } - 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(newParentNode, nodeToMove, true, assocTypeQName, assocQName); - setChildUniqueName(nodeToMove); // ensure uniqueness - ChildAssociationRef newAssocRef = tenantService.getBaseName(newAssoc.getChildAssocRef()); - - // 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); - - // Fix inherited permissions - - if (aclDaoComponent != null) - { - if (newAssoc.getChild().getAccessControlList() != null) - { - Long targetAcl = newAssoc.getChild().getAccessControlList().getId(); - AccessControlListProperties aclProperties = aclDaoComponent.getAccessControlListProperties(targetAcl); - Boolean inherits = aclProperties.getInherits(); - if ((inherits != null) && (inherits.booleanValue())) - { - if (newAssoc.getParent().getAccessControlList() != null) - { - Long parentAcl = newAssoc.getParent().getAccessControlList().getId(); - Long inheritedAcl = aclDaoComponent.getInheritedAccessControlList(parentAcl); - if (aclProperties.getAclType() == ACLType.DEFINING) - { - aclDaoComponent.enableInheritance(targetAcl, parentAcl); - } - else if (aclProperties.getAclType() == ACLType.SHARED) - { - DMAccessControlListDAO.setFixedAcls(newAssoc.getChildAssocRef().getChildRef(), inheritedAcl, true, this, aclDaoComponent, nodeDaoService); - } - } - else - { - if (aclProperties.getAclType() == ACLType.DEFINING) - { - - // there is nothing to inherit from so clear out any inherited aces - aclDaoComponent.deleteInheritedAccessControlEntries(targetAcl); - } - else if (aclProperties.getAclType() == ACLType.SHARED) - { - // there is nothing to inherit - nodeToMove.setAccessControlList(null); - } - - // throw new IllegalStateException("Share bug"); - } - } - } - else - { - if (newAssoc.getParent().getAccessControlList() != null) - { - Long parentAcl = newAssoc.getParent().getAccessControlList().getId(); - Long inheritedAcl = aclDaoComponent.getInheritedAccessControlList(parentAcl); - DMAccessControlListDAO.setFixedAcls(newAssoc.getChildAssocRef().getChildRef(), inheritedAcl, true, this, aclDaoComponent, nodeDaoService); - } - } - - } - - // invoke policy behaviour - if (movingStore) - { - Set nodeToMoveAspectQNames = new HashSet(17); - for (Long qnameEntityId : nodeToMoveAspects) - { - QName nodeToMoveAspectQName = qnameDAO.getQNameEntity(qnameEntityId).getQName(); - nodeToMoveAspectQNames.add(nodeToMoveAspectQName); - } - // 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, nodeToMoveAspectQNames, true); - invokeOnCreateNode(newAssoc.getChildAssocRef()); - } - else - { - invokeOnCreateChildAssociation(newAssoc.getChildAssocRef(), false); - invokeOnDeleteChildAssociation(oldAssoc.getChildAssocRef()); - } - invokeOnMoveNode(oldAssocRef, newAssocRef); - - // update the node status - nodeDaoService.recordChangeId(nodeToMoveRef); - - // done - return newAssoc.getChildAssocRef(); + // Get the existing values + Long nodeId = nodePair.getFirst(); + Map existingPropertyValues = nodeDaoService.getNodeProperties(nodeId); + return addDefaultProperties(nodePair, existingPropertyValues, typeQName); } - + + /** + * Adds default properties for the given type to the node. Default values will not be set if there are existing values. + */ + private boolean addDefaultProperties(Pair nodePair, Map existingPropertyValues, QName typeQName) + { + Long nodeId = nodePair.getFirst(); + // Get the default properties for this aspect + Map defaultProperties = getDefaultProperties(typeQName); + Map defaultPropertyValues = this.convertProperties(defaultProperties); + // Remove all default values where a value already exists + for (Map.Entry entry : existingPropertyValues.entrySet()) + { + QName existingPropertyQName = entry.getKey(); + PropertyValue existingPropertyValue = entry.getValue(); + if (existingPropertyValue != null) + { + defaultPropertyValues.remove(existingPropertyQName); + } + } + // Add the properties to the node - but only if there is anything to set + if (defaultPropertyValues.size() > 0) + { + nodeDaoService.addNodeProperties(nodeId, defaultPropertyValues); + return true; + } + else + { + return false; + } + } + public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index) { // get nodes - Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); - Node childNode = getNodeNotNull(childAssocRef.getChildRef()); - - ChildAssoc assoc = nodeDaoService.getChildAssoc(parentNode, childNode, childAssocRef.getTypeQName(), childAssocRef.getQName()); - if (assoc == null) + Pair parentNodePair = getNodePairNotNull(childAssocRef.getParentRef()); + Pair childNodePair = getNodePairNotNull(childAssocRef.getChildRef()); + + Long parentNodeId = parentNodePair.getFirst(); + Long childNodeId = childNodePair.getFirst(); + QName assocTypeQName = childAssocRef.getTypeQName(); + QName assocQName = childAssocRef.getQName(); + + Pair assocPair = nodeDaoService.getChildAssoc( + parentNodeId, + childNodeId, + assocTypeQName, + assocQName); + if (assocPair == null) { - throw new InvalidChildAssociationRefException("Unable to set child association index: \n" + " assoc: " + childAssocRef + "\n" + " index: " + index, childAssocRef); + 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(); + nodeDaoService.updateChildAssoc(assocPair.getFirst(), parentNodeId, childNodeId, assocTypeQName, assocQName, index); } public QName getType(NodeRef nodeRef) throws InvalidNodeRefException { - Node node = getNodeNotNull(nodeRef); - return node.getTypeQName().getQName(); + Pair nodePair = getNodePairNotNull(nodeRef); + return nodeDaoService.getNodeType(nodePair.getFirst()); } - + /** - * @see org.alfresco.service.cmr.repository.NodeService#setType(org.alfresco.service.cmr.repository.NodeRef, - * org.alfresco.service.namespace.QName) + * @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 { @@ -668,29 +507,32 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { throw new InvalidTypeException(typeQName); } - Node node = getNodeNotNull(nodeRef); - + Pair nodePair = getNodePairNotNull(nodeRef); + // Invoke policies invokeBeforeUpdateNode(nodeRef); - - // Ensure that we have a QName entity to represent the type - QNameEntity typeQNameEntity = qnameDAO.getOrCreateQNameEntity(typeQName); - // Get the node and set the new type - node.setTypeQName(typeQNameEntity); - + + // Set the type + nodeDaoService.updateNode(nodePair.getFirst(), null, null, typeQName); + // Add the default aspects to the node (update the properties with any new default values) - Map properties = this.getPropertiesImpl(node); - addDefaultAspects(nodeTypeDef, node, properties); - this.setProperties(nodeRef, properties); - + addDefaultAspects(nodePair, typeQName); + + // Index + nodeIndexer.indexUpdateNode(nodeRef); + // Invoke policies invokeOnUpdateNode(nodeRef); } - + /** * @see Node#getAspects() */ - public void addAspect(NodeRef nodeRef, QName aspectTypeQName, Map aspectProperties) throws InvalidNodeRefException, InvalidAspectException + public void addAspect( + NodeRef nodeRef, + QName aspectTypeQName, + Map aspectProperties) + throws InvalidNodeRefException, InvalidAspectException { // check that the aspect is legal AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); @@ -698,379 +540,433 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName); } - - Node node = getNodeNotNull(nodeRef); - + + // Check the properties + if (aspectProperties != null) + { + // Remove any system properties + extractIntrinsicProperties(aspectProperties); + } + else + { + // Make a map + aspectProperties = Collections.emptyMap(); + } + // Make the properties immutable to be sure that they are not used incorrectly + aspectProperties = Collections.unmodifiableMap(aspectProperties); + + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); invokeBeforeAddAspect(nodeRef, aspectTypeQName); - // attach the properties to the current node properties - Map nodeProperties = getPropertiesImpl(node); - - if (aspectProperties != null) + // Add defaults + addDefaults(nodePair, aspectTypeQName); + + if (aspectProperties.size() > 0) { - nodeProperties.putAll(aspectProperties); + Map aspectPropertyValues = convertProperties(aspectProperties); + nodeDaoService.addNodeProperties(nodeId, aspectPropertyValues); } - - // 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); - - // Get the persistale QNameEntity for the aspect - QNameEntity aspectTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(aspectTypeQName); - // physically attach the aspect to the node - if (node.getAspects().add(aspectTypeQNameEntity.getId()) == true) - { + + if (!nodeDaoService.hasNodeAspect(nodeId, aspectTypeQName)) + { // Invoke policy behaviours invokeOnUpdateNode(nodeRef); invokeOnAddAspect(nodeRef, aspectTypeQName); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); + nodeDaoService.addNodeAspects(nodeId, Collections.singleton(aspectTypeQName)); } + + // Index + nodeIndexer.indexUpdateNode(nodeRef); } - /** - * @see Node#getAspects() - */ - public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) throws InvalidNodeRefException, InvalidAspectException + public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) + throws InvalidNodeRefException, InvalidAspectException { - // get the aspect - AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); - if (aspectDef == null) - { - throw new InvalidAspectException(aspectTypeQName); - } + /** + * Note: Aspect and property removal is resilient to missing dictionary definitions + */ // get the node - Node node = getNodeNotNull(nodeRef); - Set nodeAspects = node.getAspects(); - - // Get the persistale QNameEntity for the aspect - QNameEntity aspectTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(aspectTypeQName); - if (!nodeAspects.contains(aspectTypeQNameEntity.getId())) - { - // The aspect isn't present so just leave it - return; - } - + final Pair nodePair = getNodePairNotNull(nodeRef); + final Long nodeId = nodePair.getFirst(); + + boolean hadAspect = nodeDaoService.hasNodeAspect(nodeId, aspectTypeQName); + // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); - invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); - - // remove the aspect, if present - nodeAspects.remove(aspectTypeQNameEntity.getId()); - - Map nodeProperties = node.getProperties(); - Map propertyDefs = aspectDef.getProperties(); - for (QName propertyQName : propertyDefs.keySet()) + if (hadAspect) { - QNameEntity propertyQNameEntity = qnameDAO.getOrCreateQNameEntity(propertyQName); - nodeProperties.remove(propertyQNameEntity.getId()); + invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); + nodeDaoService.removeNodeAspects(nodeId, Collections.singleton(aspectTypeQName)); } - - // Remove child associations - Map childAssocDefs = aspectDef.getChildAssociations(); - Collection childAssocs = nodeDaoService.getChildAssocs(node); - for (ChildAssoc childAssoc : childAssocs) + + AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); + boolean updated = false; + if (aspectDef != null) { - // Ignore if the association type is not defined by the aspect - QName childAssocQName = childAssoc.getTypeQName().getQName(); - if (!childAssocDefs.containsKey(childAssocQName)) + // Remove default properties + Map propertyDefs = aspectDef.getProperties(); + Set propertyToRemoveQNames = propertyDefs.keySet(); + nodeDaoService.removeNodeProperties(nodeId, propertyToRemoveQNames); + + // Remove child associations + // We have to iterate over the associations and remove all those between the parent and child + final List> assocsToDelete = new ArrayList>(5); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() { - continue; - } - // The association is of a type that should be removed - nodeDaoService.deleteChildAssoc(childAssoc, true); - } - - // Remove regular associations - Map assocDefs = aspectDef.getAssociations(); - List nodeAssocs = nodeDaoService.getTargetNodeAssocs(node); - for (NodeAssoc nodeAssoc : nodeAssocs) - { - // Ignore if the association type is not defined by the aspect - QName nodeAssocQName = nodeAssoc.getTypeQName().getQName(); - if (!assocDefs.containsKey(nodeAssocQName)) + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair + ) + { + // Add it + assocsToDelete.add(childAssocPair); + // No recurse + return false; + } + }; + // Get all the QNames to remove + List assocTypeQNamesToRemove = new ArrayList(aspectDef.getChildAssociations().keySet()); + nodeDaoService.getChildAssocsByTypeQNames(nodeId, assocTypeQNamesToRemove, callback); + // Delete all the collected associations + for (Pair assocPair : assocsToDelete) { - continue; + updated = true; + Long assocId = assocPair.getFirst(); + ChildAssociationRef assocRef = assocPair.getSecond(); + // delete the association instance - it is not primary + invokeBeforeDeleteChildAssociation(assocRef); + nodeDaoService.deleteChildAssoc(assocId); + invokeOnDeleteChildAssociation(assocRef); + } + + // Remove regular associations + Map nodeAssocDefs = aspectDef.getAssociations(); + Collection> nodeAssocPairs = nodeDaoService.getNodeAssocsToAndFrom(nodeId); + for (Pair nodeAssocPair : nodeAssocPairs) + { + updated = true; + QName nodeAssocTypeQName = nodeAssocPair.getSecond().getTypeQName(); + // Ignore if the association type is not defined by the aspect + if (!nodeAssocDefs.containsKey(nodeAssocTypeQName)) + { + continue; + } + updated = true; + // It has to be removed + nodeDaoService.deleteNodeAssoc(nodeAssocPair.getFirst()); } - // Delete the association - nodeDaoService.deleteNodeAssoc(nodeAssoc); } - + // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnRemoveAspect(nodeRef, aspectTypeQName); + if (updated) + { + invokeOnUpdateNode(nodeRef); + } + if (hadAspect) + { + invokeOnRemoveAspect(nodeRef, aspectTypeQName); + } - // update the node status - nodeDaoService.recordChangeId(nodeRef); + // Index + nodeIndexer.indexUpdateNode(nodeRef); } /** * Performs a check on the set of node aspects - * - * @see Node#getAspects() */ public boolean hasAspect(NodeRef nodeRef, QName aspectQName) throws InvalidNodeRefException, InvalidAspectException { - QNameEntity aspectQNameEntity = qnameDAO.getQNameEntity(aspectQName); - if (aspectQNameEntity == null) - { - // There is no persisted, fixed QName like this - return false; - } - Node node = getNodeNotNull(nodeRef); - Set aspectQNames = node.getAspects(); - boolean hasAspect = aspectQNames.contains(aspectQNameEntity.getId()); - // done - return hasAspect; + Pair nodePair = getNodePairNotNull(nodeRef); + return nodeDaoService.hasNodeAspect(nodePair.getFirst(), aspectQName); } public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException { - Node node = getNodeNotNull(nodeRef); - Set aspectQNameEntities = node.getAspects(); - // copy the set to ensure initialization - Set ret = new HashSet(aspectQNameEntities.size()); - for (Long aspectQNameEntityId : aspectQNameEntities) - { - QNameEntity aspectQNameEntity = qnameDAO.getQNameEntity(aspectQNameEntityId); - ret.add(aspectQNameEntity.getQName()); - } - // done - return ret; + Pair nodePair = getNodePairNotNull(nodeRef); + return nodeDaoService.getNodeAspects(nodePair.getFirst()); } public void deleteNode(NodeRef nodeRef) { - // First get the node to ensure that it exists - Node node = getNodeNotNull(nodeRef); + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); boolean requiresDelete = false; - + // Invoke policy behaviours invokeBeforeDeleteNode(nodeRef); - + // get the primary parent-child relationship before it is gone - ChildAssociationRef childAssocRef = getPrimaryParent(nodeRef); - + Pair childAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId); + ChildAssociationRef childAssocRef = childAssocPair.getSecond(); // get type and aspect QNames as they will be unavailable after the delete - QName nodeTypeQName = node.getTypeQName().getQName(); - Set nodeAspectQNameEntityIds = node.getAspects(); - - // Get QNameEntity for subsequent checks - QNameEntity aspectTempQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_TEMPORARY); - QNameEntity aspectWorkingCopyQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_WORKING_COPY); + QName nodeTypeQName = nodeDaoService.getNodeType(nodeId); + Set nodeAspectQNames = nodeDaoService.getNodeAspects(nodeId); // check if we need to archive the node StoreRef archiveStoreRef = null; - if (nodeAspectQNameEntityIds.contains(aspectTempQNameEntity.getId()) || nodeAspectQNameEntityIds.contains(aspectWorkingCopyQNameEntity.getId())) + if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY) || + nodeAspectQNames.contains(ContentModel.ASPECT_WORKING_COPY)) { - // The node is either temporary or a working copy. - // It can not be archived. - requiresDelete = true; + // The node is either temporary or a working copy. + // It can not be archived. + requiresDelete = true; } else { - StoreRef storeRef = nodeRef.getStoreRef(); - - // remove tenant domain - to retrieve archive store from map - archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - // get the type and check if we need archiving - TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName); - if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) - { - requiresDelete = true; - } + StoreRef storeRef = nodeRef.getStoreRef(); + archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + // get the type and check if we need archiving + TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName); + if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) + { + requiresDelete = true; + } } - + if (requiresDelete) { - // perform a normal deletion - nodeDaoService.deleteNode(node, true); - // Invoke policy behaviours - Set nodeToDeleteAspectQNames = new HashSet(17); - for (Long qnameEntityId : nodeAspectQNameEntityIds) + // Cascade as required + if (cascadeInTransaction) { - QName nodeToDeleteAspectQName = qnameDAO.getQNameEntity(qnameEntityId).getQName(); - nodeToDeleteAspectQNames.add(nodeToDeleteAspectQName); + deletePrimaryChildren(nodePair, true); } - invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeToDeleteAspectQNames, false); + // perform a normal deletion + nodeDaoService.deleteNode(nodeId); + // Invoke policy behaviours + invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, false); + + // Index + nodeIndexer.indexDeleteNode(childAssocRef); } else { - archiveStoreRef = tenantService.getName(archiveStoreRef); // archive it archiveNode(nodeRef, archiveStoreRef); // The archive performs a move, which will fire the appropriate OnDeleteNode + invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, true); } } - + + private void deletePrimaryChildren(Pair nodePair, boolean cascade) + { + Long nodeId = nodePair.getFirst(); + // Get the node's primary children + final List> childNodePairs = new ArrayList>(5); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair + ) + { + // Add it + childNodePairs.add(childNodePair); + // No recurse + return false; + } + }; + // Get all the QNames to remove + nodeDaoService.getPrimaryChildAssocs(nodeId, callback); + // Each child must be deleted + for (Pair childNodePair : childNodePairs) + { + // Cascade, if required + if (cascade) + { + deletePrimaryChildren(childNodePair, true); + } + // Delete the child + nodeDaoService.deleteNode(childNodePair.getFirst()); +// It would appear that policies should be fired here, but they have never been, so +// in the order to maintain historical consistency we keep it the same. +// // Fire node policies. This ensures that each node in the hierarchy gets a notification fired. +// invokeOnDeleteNode(oldParentAssocPair.getSecond(), childNodeTypeQName, childNodeAspectQNames, true); +// invokeOnCreateNode(newParentAssocPair.getSecond()); + } + } + public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) { - // get the parent node and ensure that it is a container node - Node parentNode = getNodeNotNull(parentRef); - // get the child node - Node childNode = getNodeNotNull(childRef); + Pair parentNodePair = getNodePairNotNull(parentRef); + Long parentNodeId = parentNodePair.getFirst(); + Pair childNodePair = getNodePairNotNull(childRef); + Long childNodeId = childNodePair.getFirst(); // Invoke policy behaviours invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false); - + // make the association - ChildAssoc assoc = nodeDaoService.newChildAssoc(parentNode, childNode, false, assocTypeQName, assocQName); + Pair childAssocPair = nodeDaoService.newChildAssoc(parentNodeId, childNodeId, false, assocTypeQName, assocQName); + ChildAssociationRef childAssocRef = childAssocPair.getSecond(); // ensure name uniqueness - setChildUniqueName(childNode); - ChildAssociationRef assocRef = assoc.getChildAssocRef(); - NodeRef childNodeRef = assocRef.getChildRef(); - + setChildNameUnique(childAssocPair, childNodePair); + NodeRef childNodeRef = childAssocRef.getChildRef(); + // check that the child addition of the child has not created a cyclic relationship // this functionality is provided for free in getPath getPaths(childNodeRef, false); // Invoke policy behaviours - invokeOnCreateChildAssociation(assocRef, false); + invokeOnCreateChildAssociation(childAssocRef, false); - // update the node status - nodeDaoService.recordChangeId(childNodeRef); + // Index + nodeIndexer.indexCreateChildAssociation(childAssocRef); - return assoc.getChildAssocRef(); + return childAssocRef; } public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException { - Node parentNode = getNodeNotNull(parentRef); - Node childNode = getNodeNotNull(childRef); - Long childNodeId = childNode.getId(); + final Pair parentNodePair = getNodePairNotNull(parentRef); + final Long parentNodeId = parentNodePair.getFirst(); + final Pair childNodePair = getNodePairNotNull(childRef); + final Long childNodeId = childNodePair.getFirst(); + + // Get the primary parent association for the child + Pair primaryChildAssocPair = nodeDaoService.getPrimaryParentAssoc(childNodeId); + // We can shortcut if our parent is also the primary parent + if (primaryChildAssocPair != null) + { + NodeRef primaryParentNodeRef = primaryChildAssocPair.getSecond().getParentRef(); + if (primaryParentNodeRef.equals(parentRef)) + { + // Shortcut - just delete the child node + deleteNode(childRef); + return; + } + } + + // We have to iterate over the associations and remove all those between the parent and child + final List> assocsToDelete = new ArrayList>(5); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + // Ignore if the child is not ours + if (!childNodePair.getFirst().equals(childNodeId)) + { + return false; + } + // Add it + assocsToDelete.add(childAssocPair); + // No recurse + return false; + } + }; + nodeDaoService.getChildAssocs(parentNodeId, callback, false); + + // Delete all the collected associations + for (Pair assocPair : assocsToDelete) + { + Long assocId = assocPair.getFirst(); + ChildAssociationRef assocRef = assocPair.getSecond(); + // delete the association instance - it is not primary + invokeBeforeDeleteChildAssociation(assocRef); + nodeDaoService.deleteChildAssoc(assocId); + invokeOnDeleteChildAssociation(assocRef); - // get all the child assocs - ChildAssociationRef primaryAssocRef = null; - Collection assocs = nodeDaoService.getChildAssocs(parentNode); - assocs = new HashSet(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 = assoc.getChildAssocRef(); - // Is this a primary association? - if (assoc.getIsPrimary()) - { - // keep the primary associaton 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 assoc deletion will not - // Update the node status - nodeDaoService.recordChangeId(childRef); + // Index + nodeIndexer.indexDeleteChildAssociation(assocRef); } - // done + // Done } - + public boolean removeChildAssociation(ChildAssociationRef childAssocRef) { - Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); - Node childNode = getNodeNotNull(childAssocRef.getChildRef()); - QName typeQName = childAssocRef.getTypeQName(); - QName qname = childAssocRef.getQName(); + Long parentNodeId = getNodePairNotNull(childAssocRef.getParentRef()).getFirst(); + Long childNodeId = getNodePairNotNull(childAssocRef.getChildRef()).getFirst(); + QName assocTypeQName = childAssocRef.getTypeQName(); + QName assocQName = childAssocRef.getQName(); // Delete the association invokeBeforeDeleteChildAssociation(childAssocRef); - boolean deleted = nodeDaoService.deleteChildAssoc(parentNode, childNode, typeQName, qname); + boolean deleted = nodeDaoService.deleteChildAssoc(parentNodeId, childNodeId, assocTypeQName, assocQName); if (deleted) { invokeOnDeleteChildAssociation(childAssocRef); - // Update the node status - nodeDaoService.recordChangeId(childNode.getNodeRef()); } + // Index + nodeIndexer.indexDeleteChildAssociation(childAssocRef); // Done return deleted; } public boolean removeSeconaryChildAssociation(ChildAssociationRef childAssocRef) { - Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); - Node childNode = getNodeNotNull(childAssocRef.getChildRef()); + Long parentNodeId = getNodePairNotNull(childAssocRef.getParentRef()).getFirst(); + Long childNodeId = getNodePairNotNull(childAssocRef.getChildRef()).getFirst(); QName typeQName = childAssocRef.getTypeQName(); QName qname = childAssocRef.getQName(); - ChildAssoc assoc = nodeDaoService.getChildAssoc(parentNode, childNode, typeQName, qname); - if (assoc == null) + Pair assocPair = nodeDaoService.getChildAssoc(parentNodeId, childNodeId, typeQName, qname); + if (assocPair == null) { // No association exists return false; } - if (assoc.getIsPrimary()) + Long assocId = assocPair.getFirst(); + ChildAssociationRef assocRef = assocPair.getSecond(); + if (assocRef.isPrimary()) { - throw new IllegalArgumentException("removeSeconaryChildAssociation can not be applied to a primary association: \n" + " Child Assoc: " + assoc); + throw new IllegalArgumentException( + "removeSeconaryChildAssociation can not be applied to a primary association: \n" + + " Child Assoc: " + assocRef); } // Delete the secondary association - nodeDaoService.deleteChildAssoc(assoc, false); + nodeDaoService.deleteChildAssoc(assocId); invokeOnDeleteChildAssociation(childAssocRef); - // Update the node status - nodeDaoService.recordChangeId(childNode.getNodeRef()); + // Index + nodeIndexer.indexDeleteChildAssociation(childAssocRef); // Done return true; } /** - * Remove properties that should not be persisted as general properties. Where necessary, the properties are set on - * the node. + * 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 + * @param node the node to set properties on + * @param properties properties to change */ - private void extractIntrinsicProperties(Node node, Map properties) + private void extractIntrinsicProperties(Map properties) { properties.remove(ContentModel.PROP_STORE_PROTOCOL); properties.remove(ContentModel.PROP_STORE_IDENTIFIER); properties.remove(ContentModel.PROP_NODE_UUID); properties.remove(ContentModel.PROP_NODE_DBID); } - + /** - * Adds all properties used by the {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}. + * Adds all properties used by the + * {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}. *

- * This method can be used to ensure that the values used by the aspect are present as node properties. + * This method can be used to ensure that the values used by the aspect + * are present as node properties. *

- * This method also ensures that the {@link ContentModel#PROP_NAME name property} is always present as a property on - * a node. + * 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 + * @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 properties) + private void addIntrinsicProperties(Pair nodePair, Map properties) { - NodeRef nodeRef = tenantService.getBaseName(node.getNodeRef()); + Long nodeId = nodePair.getFirst(); + NodeRef nodeRef = nodePair.getSecond(); 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()); + properties.put(ContentModel.PROP_NODE_DBID, nodeId); // add the ID as the name, if required if (properties.get(ContentModel.PROP_NAME) == null) { @@ -1078,43 +974,10 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } } - public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - return getPropertiesImpl(node); - } - - private Map getPropertiesImpl(Node node) throws InvalidNodeRefException - { - Map propDefs = dictionaryService.getPropertyDefs(node.getTypeQName().getQName()); - Map nodeProperties = node.getProperties(); - Map ret = new HashMap(nodeProperties.size()); - // copy values - for (Map.Entry entry : nodeProperties.entrySet()) - { - Long propertyQNameId = entry.getKey(); - QName propertyQName = qnameDAO.getQNameEntity(propertyQNameId).getQName(); - PropertyValue propertyValue = entry.getValue(); - // get the property definition - PropertyDefinition propertyDef = propDefs.get(propertyQName); - - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); - // copy across - ret.put(propertyQName, value); - } - // spoof referencable properties - addIntrinsicProperties(node, ret); - // done - return ret; - } - public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException { - // get the property from the node - Node node = getNodeNotNull(nodeRef); - - // spoof referencable properties + Long nodeId = getNodePairNotNull(nodeRef).getFirst(); + // Spoof referencable properties if (qname.equals(ContentModel.PROP_STORE_PROTOCOL)) { return nodeRef.getStoreRef().getProtocol(); @@ -1127,214 +990,260 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { return nodeRef.getId(); } - - if (qname.equals(ContentModel.PROP_NODE_DBID)) + else if (qname.equals(ContentModel.PROP_NODE_DBID)) { - return node.getId(); + return nodeId; } - - // Get the QName entity - QNameEntity qnameEntity = qnameDAO.getQNameEntity(qname); - if (qnameEntity == null) + + PropertyValue propertyValue = nodeDaoService.getNodeProperty(nodeId, qname); + + // check if we need to provide a spoofed name + if (propertyValue == null && qname.equals(ContentModel.PROP_NAME)) { - // There is no persisted, fixed QName like this - return null; + return nodeRef.getId(); } - else + + // get the property definition + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + // convert to the correct type + Serializable value = makeSerializableValue(propertyDef, propertyValue); + // done + return value; + } + + public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException + { + Pair nodePair = getNodePairNotNull(nodeRef); + return getPropertiesImpl(nodePair); + } + + /** + * Gets, converts and adds the intrinsic properties to the current node's properties + */ + private Map getPropertiesImpl(Pair nodePair) throws InvalidNodeRefException + { + Long nodeId = nodePair.getFirst(); + Map nodeProperties = nodeDaoService.getNodeProperties(nodeId); + Map ret = new HashMap(nodeProperties.size()); + // copy values + for (Map.Entry entry: nodeProperties.entrySet()) { - Map properties = node.getProperties(); - PropertyValue propertyValue = properties.get(qnameEntity.getId()); - - // check if we need to provide a spoofed name - if (propertyValue == null && qname.equals(ContentModel.PROP_NAME)) - { - return nodeRef.getId(); - } - - // Convert any NodeRefs using multi-tenant translation - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - + QName propertyQName = entry.getKey(); + PropertyValue propertyValue = entry.getValue(); + // get the property definition + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); // convert to the correct type Serializable value = makeSerializableValue(propertyDef, propertyValue); - // done - return value; + // copy across + ret.put(propertyQName, value); } + // spoof referencable properties + addIntrinsicProperties(nodePair, ret); + // done + return ret; } - + /** - * Ensures that all required properties are present on the node and copies the property values to the - * Node. - *

- * To remove a property, remove it from the map before calling this method. Null-valued properties are - * allowed. - *

- * 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 properties) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - - // Do the set properties - Map propertiesBefore = getPropertiesImpl(node); - Map propertiesAfter = setPropertiesImpl(node, properties); - - setChildUniqueName(node); // ensure uniqueness - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - } - - /** - * 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 setPropertiesImpl(Node node, Map properties) throws InvalidNodeRefException - { - ParameterCheck.mandatory("properties", properties); - - // remove referencable properties - extractIntrinsicProperties(node, properties); - - // copy properties onto node - Map nodeProperties = node.getProperties(); - nodeProperties.clear(); - - // check the property type and copy the values across - for (QName propertyQName : properties.keySet()) - { - QNameEntity propertyQNameEntity = qnameDAO.getOrCreateQNameEntity(propertyQName); - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - // Get the value to persist - Serializable value = properties.get(propertyQName); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - nodeProperties.put(propertyQNameEntity.getId(), propertyValue); - } - - // update the node status - NodeRef nodeRef = node.getNodeRef(); - nodeDaoService.recordChangeId(nodeRef); - - // Return the properties after - return Collections.unmodifiableMap(properties); - } - - /** - * Gets the properties map, sets the value (null is allowed) and checks that the new set of properties is valid. + * 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 { Assert.notNull(qname); - + // get the node - Node node = getNodeNotNull(nodeRef); + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + + // Ensure that we are not setting intrinsic properties + Map properties = Collections.singletonMap(qname, value); + extractIntrinsicProperties(properties); + + // Get the properties from before + Map propertiesBefore = getPropertiesImpl(nodePair); - // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); - - // Do the set operation - Map propertiesBefore = getPropertiesImpl(node); - Map propertiesAfter = setPropertyImpl(node, qname, value); - - if (qname.equals(ContentModel.PROP_NAME)) - { - setChildUniqueName(node); // ensure uniqueness - } - - // Invoke policy behaviours + // Update the properties + setPropertyImpl(nodeId, qname, value); + // Policy callbacks + Map propertiesAfter = getPropertiesImpl(nodePair); invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + + // Index + nodeIndexer.indexUpdateNode(nodeRef); } - + /** - * 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 + * Sets the property, taking special care to handle intrinsic properties and cm:name properly */ - private Map setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException + private void setPropertyImpl(Long nodeId, QName qname, Serializable value) { - NodeRef nodeRef = node.getNodeRef(); - - Map properties = node.getProperties(); - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // Get the persistable key - QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(qname); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - properties.put(qnameEntity.getId(), propertyValue); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - - return getPropertiesImpl(node); + if (qname.equals(ContentModel.PROP_NODE_UUID)) + { + throw new IllegalArgumentException("The node UUID cannot be changed."); + } + else + { + // cm:name special handling + if (qname.equals(ContentModel.PROP_NAME)) + { + Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId); + if (primaryParentAssocPair != null) + { + String oldName = extractNameProperty(nodeDaoService.getNodeProperties(nodeId)); + String newName = DefaultTypeConverter.INSTANCE.convert(String.class, value); + setChildNameUnique(primaryParentAssocPair, newName, oldName); + } + } + // Set the property + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + // get a persistable value + PropertyValue propertyValue = makePropertyValue(propertyDef, value); + nodeDaoService.addNodeProperty(nodeId, qname, propertyValue); + } } + + /** + * Ensures that all required properties are present on the node and copies the + * property values to the Node. + *

+ * To remove a property, remove it from the map before calling this method. + * Null-valued properties are allowed. + *

+ * 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 properties) throws InvalidNodeRefException + { + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + + extractIntrinsicProperties(properties); + // Invoke policy behaviours + Map propertiesBefore = getPropertiesImpl(nodePair); + invokeBeforeUpdateNode(nodeRef); + + // Do the set properties + setPropertiesImpl(nodeId, properties); + + // Invoke policy behaviours + Map propertiesAfter = getPropertiesImpl(nodePair); + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + + // Index + nodeIndexer.indexUpdateNode(nodeRef); + } + + private void setPropertiesImpl(Long nodeId, Map properties) + { + // Get the cm:name and uuid for special handling + if (properties.containsKey(ContentModel.PROP_NAME)) + { + Serializable name = properties.get(ContentModel.PROP_NAME); + setPropertyImpl(nodeId, ContentModel.PROP_NAME, name); + } + if (properties.containsKey(ContentModel.PROP_NODE_UUID)) + { + throw new IllegalArgumentException("The node UUID cannot be set"); + } + // Now remove special properties + extractIntrinsicProperties(properties); + // convert the map + Map propertyValues = convertProperties(properties); + // Update the node + nodeDaoService.setNodeProperties(nodeId, propertyValues); + } + public void removeProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException { - if (qname.equals(ContentModel.PROP_NAME)) - { - throw new UnsupportedOperationException("The property " + qname + " may not be removed individually"); - } - // Get the node - Node node = getNodeNotNull(nodeRef); - + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); - - // Get the persistable QNameEntity - QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(qname); + // Get the values before - Map propertiesBefore = getPropertiesImpl(node); - // Remove the property - Map properties = node.getProperties(); - properties.remove(qnameEntity.getId()); - // Get the values afterwards - Map propertiesAfter = getPropertiesImpl(node); + Map propertiesBefore = getPropertiesImpl(nodePair); + + // cm:name special handling + if (qname.equals(ContentModel.PROP_NAME)) + { + Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId); + String oldName = extractNameProperty(nodeDaoService.getNodeProperties(nodeId)); + String newName = null; + setChildNameUnique(primaryParentAssocPair, newName, oldName); + } + // Remove + nodeDaoService.removeNodeProperties(nodeId, Collections.singleton(qname)); + // Invoke policy behaviours + Map propertiesAfter = getPropertiesImpl(nodePair); invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + + // Index + nodeIndexer.indexUpdateNode(nodeRef); } - /** - * Transforms {@link Node#getParentAssocs()} to a new collection - */ + private Map convertProperties(Map properties) throws InvalidNodeRefException + { + Map convertedProperties = new HashMap(17); + + // check the property type and copy the values across + for (QName propertyQName : properties.keySet()) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + Serializable value = properties.get(propertyQName); + // get a persistable value + PropertyValue propertyValue = makePropertyValue(propertyDef, value); + convertedProperties.put(propertyQName, propertyValue); + } + + // Return the converted properties + return convertedProperties; + } + + private Map convertPropertyValues(Map propertyValues) throws InvalidNodeRefException + { + Map convertedProperties = new HashMap(17); + + // check the property type and copy the values across + for (Map.Entry entry : propertyValues.entrySet()) + { + QName propertyQName = entry.getKey(); + PropertyValue propertyValue = entry.getValue(); + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + Serializable property = makeSerializableValue(propertyDef, propertyValue); + convertedProperties.put(propertyQName, property); + } + + // Return the converted properties + return convertedProperties; + } + public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing to it - Collection parentAssocs = nodeDaoService.getParentAssocs(node); + // Get the node + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + + // Get the assocs pointing to it + Collection> parentAssocPairs = nodeDaoService.getParentAssocs(nodeId); // list of results - Collection results = new ArrayList(parentAssocs.size()); - for (ChildAssoc assoc : parentAssocs) + Collection results = new ArrayList(parentAssocPairs.size()); + for (Pair assocPair : parentAssocPairs) { - // get the parent - results.add(tenantService.getBaseName(assoc.getParent().getNodeRef())); + NodeRef parentNodeRef = assocPair.getSecond().getParentRef(); + results.add(parentNodeRef); } // done return results; @@ -1345,28 +1254,25 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl */ public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing to it - Collection parentAssocs = nodeDaoService.getParentAssocs(node); - // shortcut if there are no assocs - if (parentAssocs.size() == 0) - { - return Collections.emptyList(); - } + // Get the node + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + + // Get the assocs pointing to it + Collection> parentAssocPairs = nodeDaoService.getParentAssocs(nodeId); // list of results - List results = new ArrayList(parentAssocs.size()); - for (ChildAssoc assoc : parentAssocs) + List results = new ArrayList(parentAssocPairs.size()); + for (Pair assocPair : parentAssocPairs) { - QName assocTypeQName = assoc.getTypeQName().getQName(); - QName assocQName = assoc.getQname(); - // does the qname match the pattern? + ChildAssociationRef assocRef = assocPair.getSecond(); + QName assocTypeQName = assocRef.getTypeQName(); + QName assocQName = assocRef.getQName(); if (!qnamePattern.isMatch(assocQName) || !typeQNamePattern.isMatch(assocTypeQName)) { - // no match - ignore + // No match continue; } - ChildAssociationRef childAssocRef = tenantService.getBaseName(assoc.getChildAssocRef()); - results.add(childAssocRef); + results.add(assocRef); } // done return results; @@ -1375,40 +1281,78 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl /** * Filters out any associations if their qname is not a match to the given pattern. */ - public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) + public List getChildAssocs(NodeRef nodeRef, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern) { - Node node = getNodeNotNull(nodeRef); + // Get the node + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); - Collection childAssocRefs = null; + final List results = new ArrayList(100); + // 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) { - // get all child associations with the specific qualified name - childAssocRefs = nodeDaoService.getChildAssocRefs(node, (QName) qnamePattern); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + results.add(childAssocPair.getSecond()); + return false; + } + }; + // Get all child associations with the specific qualified name + nodeDaoService.getChildAssocs(nodeId, (QName)qnamePattern, callback); + } + else if (typeQNamePattern instanceof QName && qnamePattern instanceof QName) + { + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + results.add(childAssocPair.getSecond()); + return false; + } + }; + // Get all child associations with the specific qualified name + nodeDaoService.getChildAssocsByTypeQNameAndQName( + nodeId, + (QName)typeQNamePattern, + (QName)qnamePattern, + callback); } else { - // get all child associations - childAssocRefs = nodeDaoService.getChildAssocRefs(node); - // remove non-matching assocs - Iterator iterator = childAssocRefs.iterator(); - while (iterator.hasNext()) + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() { - ChildAssociationRef childAssocRef = iterator.next(); - // does the qname match the pattern? - if (!qnamePattern.isMatch(childAssocRef.getQName()) || !typeQNamePattern.isMatch(childAssocRef.getTypeQName())) + public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) { - // no match - remove - iterator.remove(); + ChildAssociationRef assocRef = childAssocPair.getSecond(); + QName assocTypeQName = assocRef.getTypeQName(); + QName assocQName = assocRef.getQName(); + if (!qnamePattern.isMatch(assocQName) || !typeQNamePattern.isMatch(assocTypeQName)) + { + // No match + return false; + } + results.add(assocRef); + return false; } - } + }; + // Get all child associations + nodeDaoService.getChildAssocs(nodeId, callback, false); } // sort the results - List orderedList = reorderChildAssocs(childAssocRefs); + List orderedList = reorderChildAssocs(results); // done return orderedList; } - + private List reorderChildAssocs(Collection childAssocRefs) { // shortcut if there are no assocs @@ -1419,11 +1363,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // sort results ArrayList orderedList = new ArrayList(childAssocRefs); Collections.sort(orderedList); - + // list of results int nthSibling = 0; Iterator iterator = orderedList.iterator(); - while (iterator.hasNext()) + while(iterator.hasNext()) { ChildAssociationRef childAssocRef = iterator.next(); childAssocRef.setNthSibling(nthSibling); @@ -1435,11 +1379,14 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) { - Node node = getNodeNotNull(nodeRef); - ChildAssoc childAssoc = nodeDaoService.getChildAssoc(node, assocTypeQName, childName); - if (childAssoc != null) + // Get the node + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + + Pair childAssocPair = nodeDaoService.getChildAssoc(nodeId, assocTypeQName, childName); + if (childAssocPair != null) { - return tenantService.getBaseName(childAssoc.getChild().getNodeRef()); + return childAssocPair.getSecond().getChildRef(); } else { @@ -1449,78 +1396,85 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException { - Node node = getNodeNotNull(nodeRef); + // Get the node + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + // get the primary parent assoc - ChildAssoc assoc = nodeDaoService.getPrimaryParentAssoc(node); + Pair assocPair = nodeDaoService.getPrimaryParentAssoc(nodeId); // done - the assoc may be null for a root node ChildAssociationRef assocRef = null; - if (assoc == null) + if (assocPair == null) { assocRef = new ChildAssociationRef(null, null, null, nodeRef); } else { - assocRef = tenantService.getBaseName(assoc.getChildAssocRef()); + assocRef = assocPair.getSecond(); } return assocRef; } - public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) throws InvalidNodeRefException, AssociationExistsException + public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException, AssociationExistsException { - Node sourceNode = getNodeNotNull(sourceRef); - Node targetNode = getNodeNotNull(targetRef); - // see if it exists - NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); - if (assoc != null) - { - throw new AssociationExistsException(sourceRef, targetRef, assocTypeQName); - } + Pair sourceNodePair = getNodePairNotNull(sourceRef); + long sourceNodeId = sourceNodePair.getFirst(); + Pair targetNodePair = getNodePairNotNull(targetRef); + long targetNodeId = targetNodePair.getFirst(); + // we are sure that the association doesn't exist - make it - assoc = nodeDaoService.newNodeAssoc(sourceNode, targetNode, assocTypeQName); - AssociationRef assocRef = assoc.getNodeAssocRef(); + Pair assocPair = nodeDaoService.newNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName); + AssociationRef assocRef = assocPair.getSecond(); // Invoke policy behaviours invokeOnCreateAssociation(assocRef); - + return assocRef; } - public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) throws InvalidNodeRefException + public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException { - Node sourceNode = getNodeNotNull(sourceRef); - Node targetNode = getNodeNotNull(targetRef); + Pair sourceNodePair = getNodePairNotNull(sourceRef); + long sourceNodeId = sourceNodePair.getFirst(); + Pair targetNodePair = getNodePairNotNull(targetRef); + long targetNodeId = targetNodePair.getFirst(); + // get the association - NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); - if (assoc == null) + Pair assocPair = nodeDaoService.getNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName); + if (assocPair == null) { // nothing to remove return; } - AssociationRef assocRef = assoc.getNodeAssocRef(); - + AssociationRef assocRef = assocPair.getSecond(); + // delete it - nodeDaoService.deleteNodeAssoc(assoc); - + nodeDaoService.deleteNodeAssoc(assocPair.getFirst()); + // Invoke policy behaviours invokeOnDeleteAssociation(assocRef); } public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) { - Node sourceNode = getNodeNotNull(sourceRef); + Pair sourceNodePair = getNodePairNotNull(sourceRef); + long sourceNodeId = sourceNodePair.getFirst(); + // get all assocs to target - Collection assocs = nodeDaoService.getTargetNodeAssocs(sourceNode); - List nodeAssocRefs = new ArrayList(assocs.size()); - for (NodeAssoc assoc : assocs) + Collection> assocPairs = nodeDaoService.getTargetNodeAssocs(sourceNodeId); + List nodeAssocRefs = new ArrayList(assocPairs.size()); + for (Pair assocPair : assocPairs) { - QName assocTypeQName = assoc.getTypeQName().getQName(); + AssociationRef assocRef = assocPair.getSecond(); // check qname pattern - if (!qnamePattern.isMatch(assocTypeQName)) + if (!qnamePattern.isMatch(assocRef.getTypeQName())) { - continue; // the assoc name doesn't match the pattern given + continue; // the assoc name doesn't match the pattern given } - nodeAssocRefs.add(assoc.getNodeAssocRef()); + nodeAssocRefs.add(assocRef); } // done return nodeAssocRefs; @@ -1528,68 +1482,87 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) { - Node targetNode = getNodeNotNull(targetRef); - // get all assocs to source - Collection assocs = nodeDaoService.getSourceNodeAssocs(targetNode); - List nodeAssocRefs = new ArrayList(assocs.size()); - for (NodeAssoc assoc : assocs) + Pair targetNodePair = getNodePairNotNull(targetRef); + long targetNodeId = targetNodePair.getFirst(); + + // get all assocs to target + Collection> assocPairs = nodeDaoService.getSourceNodeAssocs(targetNodeId); + List nodeAssocRefs = new ArrayList(assocPairs.size()); + for (Pair assocPair : assocPairs) { - QName assocTypeQName = assoc.getTypeQName().getQName(); + AssociationRef assocRef = assocPair.getSecond(); // check qname pattern - if (!qnamePattern.isMatch(assocTypeQName)) + if (!qnamePattern.isMatch(assocRef.getTypeQName())) { - continue; // the assoc name doesn't match the pattern given + continue; // the assoc name doesn't match the pattern given } - nodeAssocRefs.add(assoc.getNodeAssocRef()); + nodeAssocRefs.add(assocRef); } // done return nodeAssocRefs; } - + /** * Recursive method used to build up paths from a given node to the root. *

- * Whilst walking up the hierarchy to the root, some nodes may have a root aspect. Everytime one of these is - * encountered, a new path is farmed off, but the method continues to walk up the hierarchy. + * Whilst walking up the hierarchy to the root, some nodes may have a root 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. + * @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 completedPaths, Stack assocStack, boolean primaryOnly) - throws CyclicChildRelationshipException + private void prependPaths( + Pair currentNodePair, + Pair currentRootNodePair, + Path currentPath, + Collection completedPaths, + Stack assocIdStack, + boolean primaryOnly) + throws CyclicChildRelationshipException { - NodeRef currentNodeRef = currentNode.getNodeRef(); + Long currentNodeId = currentNodePair.getFirst(); + NodeRef currentNodeRef = currentNodePair.getSecond(); + + // Check if we have changed root nodes + StoreRef currentStoreRef = currentNodeRef.getStoreRef(); + if (currentRootNodePair == null || !currentStoreRef.equals(currentRootNodePair.getFirst())) + { + // We've changed stores + Pair rootNodePair = nodeDaoService.getRootNode(currentStoreRef); + currentRootNodePair = new Pair(currentStoreRef, rootNodePair.getSecond()); + } + // get the parent associations of the given node - Collection parentAssocs = nodeDaoService.getParentAssocs(currentNode); + Collection> parentAssocPairs = nodeDaoService.getParentAssocs(currentNodeId); // does the node have parents - boolean hasParents = parentAssocs.size() > 0; + boolean hasParents = parentAssocPairs.size() > 0; // does the current node have a root aspect? - boolean isRoot = hasAspect(currentNodeRef, ContentModel.ASPECT_ROOT); - boolean isStoreRoot = currentNode.getTypeQName().getQName().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 + boolean isRoot = nodeDaoService.hasNodeAspect(currentNodeId, ContentModel.ASPECT_ROOT); + boolean isStoreRoot = nodeDaoService.getNodeType(currentNodeId).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 assoc ref 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 assoc in as the first assoc in the path must be a one-sided - // reference pointing to the root node - ChildAssociationRef assocRef = new ChildAssociationRef(null, null, null, getRootNode(currentNode.getNodeRef().getStoreRef())); + // reference pointing to the root node + ChildAssociationRef assocRef = new ChildAssociationRef( + null, + null, + null, + currentRootNodePair.getSecond()); // create a path to save and add the 'root' assoc Path pathToSave = new Path(); Path.ChildAssocElement first = null; - for (Path.Element element : currentPath) + for (Path.Element element: currentPath) { if (first == null) { @@ -1603,47 +1576,39 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl 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); + // or if first beneath the root node it will make the real thing + ChildAssociationRef updateAssocRef = new ChildAssociationRef( + isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(), + currentRootNodePair.getSecond(), + 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 (parentAssocs.size() == 0 && !isRoot) + if (parentAssocPairs.size() == 0 && !isRoot) { - throw new RuntimeException("Node without parents does not have root aspect: " + currentNodeRef); + throw new RuntimeException("Node without parents does not have root aspect: " + + currentNodeRef); } // walk up each parent association - for (ChildAssoc assoc : parentAssocs) + for (Pair assocPair : parentAssocPairs) { - // 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); - } + Long assocId = assocPair.getFirst(); + ChildAssociationRef assocRef = assocPair.getSecond(); // do we consider only primary assocs? - if (primaryOnly && !assoc.getIsPrimary()) + if (primaryOnly && !assocRef.isPrimary()) { continue; } // build a path element - NodeRef parentRef = tenantService.getBaseName(assoc.getParent().getNodeRef()); - QName qname = assoc.getQname(); - NodeRef childRef = tenantService.getBaseName(assoc.getChild().getNodeRef()); - boolean isPrimary = assoc.getIsPrimary(); - // build a real association reference - ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName().getQName(), parentRef, qname, childRef, isPrimary, -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(); @@ -1651,12 +1616,24 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // prepend element path.prepend(element); // get parent node - Node parentNode = assoc.getParent(); - + NodeRef parentRef = assocRef.getParentRef(); + Pair parentNodePair = getNodePairNotNull(parentRef); + // does the association already exist in the stack + if (assocIdStack.contains(assocId)) + { + // the association was present already + throw new CyclicChildRelationshipException( + "Cyclic parent-child relationship detected: \n" + + " current node: " + currentNodeId + "\n" + + " current path: " + currentPath + "\n" + + " next assoc: " + assocId, + assocRef); + } + // push the assoc stack, recurse and pop - assocStack.push(assoc); - prependPaths(parentNode, path, completedPaths, assocStack, primaryOnly); - assocStack.pop(); + assocIdStack.push(assocId); + prependPaths(parentNodePair, currentRootNodePair, path, completedPaths, assocIdStack, primaryOnly); + assocIdStack.pop(); } // done } @@ -1667,38 +1644,38 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl */ public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException { - List paths = getPaths(nodeRef, true); // checks primary path count + List paths = getPaths(nodeRef, true); // checks primary path count if (paths.size() == 1) { - return paths.get(0); // we know there is only one + return paths.get(0); // we know there is only one } - throw new RuntimeException("Primary path count not checked"); // checked by getPaths() + throw new RuntimeException("Primary path count not checked"); // checked by getPaths() } /** - * When searching for primaryOnly == true, checks that there is exactly one path. - * + * When searching for primaryOnly == true, checks that there is exactly + * one path. * @see #prependPaths(Node, Path, Collection, Stack, boolean) */ public List getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException { // get the starting node - Node node = getNodeNotNull(nodeRef); + Pair nodePair = getNodePairNotNull(nodeRef); // create storage for the paths - only need 1 bucket if we are looking for the primary path List paths = new ArrayList(primaryOnly ? 1 : 10); // create an empty current path to start from Path currentPath = new Path(); // create storage for touched associations - Stack assocStack = new Stack(); + Stack assocIdStack = new Stack(); // call recursive method to sort it out - prependPaths(node, currentPath, paths, assocStack, primaryOnly); - + prependPaths(nodePair, null, currentPath, paths, assocIdStack, 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); } - + // done if (loggerPaths.isDebugEnabled()) { @@ -1720,344 +1697,93 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } return paths; } - + private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) { - QNameEntity aspectQNameEntityArchived = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_ARCHIVED); - QNameEntity propQNameEntityOwner = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_OWNER); - QNameEntity propQNameEntityCreator = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_CREATOR); - QNameEntity propQNameArchivedBy = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_BY); - QNameEntity propQNameArchivedDate = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_DATE); - QNameEntity propQNameArchivedOriginalParentAssoc = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); - - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); - Node node = nodeStatus.getNode(); - ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node); - + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId); + Set newAspects = new HashSet(5); + Map existingPropertyValues = nodeDaoService.getNodeProperties(nodeId); + Map newPropertyValues = new HashMap(11); + // add the aspect - Set aspects = node.getAspects(); - aspects.add(aspectQNameEntityArchived.getId()); - Map properties = node.getProperties(); - PropertyValue archivedByProperty = makePropertyValue(dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), AuthenticationUtil.getCurrentUserName()); - properties.put(propQNameArchivedBy.getId(), archivedByProperty); - PropertyValue archivedDateProperty = makePropertyValue(dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), new Date()); - properties.put(propQNameArchivedDate.getId(), archivedDateProperty); - PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue(dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), primaryParentAssoc - .getChildAssocRef()); - properties.put(propQNameArchivedOriginalParentAssoc.getId(), archivedPrimaryParentNodeRefProperty); - PropertyValue originalOwnerProperty = properties.get(propQNameEntityOwner.getId()); - PropertyValue originalCreatorProperty = properties.get(propQNameEntityCreator.getId()); + newAspects.add(ContentModel.ASPECT_ARCHIVED); + PropertyValue archivedByProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), + AuthenticationUtil.getCurrentUserName()); + newPropertyValues.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); + PropertyValue archivedDateProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), + new Date()); + newPropertyValues.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); + PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), + primaryParentAssocPair.getSecond()); + newPropertyValues.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty); + PropertyValue originalOwnerProperty = existingPropertyValues.get(ContentModel.PROP_OWNER); + PropertyValue originalCreatorProperty = existingPropertyValues.get(ContentModel.PROP_CREATOR); if (originalOwnerProperty != null || originalCreatorProperty != null) { - QNameEntity propQNameArchivedOriginalOwner = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); - properties.put(propQNameArchivedOriginalOwner.getId(), originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); + newPropertyValues.put( + ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER, + originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); } - + // change the node ownership - QNameEntity ownableAspectQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_OWNABLE); - aspects.add(ownableAspectQNameEntity.getId()); - PropertyValue newOwnerProperty = makePropertyValue(dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), AuthenticationUtil.getCurrentUserName()); - QNameEntity propQNameOwner = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_OWNER); - properties.put(propQNameOwner.getId(), newOwnerProperty); - + newAspects.add(ContentModel.ASPECT_OWNABLE); + PropertyValue newOwnerProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), + AuthenticationUtil.getCurrentUserName()); + newPropertyValues.put(ContentModel.PROP_OWNER, newOwnerProperty); + + // Set the aspects and properties + nodeDaoService.addNodeProperties(nodeId, newPropertyValues); + nodeDaoService.addNodeAspects(nodeId, newAspects); + // 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 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); - } + Pair archiveStoreRootNodePair = nodeDaoService.getRootNode(archiveStoreRef); + moveNode( + nodeRef, + archiveStoreRootNodePair.getSecond(), + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem")); } - - /** - * 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) - { - NodeRef nodeRef = node.getNodeRef(); - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, true); - // get the IDs of all the node's primary children, including its own - Map nodeStatusesById = getNodeHierarchy(nodeStatus, null); - - // move each node into the archive store - for (NodeStatus oldNodeStatus : nodeStatusesById.values()) - { - // 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 (nodeToMove == null) - { - // Ignore it. It was moved already. - continue; - } - 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); - } - } - - /** - * 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 getNodeHierarchy(NodeStatus nodeStatus, Map nodeStatusesById) - { - if (nodeStatusesById == null) - { - nodeStatusesById = new HashMap(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 - logger.warn("Circular hierarchy found including node " + nodeId); - return nodeStatusesById; - } - // add the node to the map - nodeStatusesById.put(nodeId, nodeStatus); - // recurse into the primary children - Collection primaryChildNodeStatuses = nodeDaoService.getPrimaryChildNodeStatuses(node); - for (NodeStatus primaryChildNodeStatus : primaryChildNodeStatuses) - { - // cascade into primary associations - nodeStatusesById = getNodeHierarchy(primaryChildNodeStatus, nodeStatusesById); - } - return nodeStatusesById; - } - - /** - * Archive all associations to and from the given node, with the exception of associations to or from nodes in the - * given map. - *

- * 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 nodeStatusesById) - { - List childAssocsToDelete = new ArrayList(5); - // child associations - ArrayList archivedChildAssocRefs = new ArrayList(5); - Collection childAssocs = nodeDaoService.getChildAssocs(node); - for (ChildAssoc assoc : childAssocs) - { - 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 archivedParentAssocRefs = new ArrayList(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); - archivedParentAssocRefs.add(assoc.getChildAssocRef()); - } - - List nodeAssocsToDelete = new ArrayList(5); - // source associations - ArrayList archivedSourceAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) - { - Long relatedNodeId = assoc.getSource().getId(); - if (nodeStatusesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - nodeAssocsToDelete.add(assoc); - archivedSourceAssocRefs.add(assoc.getNodeAssocRef()); - } - // target associations - ArrayList archivedTargetAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) - { - Long relatedNodeId = assoc.getTarget().getId(); - if (nodeStatusesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - nodeAssocsToDelete.add(assoc); - archivedTargetAssocRefs.add(assoc.getNodeAssocRef()); - } - // delete child assocs - for (ChildAssoc assoc : childAssocsToDelete) - { - nodeDaoService.deleteChildAssoc(assoc, false); - } - // delete node assocs - for (NodeAssoc assoc : nodeAssocsToDelete) - { - nodeDaoService.deleteNodeAssoc(assoc); - } - - // add archived aspect - QNameEntity archivedAssocsAspectQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_ARCHIVED_ASSOCS); - node.getAspects().add(archivedAssocsAspectQNameEntity.getId()); - // set properties - Map properties = node.getProperties(); - - if (archivedParentAssocRefs.size() > 0) - { - QNameEntity propQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs); - properties.put(propQNameEntity.getId(), propertyValue); - } - if (archivedChildAssocRefs.size() > 0) - { - QNameEntity propQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs); - properties.put(propQNameEntity.getId(), propertyValue); - } - if (archivedSourceAssocRefs.size() > 0) - { - QNameEntity propQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs); - properties.put(propQNameEntity.getId(), propertyValue); - } - if (archivedTargetAssocRefs.size() > 0) - { - QNameEntity propQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs); - properties.put(propQNameEntity.getId(), propertyValue); - } - } - - public NodeRef getStoreArchiveNode(StoreRef storeRef) - { - StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - if (archiveStoreRef == null) - { - // no mapping for the given store - return null; - } - else - { - return getRootNode(archiveStoreRef); - } - } - + public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName) { - QNameEntity aspectQNameEntityArchived = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_ARCHIVED); - QNameEntity propQNameEntityOwner = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_OWNER); - QNameEntity propQNameEntityOrigParentAssoc = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); - QNameEntity propQNameEntityArchivedBy = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_BY); - QNameEntity propQNameEntityArchivedDate = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_DATE); - QNameEntity propQNameEntityOrigOwner = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); - - NodeStatus archivedNodeStatus = getNodeStatusNotNull(archivedNodeRef); - Node archivedNode = archivedNodeStatus.getNode(); - Set aspects = archivedNode.getAspects(); - Map properties = archivedNode.getProperties(); + Pair archivedNodePair = getNodePairNotNull(archivedNodeRef); + Long archivedNodeId = archivedNodePair.getFirst(); + Set existingAspects = nodeDaoService.getNodeAspects(archivedNodeId); + Set newAspects = new HashSet(5); + Map existingPropertyValues = nodeDaoService.getNodeProperties(archivedNodeId); + Map newPropertyValues = new HashMap(11); + // the node must be a top-level archive node - if (!aspects.contains(aspectQNameEntityArchived.getId())) + if (!existingAspects.contains(ContentModel.ASPECT_ARCHIVED)) { - throw new AlfrescoRuntimeException("The node to archive is not an archive node"); + throw new AlfrescoRuntimeException("The node to restore is not an archive node"); } - ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue(dictionaryService - .getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), properties.get(propQNameEntityOrigParentAssoc.getId())); - PropertyValue originalOwnerProperty = properties.get(propQNameEntityOrigOwner.getId()); - + ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), + existingPropertyValues.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC)); + PropertyValue originalOwnerProperty = existingPropertyValues.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); // remove the archived aspect - removeAspect(archivedNodeRef, ContentModel.ASPECT_ARCHIVED); // allow policy to fire, e.g. for - // DictionaryModelType - - properties.remove(propQNameEntityOrigParentAssoc.getId()); - properties.remove(propQNameEntityArchivedBy.getId()); - properties.remove(propQNameEntityArchivedDate.getId()); - properties.remove(propQNameEntityOrigOwner.getId()); - + Set removePropertyQNames = new HashSet(11); + removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); + removePropertyQNames.add(ContentModel.PROP_ARCHIVED_BY); + removePropertyQNames.add(ContentModel.PROP_ARCHIVED_DATE); + removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); + nodeDaoService.removeNodeProperties(archivedNodeId, removePropertyQNames); + nodeDaoService.removeNodeAspects(archivedNodeId, Collections.singleton(ContentModel.ASPECT_ARCHIVED)); + // restore the original ownership if (originalOwnerProperty != null) { - QNameEntity ownableAspectQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_OWNABLE); - aspects.add(ownableAspectQNameEntity.getId()); - properties.put(propQNameEntityOwner.getId(), originalOwnerProperty); + newAspects.add(ContentModel.ASPECT_OWNABLE); + newPropertyValues.put(ContentModel.PROP_OWNER, originalOwnerProperty); } - + if (destinationParentNodeRef == null) { // we must restore to the original location @@ -2074,165 +1800,511 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } // move the node to the target parent, which may or may not be the original parent - 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 restoreNodeStatusesById = getNodeHierarchy(archivedNodeStatus, null); - // Restore the archived associations, if required - for (NodeStatus restoreNodeStatus : restoreNodeStatusesById.values()) - { - Node restoreNode = restoreNodeStatus.getNode(); - restoreAssocs(restoreNode, propQNameEntityOrigParentAssoc); - } + ChildAssociationRef newChildAssocRef = moveNode( + archivedNodeRef, + destinationParentNodeRef, + assocTypeQName, + assocQName); // the node reference has changed due to the store move - NodeRef restoredNodeRef = archivedNode.getNodeRef(); - + NodeRef restoredNodeRef = newChildAssocRef.getChildRef(); + // done if (logger.isDebugEnabled()) { - logger.debug("Restored node: \n" - + " original noderef: " + archivedNodeRef + "\n" + " restored noderef: " + restoredNodeRef + "\n" + " new parent: " + destinationParentNodeRef); + logger.debug("Restored node: \n" + + " original noderef: " + archivedNodeRef + "\n" + + " restored noderef: " + restoredNodeRef + "\n" + + " new parent: " + destinationParentNodeRef); } return restoredNodeRef; } - private void restoreAssocs(Node node, QNameEntity propQNameEntityOrigParentAssoc) - { - NodeRef nodeRef = node.getNodeRef(); - // set properties - Map properties = node.getProperties(); - - // restore parent associations - Collection parentAssocRefs = (Collection) getProperty(nodeRef, ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - if (parentAssocRefs != null) - { - for (ChildAssociationRef assocRef : parentAssocRefs) - { - NodeRef parentNodeRef = assocRef.getParentRef(); - if (!exists(parentNodeRef)) - { - continue; - } - Node parentNode = getNodeNotNull(parentNodeRef); - // get the name to use for the unique child check - QName assocTypeQName = assocRef.getTypeQName(); - nodeDaoService.newChildAssoc(parentNode, node, assocRef.isPrimary(), assocTypeQName, assocRef.getQName()); - } - properties.remove(propQNameEntityOrigParentAssoc.getId()); - } - - // make sure that the node name uniqueness is enforced - setChildUniqueName(node); - - // restore child associations - Collection childAssocRefs = (Collection) getProperty(nodeRef, ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - if (childAssocRefs != null) - { - for (ChildAssociationRef assocRef : childAssocRefs) - { - NodeRef childNodeRef = assocRef.getChildRef(); - if (!exists(childNodeRef)) - { - continue; - } - Node childNode = getNodeNotNull(childNodeRef); - QName assocTypeQName = assocRef.getTypeQName(); - // get the name to use for the unique child check - nodeDaoService.newChildAssoc(node, childNode, assocRef.isPrimary(), assocTypeQName, assocRef.getQName()); - // ensure that the name uniqueness is enforced for the child node - setChildUniqueName(childNode); - } - properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - } - // restore source associations - Collection sourceAssocRefs = (Collection) 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 targetAssocRefs = (Collection) 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); - } - /** - * 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 + * Drops the old primary association and creates a new one */ - private void setChildUniqueName(Node childNode) + public ChildAssociationRef moveNode( + NodeRef nodeToMoveRef, + NodeRef newParentRef, + QName assocTypeQName, + QName assocQName) { - // get the name property - Map properties = childNode.getProperties(); - QNameEntity nameQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_NAME); - PropertyValue nameValue = properties.get(nameQNameEntity.getId()); - String useName = null; - if (nameValue == null) + Pair nodeToMovePair = getNodePairNotNull(nodeToMoveRef); + Pair parentNodePair = getNodePairNotNull(newParentRef); + + Long nodeToMoveId = nodeToMovePair.getFirst(); + QName nodeToMoveTypeQName = nodeDaoService.getNodeType(nodeToMoveId); + NodeRef oldNodeToMoveRef = nodeToMovePair.getSecond(); + Long parentNodeId = parentNodePair.getFirst(); + NodeRef parentNodeRef = parentNodePair.getSecond(); + StoreRef oldStoreRef = oldNodeToMoveRef.getStoreRef(); + StoreRef newStoreRef = parentNodeRef.getStoreRef(); + NodeRef newNodeToMoveRef = new NodeRef(newStoreRef, oldNodeToMoveRef.getId()); + Pair newNodeToMovePair = new Pair(nodeToMoveId, newNodeToMoveRef); + + // Get the primary parent association + Pair oldParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeToMoveId); + if (oldParentAssocPair == null) { - // no name has been assigned, so assign the ID of the child node - useName = childNode.getUuid(); + // The node doesn't have parent. Moving it is not possible. + throw new IllegalArgumentException("Node " + nodeToMoveId + " doesn't have a parent. Use 'addChild' instead of move."); + } + Long oldParentAssocId = oldParentAssocPair.getFirst(); + ChildAssociationRef oldParentAssocRef = oldParentAssocPair.getSecond(); + + // Shortcut this whole process if nothing has changed + if (EqualsHelper.nullSafeEquals(oldParentAssocRef.getParentRef(), newParentRef) && + EqualsHelper.nullSafeEquals(oldParentAssocRef.getTypeQName(), assocTypeQName) && + EqualsHelper.nullSafeEquals(oldParentAssocRef.getQName(), assocQName)) + { + // It's all just the same + return oldParentAssocRef; + } + + boolean movingStore = !oldStoreRef.equals(newStoreRef); + // Handle store conflicts + if (movingStore) + { + handleStoreMoveConflicts(nodeToMovePair, newStoreRef); + } + + // Invoke policy behaviour + if (movingStore) + { + invokeBeforeDeleteNode(nodeToMoveRef); + invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName); } else { - useName = (String) nameValue.getValue(DataTypeDefinition.TEXT); + invokeBeforeDeleteChildAssociation(oldParentAssocRef); + invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName, false); } - // get all the parent assocs - Collection parentAssocs = nodeDaoService.getParentAssocs(childNode); - for (ChildAssoc assoc : parentAssocs) + + // Handle store moves + if (movingStore) { - QName assocTypeQName = assoc.getTypeQName().getQName(); - AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); - if (!assocDef.isChild()) + Pair newNodePair = nodeDaoService.moveNodeToStore(nodeToMoveId, newStoreRef); + if (!newNodePair.equals(newNodeToMovePair)) { - throw new DataIntegrityViolationException("Child association has non-child type: " + assoc.getId()); + throw new RuntimeException("Store-moved pair isn't expected: " + newNodePair + " != " + newNodeToMovePair); } - ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; - if (childAssocDef.getDuplicateChildNamesAllowed()) + } + + // Modify the association directly. We do this AFTER the change of the node's store so that + // the association reference returned is correct. + Pair newParentAssocPair = nodeDaoService.updateChildAssoc( + oldParentAssocId, + parentNodeId, + nodeToMoveId, + assocTypeQName, + assocQName, + -1); + ChildAssociationRef newParentAssocRef = newParentAssocPair.getSecond(); + + // Handle indexing differently if it is a store move + if (movingStore) + { + // The association existed before and the node is moving to a new store + nodeIndexer.indexDeleteNode(oldParentAssocRef); + nodeIndexer.indexCreateNode(newParentAssocRef); + } + else + { + // The node is in the same store and is just having it's child association modified + nodeIndexer.indexUpdateChildAssociation(oldParentAssocRef, newParentAssocRef); + } + + // Ensure name uniqueness + setChildNameUnique(newParentAssocPair, newNodeToMovePair); + + // Check that there is not a cyclic relationship + getPaths(newNodeToMoveRef, false); + + // Call behaviours + if (movingStore) + { + Set nodeToMoveAspectQNames = nodeDaoService.getNodeAspects(nodeToMoveId); + // The Node changes NodeRefs, so this is really the deletion of the old node and creation + // of a node in a new store as far as the clients are concerned. + invokeOnDeleteNode(oldParentAssocRef, nodeToMoveTypeQName, nodeToMoveAspectQNames, true); + invokeOnCreateNode(newParentAssocRef); + } + else + { + invokeOnCreateChildAssociation(newParentAssocRef, false); + invokeOnDeleteChildAssociation(oldParentAssocRef); + invokeOnMoveNode(oldParentAssocRef, newParentAssocRef); + } + + // If we have to cascade in the transaction, then pull the children over to the new store + if (cascadeInTransaction) + { + // Pull children to the new store + pullNodeChildrenToSameStore(newNodeToMovePair, true, true); + } + + // Done + return newParentAssocRef; + } + + /** + * Silently gives any clashing target nodes a new UUID + * @param nodeToMovePair the node that will be moved + * @param newStoreRef the store that the node will be moved to + */ + private void handleStoreMoveConflicts(Pair nodeToMovePair, StoreRef newStoreRef) + { + NodeRef oldNodeToMoveRef = nodeToMovePair.getSecond(); + NodeRef newNodeToMoveRef = new NodeRef(newStoreRef, oldNodeToMoveRef.getId()); + // If the new node reference is already taken, then give it a new uuid + Pair conflictingNodePair = nodeDaoService.getNodePair(newNodeToMoveRef); + if (conflictingNodePair != null) + { + // We are creating a new node. This noderef will be reused, so will be an update + nodeDaoService.updateNode(conflictingNodePair.getFirst(), null, GUID.generate(), null); + } + } + + /** + * This process is less invasive than the move method as the child associations + * do not need to be remade. If the children are in the same store, only the indexChildren + * value is needed. + */ + private void pullNodeChildrenToSameStore(Pair nodePair, boolean cascade, boolean indexChildren) + { + Long nodeId = nodePair.getFirst(); + NodeRef nodeRef = nodePair.getSecond(); + StoreRef storeRef = nodeRef.getStoreRef(); + // Get the node's children, but only one's that aren't in the same store + final List> childNodePairs = new ArrayList>(5); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair + ) { - // the name is irrelevant, so it doesn't need to be put into the unique key - nodeDaoService.setChildNameUnique(assoc, null); + // Add it + childNodePairs.add(childNodePair); + return false; + } + }; + // We only need to move child nodes that are not already in the same store + nodeDaoService.getPrimaryChildAssocsNotInSameStore(nodeId, callback); + // Each child must be moved to the same store as the parent + for (Pair oldChildNodePair : childNodePairs) + { + Long childNodeId = oldChildNodePair.getFirst(); + NodeRef childNodeRef = oldChildNodePair.getSecond(); + QName childNodeTypeQName = nodeDaoService.getNodeType(childNodeId); + Set childNodeAspectQNames = nodeDaoService.getNodeAspects(childNodeId); + Pair oldParentAssocPair = nodeDaoService.getPrimaryParentAssoc(childNodeId); + Pair newChildNodePair = oldChildNodePair; + Pair newParentAssocPair = oldParentAssocPair; + ChildAssociationRef newParentAssocRef = newParentAssocPair.getSecond(); + // Fire node policies. This ensures that each node in the hierarchy gets a notification fired. + invokeBeforeDeleteNode(childNodeRef); + invokeBeforeCreateNode( + newParentAssocRef.getParentRef(), + newParentAssocRef.getTypeQName(), + newParentAssocRef.getQName(), + childNodeTypeQName); + // Move the node + handleStoreMoveConflicts(oldChildNodePair, storeRef); + // Change the store + newChildNodePair = nodeDaoService.moveNodeToStore(oldChildNodePair.getFirst(), storeRef); + // Get the new parent assoc + newParentAssocPair = nodeDaoService.getPrimaryParentAssoc(childNodeId); + // Index + if (indexChildren) + { + nodeIndexer.indexDeleteNode(oldParentAssocPair.getSecond()); + nodeIndexer.indexCreateNode(newParentAssocPair.getSecond()); } else { - nodeDaoService.setChildNameUnique(assoc, useName); + // The node we have just moved doesn't have it's children indexed, so tag it + nodeDaoService.addNodeAspects(childNodeId, Collections.singleton(ContentModel.ASPECT_INDEX_CHILDREN)); + } + // Fire node policies. This ensures that each node in the hierarchy gets a notification fired. + invokeOnDeleteNode(oldParentAssocPair.getSecond(), childNodeTypeQName, childNodeAspectQNames, true); + invokeOnCreateNode(newParentAssocPair.getSecond()); + // Cascade, if required + if (cascade) + { + pullNodeChildrenToSameStore(newChildNodePair, cascade, indexChildren); } } - // done - if (logger.isDebugEnabled()) + } + + private void indexChildren(Pair nodePair, boolean cascade) + { + Long nodeId = nodePair.getFirst(); + // Get the node's children, but only one's that aren't in the same store + final List> childNodePairs = new ArrayList>(5); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() { - logger.debug("Unique name set for all " + parentAssocs.size() + " parent associations: \n" + " name: " + useName); + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair + ) + { + // Add it + childNodePairs.add(childNodePair); + return false; + } + }; + nodeDaoService.getPrimaryChildAssocs(nodeId, callback); + // Each child must be moved to the same store as the parent + for (Pair oldChildNodePair : childNodePairs) + { + Long childNodeId = oldChildNodePair.getFirst(); + NodeRef oldChildNodeRef = oldChildNodePair.getSecond(); + Pair newChildNodePair = oldChildNodePair; + // Touch the node child node so that index tracking will work + nodeDaoService.setNodeStatus(childNodeId); + // Index + nodeIndexer.indexUpdateNode(oldChildNodeRef); + // Cascade, if required + if (cascade) + { + indexChildren(newChildNodePair, cascade); + } + else + { + // We didn't cascade to the children, so tag the node to index the children later + nodeDaoService.addNodeAspects(childNodeId, Collections.singleton(ContentModel.ASPECT_INDEX_CHILDREN)); + } + } + // We have indexed the children, so remove the tagging aspect + nodeDaoService.removeNodeAspects(nodeId, Collections.singleton(ContentModel.ASPECT_INDEX_CHILDREN)); + } + + public NodeRef getStoreArchiveNode(StoreRef storeRef) + { + StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + if (archiveStoreRef == null) + { + // no mapping for the given store + return null; + } + else + { + return getRootNode(archiveStoreRef); } } + + private String extractNameProperty(Map propertyValues) + { + PropertyValue nameValue = propertyValues.get(ContentModel.PROP_NAME); + if (nameValue == null) + { + return null; + } + String name = (String) nameValue.getValue(DataTypeDefinition.TEXT); + return name; + } + + private void setChildNameUnique(Pair childAssocPair, Pair childNodePair) + { + // Get the node's existing name + PropertyValue namePropertyValue = nodeDaoService.getNodeProperty(childNodePair.getFirst(), ContentModel.PROP_NAME); + String nameValue = null; + if (namePropertyValue != null) + { + nameValue = (String) namePropertyValue.getValue(DataTypeDefinition.TEXT); + } + setChildNameUnique(childAssocPair, nameValue, null); + } + + /** + * Ensures name uniqueness for the child and the child association. Note that nothing is done if the + * association type doesn't enforce name uniqueness. + */ + private void setChildNameUnique(Pair childAssocPair, String newName, String oldName) + { + if (childAssocPair == null) + { + // This happens if the node is a root node + return; + } + else if (EqualsHelper.nullSafeEquals(newName, oldName)) + { + // The name has not changed + return; + } + Long assocId = childAssocPair.getFirst(); + QName assocTypeQName = childAssocPair.getSecond().getTypeQName(); + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (!assocDef.isChild()) + { + throw new IllegalArgumentException("Child association has non-child type: " + assocId); + } + ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; + if (!childAssocDef.getDuplicateChildNamesAllowed()) + { + nodeDaoService.setChildNameUnique(assocId, newName); + } + } + + @Override + protected List cleanupImpl() + { + List moveChildrenResults = moveChildrenToCorrectStore(); + List indexChildrenResults = indexChildrenWhereRequired(); + + List allResults = new ArrayList(100); + allResults.addAll(moveChildrenResults); + allResults.addAll(indexChildrenResults); + // Done + return allResults; + } + + private List moveChildrenToCorrectStore() + { + final List> parentNodePairs = new ArrayList>(100); + final NodeRefQueryCallback callback = new NodeRefQueryCallback() + { + public boolean handle(Pair nodePair) + { + parentNodePairs.add(nodePair); + return true; + } + }; + RetryingTransactionCallback getNodesCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + nodeDaoService.getNodesWithChildrenInDifferentStores(Long.MIN_VALUE, 100, callback); + // Done + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(getNodesCallback, true, true); + // Process the nodes in random order + Collections.shuffle(parentNodePairs); + // Iterate and operate + List results = new ArrayList(100); + for (final Pair parentNodePair : parentNodePairs) + { + RetryingTransactionCallback fixNodesCallback = new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + // Pull the children to the same store with full indexing - but don't cascade. + pullNodeChildrenToSameStore(parentNodePair, true, true); + // Done + return null; + } + }; + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setMaxRetries(1); + try + { + txnHelper.doInTransaction(fixNodesCallback, false, true); + String msg = + "Moved child nodes to parent node's store: \n" + + " Parent node: " + parentNodePair.getFirst(); + results.add(msg); + } + catch (Throwable e) + { + String msg = + "Failed to move child nodes to parent node's store: \n" + + " Parent node: " + parentNodePair.getFirst() + "\n" + + " Error: " + e.getMessage(); + // It failed, which is not an error to consider here + logger.warn(msg, e); + results.add(msg); + } + } + // Done + if (logger.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(256); + sb.append("Moved children to correct stores: \n") + .append(" Results:\n"); + for (String msg : results) + { + sb.append(" ").append(msg).append("\n"); + } + logger.debug(sb.toString()); + } + return results; + } + + private List indexChildrenWhereRequired() + { + final List> parentNodePairs = new ArrayList>(100); + final NodeRefQueryCallback callback = new NodeRefQueryCallback() + { + public boolean handle(Pair nodePair) + { + parentNodePairs.add(nodePair); + return true; + } + }; + RetryingTransactionCallback getNodesCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + nodeDaoService.getNodesWithAspect(ContentModel.ASPECT_INDEX_CHILDREN, Long.MIN_VALUE, 100, callback); + // Done + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(getNodesCallback, true, true); + // Process the nodes in random order + Collections.shuffle(parentNodePairs); + // Iterate and operate + List results = new ArrayList(100); + for (final Pair parentNodePair : parentNodePairs) + { + RetryingTransactionCallback indexChildrenCallback = new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + // Index children without full cascade + indexChildren(parentNodePair, true); + // Done + return null; + } + }; + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setMaxRetries(1); + try + { + txnHelper.doInTransaction(indexChildrenCallback, false, true); + String msg = + "Indexed child nodes: \n" + + " Parent node: " + parentNodePair.getFirst(); + results.add(msg); + } + catch (Throwable e) + { + String msg = + "Failed to index child nodes: \n" + + " Parent node: " + parentNodePair.getFirst() + "\n" + + " Error: " + e.getMessage(); + // It failed, which is not an error to consider here + logger.warn(msg, e); + results.add(msg); + } + } + // Done + if (logger.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(256); + sb.append("Indexed child nodes: \n") + .append(" Results:\n"); + for (String msg : results) + { + sb.append(" ").append(msg).append("\n"); + } + logger.debug(sb.toString()); + } + return results; + } } diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index e9755a4747..47633e8f1b 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -39,6 +39,7 @@ import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.node.BaseNodeServiceTest; +import org.alfresco.repo.node.StoreArchiveMap; import org.alfresco.repo.node.db.NodeDaoService.NodePropertyHandler; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -49,8 +50,10 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; /** * @see org.alfresco.repo.node.db.DbNodeServiceImpl @@ -66,6 +69,10 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest protected NodeService getNodeService() { + // Force cascading + DbNodeServiceImpl dbNodeServiceImpl = (DbNodeServiceImpl) applicationContext.getBean("dbNodeServiceImpl"); + dbNodeServiceImpl.setCascadeInTransaction(true); + return (NodeService) applicationContext.getBean("dbNodeService"); } @@ -207,13 +214,13 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest public Object execute() { // check n6 - NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false); + NodeRef.Status n6Status = nodeDaoService.getNodeRefStatus(n6Ref); if (!n6Status.isDeleted()) { throw new RuntimeException("Deleted node does not have deleted status"); } // n8 is a primary child - it should be deleted too - NodeStatus n8Status = nodeDaoService.getNodeStatus(n8Ref, false); + NodeRef.Status n8Status = nodeDaoService.getNodeRefStatus(n8Ref); if (!n8Status.isDeleted()) { throw new RuntimeException("Cascade-deleted node does not have deleted status"); @@ -301,7 +308,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest final List allContentDatas = new ArrayList(500); NodePropertyHandler handler = new NodePropertyHandler() { - public void handle(Node node, Serializable value) + public void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value) { allContentDatas.add(value); } @@ -356,11 +363,11 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest final NodeRef n8Ref = n6pn8Ref.getChildRef(); // Add a make n1 a second primary parent of n8 - Node n1 = nodeDaoService.getNode(n1Ref); - Node n8 = nodeDaoService.getNode(n8Ref); - ChildAssoc assoc = nodeDaoService.newChildAssoc( - n1, - n8, + Pair n1Pair = nodeDaoService.getNodePair(n1Ref); + Pair n8Pair = nodeDaoService.getNodePair(n8Ref); + Pair assocPair = nodeDaoService.newChildAssoc( + n1Pair.getFirst(), + n8Pair.getFirst(), true, ContentModel.ASSOC_CONTAINS, QName.createQName(NAMESPACE, "n1pn8")); @@ -386,4 +393,41 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest // Delete the node nodeService.deleteNode(nodeRef); } + + public void testCleanup() throws Exception + { + @SuppressWarnings("unchecked") + StoreArchiveMap storeArchiveMap = (StoreArchiveMap) applicationContext.getBean("storeArchiveMap"); + DbNodeServiceImpl ns = (DbNodeServiceImpl) applicationContext.getBean("dbNodeServiceImpl"); + ns.setCascadeInTransaction(false); + + NodeRef parentNodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(NAMESPACE, this.getName()), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef childNodeRef = nodeService.createNode( + parentNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NAMESPACE, this.getName()), + ContentModel.TYPE_FOLDER).getChildRef(); + + // Ensure that the archive feature is enabled + StoreRef archiveStoreRef = ns.createStore("test", getName() + "-" + System.currentTimeMillis()); + storeArchiveMap.getArchiveMap().put(parentNodeRef.getStoreRef(), archiveStoreRef); + + // Delete parent. Cascade is OFF, so children should be left in their current store. + ns.deleteNode(parentNodeRef); + // Check that the node n1 is in the archive store + assertFalse("Parent should be deleted", ns.exists(parentNodeRef)); + NodeRef parentArchiveRef = new NodeRef(archiveStoreRef, parentNodeRef.getId()); + assertTrue("Parent should be in the archive store", ns.exists(parentArchiveRef)); + + // Force a commit here + setComplete(); + endTransaction(); + + // Run cleanup + ns.cleanup(); + } } diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 6a42fe6905..64d08c3bd6 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -27,19 +27,23 @@ package org.alfresco.repo.node.db; import java.io.Serializable; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Set; 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.Store; +import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.domain.Transaction; +import org.alfresco.repo.domain.hibernate.DirtySessionAnnotation; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; /** * Service layer accessing persistent node entities directly @@ -65,89 +69,113 @@ public interface NodeDaoService * * @return Returns a list of stores */ - public List getStores(); + @DirtySessionAnnotation(markDirty=false) + public List getStoreRefs(); + + @DirtySessionAnnotation(markDirty=false) + public Pair getRootNode(StoreRef storeRef); /** - * Creates a unique store for the given protocol and identifier combination - * - * @param protocol a protocol, e.g. {@link org.alfresco.service.cmr.repository.StoreRef#PROTOCOL_WORKSPACE} - * @param identifier a protocol-specific identifier - * @return Returns the new persistent entity + * Creates a unique store for the given protocol and identifier combination. + * The root node is created with the "root" aspect. + * @return Returns the root node, which is added automatically. + * @throws StoreExistsException if the store already exists */ - public Store createStore(String protocol, String identifier); + @DirtySessionAnnotation(markDirty=true) + public Pair createStore(StoreRef storeRef); + @DirtySessionAnnotation(markDirty=false) + public NodeRef.Status getNodeRefStatus(NodeRef nodeRef); /** - * Deletes the unique store for the given protocol and identifier combination - * - * @param protocol a protocol, e.g. {@link org.alfresco.service.cmr.repository.StoreRef#PROTOCOL_WORKSPACE} - * @param identifier a protocol-specific identifier - */ - public void deleteStore(String protocol, String identifier); - - /** - * @param protocol the protocol that the store serves - * @param identifier the protocol-specific identifer - * @return Returns a store with the given values or null if one doesn't exist - */ - public Store getStore(String protocol, String identifier); - - /** - * Gets the node's status. If the node never existed, then - * null is returned. - * - * @param nodeRef the node reference - * @param create true if the node status is to be updated in the transaction, i.e. - * the current transaction must be assigned to the status - * @return Returns the node status if the node exists or once existed, otherwise - * returns null if create == false - */ - public NodeStatus getNodeStatus(NodeRef nodeRef, boolean update); - - /** - * Sets the current transaction ID on the node status. Note that the node - * may not exist, but the status will. - * - * @param nodeRef the node reference - */ - public void recordChangeId(NodeRef nodeRef); - - /** - * @param store the store to which the node must belong + * @param storeRef the store to which the node must belong * @param uuid the node store-unique identifier * @param nodeTypeQName the type of the node - * @return Returns a new node of the given type and attached to the store + * @return Returns a new node Id of the given type and attached to the store * @throws InvalidTypeException if the node type is invalid or if the node type * is not a valid real node */ - public Node newNode(Store store, String uuid, QName nodeTypeQName) throws InvalidTypeException; + @DirtySessionAnnotation(markDirty=true) + public Pair newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException; + + @DirtySessionAnnotation(markDirty=true) + public Pair moveNodeToStore(Long nodeId, StoreRef storeRef); /** * @param nodeRef the node reference - * @return Returns the node entity + * @return Returns the node entity ID */ - public Node getNode(NodeRef nodeRef); + @DirtySessionAnnotation(markDirty=false) + public Pair getNodePair(NodeRef nodeRef); + + @DirtySessionAnnotation(markDirty=false) + public Pair getNodePair(Long nodeId); + + @DirtySessionAnnotation(markDirty=false) + public QName getNodeType(Long nodeId); + + @DirtySessionAnnotation(markDirty=true) + public void setNodeStatus(Long nodeId); + + @DirtySessionAnnotation(markDirty=false) + public Long getNodeAccessControlList(Long nodeId); + + @DirtySessionAnnotation(markDirty=true) + public void setNodeAccessControlList(Long nodeId, Long aclId); /** - * Deletes the node instance, taking care of any cascades that are required over - * and above those provided by the persistence mechanism. - *

- * A caller must able to delete the node using this method and not have to follow - * up with any other ancillary deletes - * - * @param node the entity to delete - * @param cascade true if the assoc deletions must cascade to primary child nodes + * @param storeRef the new store or null to keep the existing one + * @param uuid the new UUID for the node or null to keep it the same + * @param nodeTypeQName the new type QName for the node or null to keep the existing one */ - public void deleteNode(Node node, boolean cascade); + @DirtySessionAnnotation(markDirty=true) + public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName); + + @DirtySessionAnnotation(markDirty=false) + public PropertyValue getNodeProperty(Long nodeId, QName propertyQName); + + @DirtySessionAnnotation(markDirty=false) + public Map getNodeProperties(Long nodeId); + + @DirtySessionAnnotation(markDirty=true) + public void addNodeProperty(Long nodeId, QName qname, PropertyValue propertyValue); + + @DirtySessionAnnotation(markDirty=true) + public void addNodeProperties(Long nodeId, Map properties); + + @DirtySessionAnnotation(markDirty=true) + public void removeNodeProperties(Long nodeId, Set propertyQNames); + + @DirtySessionAnnotation(markDirty=true) + public void setNodeProperties(Long nodeId, Map properties); + + @DirtySessionAnnotation(markDirty=false) + public Set getNodeAspects(Long nodeId); + + @DirtySessionAnnotation(markDirty=true) + public void addNodeAspects(Long nodeId, Set aspectQNames); + + @DirtySessionAnnotation(markDirty=true) + public void removeNodeAspects(Long nodeId, Set aspectQNames); + + @DirtySessionAnnotation(markDirty=false) + public boolean hasNodeAspect(Long nodeId, QName aspectQName); /** - * @return Returns the persisted and filled association + * Deletes the node and all entities + */ + @DirtySessionAnnotation(markDirty=true) + public void deleteNode(Long nodeId); + + /** + * @return Returns the persisted and filled association's ID * * @see ChildAssoc */ - public ChildAssoc newChildAssoc( - Node parentNode, - Node childNode, + @DirtySessionAnnotation(markDirty=true) + public Pair newChildAssoc( + Long parentNodeId, + Long childNodeId, boolean isPrimary, QName assocTypeQName, QName qname); @@ -155,126 +183,210 @@ public interface NodeDaoService /** * Change the name of the child node. * - * @param childAssoc the child association to change + * @param childId the child association to change * @param childName the name to put on the association */ - public void setChildNameUnique(ChildAssoc childAssoc, String childName); + @DirtySessionAnnotation(markDirty=false) + public void setChildNameUnique(Long assocId, String childName); /** - * Get the statuses of all the child primary child nodes of the given parent + * @param index the association index. -1 to keep the existing value */ - public Collection getPrimaryChildNodeStatuses(final Node parentNode); + @DirtySessionAnnotation(markDirty=true) + public Pair updateChildAssoc( + Long childAssocId, + Long parentNodeId, + Long childNodeId, + QName assocTypeQName, + QName qname, + int index); /** - * Get all child associations for a given node + * Interface used to iterate over results from child association queries + * @author Derek Hulley + */ + public interface ChildAssocRefQueryCallback + { + /** + * + * @return Return true if resursion into the child node + * is required. + */ + boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair + ); + } + + /** + * Get a collection of all child association references for a given parent node. + *

+ * WARNING: Be sure selective when doing this call recursively. * - * @param parentNode the parent of the child associations - * @return Returns all child associations for the given node + * @param parentNodeId the parent node + * @param resultsCallback the callback that will be called with the results + * @param recurse if true then iterate over the entire tree of nodes. + * Resursion is done top-down i.e. the first level children are all + * enumerated first, followed by all second level children and so on. */ - public Collection getChildAssocs(final Node parentNode); + @DirtySessionAnnotation(markDirty=false) + public void getChildAssocs(Long parentNodeId, ChildAssocRefQueryCallback resultsCallback, boolean recurse); /** * Get a collection of all child association references for a given parent node. * - * @param parentNode the parent node - * @return Returns a collection of association references + * @param parentNodeId the parent node + * @param resultsCallback the callback that will be called with the results */ - public Collection getChildAssocRefs(Node parentNode); + @DirtySessionAnnotation(markDirty=false) + public void getChildAssocs(Long parentNodeId, QName assocQName, ChildAssocRefQueryCallback resultsCallback); + + @DirtySessionAnnotation(markDirty=false) + public void getChildAssocsByTypeQNames( + Long parentNodeId, + List assocTypeQNames, + ChildAssocRefQueryCallback resultsCallback); + + @DirtySessionAnnotation(markDirty=false) + public void getChildAssocsByTypeQNameAndQName( + Long parentNodeId, + QName assocTypeQName, + QName assocQName, + ChildAssocRefQueryCallback resultsCallback); + + @DirtySessionAnnotation(markDirty=false) + public void getPrimaryChildAssocs(Long parentNodeId, ChildAssocRefQueryCallback resultsCallback); + + @DirtySessionAnnotation(markDirty=false) + public void getPrimaryChildAssocsNotInSameStore(Long parentNodeId, ChildAssocRefQueryCallback resultsCallback); /** - * Get a collection of all child association references for a given parent node. - * - * @param parentNode the parent node - * @return Returns a collection of association references + * Interface used to iterate over pure node results + * @author Derek Hulley */ - public Collection getChildAssocRefs(Node parentNode, QName assocQName); + public interface NodeRefQueryCallback + { + /** + * + * @param nodePair the node result + * @return Returns true if more results are required + */ + boolean handle(Pair nodePair); + } + + @DirtySessionAnnotation(markDirty=false) + public void getNodesWithChildrenInDifferentStores(Long minNodeId, int count, NodeRefQueryCallback resultsCallback); + + @DirtySessionAnnotation(markDirty=false) + public void getNodesWithAspect(QName aspectQName, Long minNodeId, int count, NodeRefQueryCallback resultsCallback); + + /** + * @return Returns an association matching the given parent, type and child name - or null + * if not found + */ + @DirtySessionAnnotation(markDirty=false) + public Pair getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName); /** * @return Returns a matching association or null if one was not found * * @see ChildAssoc */ - public ChildAssoc getChildAssoc( - Node parentNode, - Node childNode, + @DirtySessionAnnotation(markDirty=false) + public Pair getChildAssoc( + Long parentNodeId, + Long childNodeId, QName assocTypeQName, QName qname); - /** - * @return Returns an association matching the given parent, type and child name - or null - * if not found - */ - public ChildAssoc getChildAssoc(Node parentNode, QName assocTypeQName, String childName); - /** * Deletes an explicit child association. * * @return Returns true if the association was deleted, otherwise false */ + @DirtySessionAnnotation(markDirty=true) public boolean deleteChildAssoc( - final Node parentNode, - final Node childNode, + final Long parentNodeId, + final Long childNodeId, final QName assocTypeQName, final QName qname); /** * @param assoc the child association to remove - * @param cascade true if the assoc deletions must cascade to primary child nodes */ - public void deleteChildAssoc(ChildAssoc assoc, boolean cascade); + @DirtySessionAnnotation(markDirty=true) + public void deleteChildAssoc(Long childAssocId); /** * Finds the association between the node's primary parent and the node itself - * - * @param childNode the child node - * @return Returns the primary ChildAssoc instance where the given node is the child. - * The return value could be null for a root node - but ONLY a root node */ - public ChildAssoc getPrimaryParentAssoc(Node childNode); + @DirtySessionAnnotation(markDirty=false) + public Pair getPrimaryParentAssoc(Long childNodeId); /** * Get all parent associations for the node. This methods includes a cache safety check. * @param childNode the child node * @return Returns all parent associations for the node. */ - public Collection getParentAssocs(Node childNode); + @DirtySessionAnnotation(markDirty=false) + public Collection> getParentAssocs(final Long childNodeId); /** * @return Returns the persisted and filled association * @see NodeAssoc */ - public NodeAssoc newNodeAssoc( - Node sourceNode, - Node targetNode, + @DirtySessionAnnotation(markDirty=true) + public Pair newNodeAssoc( + Long sourceNodeId, + Long targetNodeId, QName assocTypeQName); /** * @return Returns a list of all node associations associated with the given node */ - public List getNodeAssocsToAndFrom(final Node node); + @DirtySessionAnnotation(markDirty=false) + public Collection> getNodeAssocsToAndFrom(final Long nodeId); /** * @return Returns the node association or null if not found */ - public NodeAssoc getNodeAssoc( - Node sourceNode, - Node targetNode, - QName assocTypeQName); + @DirtySessionAnnotation(markDirty=false) + public Pair getNodeAssoc(Long sourceNodeId, Long targetNodeId, QName assocTypeQName); /** * @return Returns all the node associations where the node is the source */ - public List getTargetNodeAssocs(Node sourceNode); + @DirtySessionAnnotation(markDirty=false) + public Collection> getTargetNodeAssocs(Long sourceNodeId); /** * @return Returns all the node associations where the node is the target */ - public List getSourceNodeAssocs(Node targetNode); + @DirtySessionAnnotation(markDirty=false) + public Collection> getSourceNodeAssocs(Long targetNodeId); /** * @param assoc the node association to remove */ - public void deleteNodeAssoc(NodeAssoc assoc); + @DirtySessionAnnotation(markDirty=true) + public void deleteNodeAssoc(Long assocId); + + /** + * Iterate over all nodes that have a given property type with a given string value. + * + * @param storeRef the store to search in + * @param propertyQName the qualified name of the property + * @param value the string value to match + * @param handler the callback to use while iterating over the URLs + * @return Returns the values for the given type definition + */ + @DirtySessionAnnotation(markDirty=true) + public void getPropertyValuesByPropertyAndValue( + StoreRef storeRef, + QName propertyQName, + String value, + NodePropertyHandler handler); /** * Iterate over all property values for the given type definition. This will also dig out values that @@ -284,21 +396,18 @@ public interface NodeDaoService * @param handler the callback to use while iterating over the URLs * @return Returns the values for the given type definition */ + @DirtySessionAnnotation(markDirty=true) public void getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition, NodePropertyHandler handler); - /** - * Get properties with the given type and string value. - * TODO: Refactor as in getPropertyValuesByActualType - */ - public Collection getNodesWithPropertyStringValueForStore(StoreRef storeRef, QName propQName, String propStringValue); - /** * @return Returns the total number of nodes in the ADM repository */ + @DirtySessionAnnotation(markDirty=false) public int getNodeCount(); /** * @return Returns the total number of nodes in the ADM store */ + @DirtySessionAnnotation(markDirty=false) public int getNodeCount(final StoreRef storeRef); /** @@ -309,9 +418,10 @@ public interface NodeDaoService */ public interface NodePropertyHandler { - void handle(Node node, Serializable value); + void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value); } + @DirtySessionAnnotation(markDirty=true) public Transaction getTxnById(long txnId); /** * Get all transactions in a given time range. Since time-based retrieval doesn't guarantee uniqueness @@ -320,6 +430,7 @@ public interface NodeDaoService * @param excludeTxnIds a list of txn IDs to ignore. null is allowed. * @param remoteOnly true if locally-written transactions must be ignored */ + @DirtySessionAnnotation(markDirty=true) public List getTxnsByCommitTimeAscending( long fromTimeInclusive, long toTimeExclusive, @@ -333,6 +444,7 @@ public interface NodeDaoService * @param excludeTxnIds a list of txn IDs to ignore. null is allowed. * @param remoteOnly true if locally-written transactions must be ignored */ + @DirtySessionAnnotation(markDirty=true) public List getTxnsByCommitTimeDescending( long fromTimeInclusive, long toTimeExclusive, @@ -345,10 +457,21 @@ public interface NodeDaoService * @param includeTxnIds a list of transaction IDs to search for * @return Returns the transactions by commit time for the given IDs */ + @DirtySessionAnnotation(markDirty=true) public List getTxnsByMinCommitTime(List includeTxnIds); + + @DirtySessionAnnotation(markDirty=false) public int getTxnUpdateCount(final long txnId); + + @DirtySessionAnnotation(markDirty=false) public int getTxnDeleteCount(final long txnId); + + @DirtySessionAnnotation(markDirty=false) public int getTransactionCount(); + + @DirtySessionAnnotation(markDirty=false) public List getTxnChangesForStore(final StoreRef storeRef, final long txnId); + + @DirtySessionAnnotation(markDirty=false) public List getTxnChanges(final long txnId); } diff --git a/source/java/org/alfresco/repo/node/db/NodeServiceCleanupJob.java b/source/java/org/alfresco/repo/node/db/NodeServiceCleanupJob.java new file mode 100644 index 0000000000..727e159462 --- /dev/null +++ b/source/java/org/alfresco/repo/node/db/NodeServiceCleanupJob.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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 + * of the License, 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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.node.db; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.NodeService; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Prompts the Node Service to perform regular cleanup operations. + * + * @see NodeService#cleanup() + * + * @author Derek Hulley + * @since 2.1.6 + */ +public class NodeServiceCleanupJob implements Job +{ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the content cleaner to use + Object nodeServiceObj = jobData.get("nodeService"); + if (nodeServiceObj == null || !(nodeServiceObj instanceof NodeService)) + { + throw new AlfrescoRuntimeException( + "NodeServiceCleanupJob data must contain valid 'nodeService' reference"); + } + NodeService nodeService = (NodeService) nodeServiceObj; + nodeService.cleanup(); + } +} diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index bb08b39f26..c804bc431a 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -27,9 +27,10 @@ package org.alfresco.repo.node.db.hibernate; import java.io.Serializable; import java.net.InetAddress; import java.net.UnknownHostException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -40,6 +41,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.DbAccessControlList; import org.alfresco.repo.domain.NamespaceEntity; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.NodeAssoc; @@ -52,7 +54,11 @@ import org.alfresco.repo.domain.Server; import org.alfresco.repo.domain.Store; import org.alfresco.repo.domain.StoreKey; import org.alfresco.repo.domain.Transaction; +import org.alfresco.repo.domain.UsageDeltaDAO; import org.alfresco.repo.domain.hibernate.ChildAssocImpl; +import org.alfresco.repo.domain.hibernate.DMPermissionsDaoComponentImpl; +import org.alfresco.repo.domain.hibernate.DbAccessControlListImpl; +import org.alfresco.repo.domain.hibernate.DirtySessionMethodInterceptor; import org.alfresco.repo.domain.hibernate.NodeAssocImpl; import org.alfresco.repo.domain.hibernate.NodeImpl; import org.alfresco.repo.domain.hibernate.NodeStatusImpl; @@ -60,29 +66,38 @@ import org.alfresco.repo.domain.hibernate.ServerImpl; import org.alfresco.repo.domain.hibernate.StoreImpl; import org.alfresco.repo.domain.hibernate.TransactionImpl; import org.alfresco.repo.node.db.NodeDaoService; -import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.security.permissions.ACLType; +import org.alfresco.repo.security.permissions.AccessControlListProperties; +import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties; +import org.alfresco.repo.security.permissions.impl.AclChange; +import org.alfresco.repo.security.permissions.impl.AclDaoComponent; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionAwareSingleton; import org.alfresco.repo.transaction.TransactionalDao; -import org.alfresco.repo.usage.UsageDeltaDAO; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.InvalidTypeException; 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.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConverter; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; +import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.hibernate.HibernateException; import org.hibernate.ObjectDeletedException; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.Session; +import org.hibernate.StaleStateException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.hibernate3.HibernateCallback; @@ -96,35 +111,36 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService, TransactionalDao { private static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; - private static final String QUERY_GET_PRIMARY_CHILD_NODE_STATUSES = "node.GetPrimaryChildNodeStatuses"; - private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs"; + private static final String QUERY_GET_CHILD_NODE_IDS = "node.GetChildNodeIds"; private static final String QUERY_GET_CHILD_ASSOCS_BY_ALL = "node.GetChildAssocsByAll"; - private static final String QUERY_GET_CHILD_ASSOC_ID_TYPE_AND_BY_NAME = "node.GetChildAssocIdByTypeAndName"; private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs"; private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME = "node.GetChildAssocRefsByQName"; + private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_TYPEQNAMES = "node.GetChildAssocRefsByTypeQNames"; + private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_TYPEQNAME_AND_QNAME = "node.GetChildAssocRefsByTypeQNameAndQName"; + private static final String QUERY_GET_PRIMARY_CHILD_ASSOCS = "node.GetPrimaryChildAssocs"; + private static final String QUERY_GET_PRIMARY_CHILD_ASSOCS_NOT_IN_SAME_STORE = "node.GetPrimaryChildAssocsNotInSameStore"; + private static final String QUERY_GET_NODES_WITH_CHILDREN_IN_DIFFERENT_STORES ="node.GetNodesWithChildrenInDifferentStores"; + private static final String QUERY_GET_NODES_WITH_ASPECT ="node.GetNodesWithAspect"; private static final String QUERY_GET_PARENT_ASSOCS = "node.GetParentAssocs"; private static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; private static final String QUERY_GET_NODE_ASSOCS_TO_AND_FROM = "node.GetNodeAssocsToAndFrom"; private static final String QUERY_GET_TARGET_ASSOCS = "node.GetTargetAssocs"; private static final String QUERY_GET_SOURCE_ASSOCS = "node.GetSourceAssocs"; + private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_STRING_AND_STORE = "node.GetNodesWithPropertyValuesByStringAndStore"; private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE = "node.GetNodesWithPropertyValuesByActualType"; private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress"; private static final String QUERY_GET_NODE_COUNT = "node.GetNodeCount"; private static final String QUERY_GET_NODE_COUNT_FOR_STORE = "node.GetNodeCountForStore"; - private static final String QUERY_GET_NODE_STATUSES_FOR_STORE = "node.GetNodeStatusesForStore"; - private static final String QUERY_GET_CHILD_ASSOCS_FOR_STORE = "node.GetChildAssocsForStore"; - private static final String QUERY_GET_NODES_EXCEPT_ROOT_FOR_STORE = "node.GetNodesExceptRootForStore"; - - private static final String QUERY_NODES_WITH_PROPERTY_STRING_VALUE_FOR_STORE = "node.GetNodesWithPropertyStringValueForStore"; - private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class); /** Log to trace parent association caching: classname + .ParentAssocsCache */ private static Log loggerParentAssocsCache = LogFactory.getLog(HibernateNodeDaoServiceImpl.class.getName() + ".ParentAssocsCache"); private QNameDAO qnameDAO; + private UsageDeltaDAO usageDeltaDAO; + private AclDaoComponent aclDaoComponent; /** A cache for more performant lookups of the parent associations */ private SimpleCache> parentAssocsCache; private boolean isDebugEnabled = logger.isDebugEnabled(); @@ -138,21 +154,6 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements /** used for debugging */ private Set changeTxnIdSet; - - private UsageDeltaDAO usageDeltaDao; - private TenantService tenantService; - - - public void setUsageDeltaDao(UsageDeltaDAO usageDeltaDao) - { - this.usageDeltaDao = usageDeltaDao; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - /** * @@ -205,6 +206,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements this.qnameDAO = qnameDAO; } + public void setUsageDeltaDAO(UsageDeltaDAO usageDeltaDAO) + { + this.usageDeltaDAO = usageDeltaDAO; + } + + public void setAclDaoComponent(AclDaoComponent aclDaoComponent) + { + this.aclDaoComponent = aclDaoComponent; + } + /** * Set the transaction-aware cache to store parent associations by child node id * @@ -273,6 +284,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SERVER_BY_IPADDRESS) .setString("ipAddress", ipAddress); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -386,43 +398,135 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements getSession().flush(); } + private Store getStore(StoreRef storeRef) + { + StoreKey storeKey = new StoreKey(storeRef); + Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); + // done + return store; + } + + private Store getStoreNotNull(StoreRef storeRef) + { + StoreKey storeKey = new StoreKey(storeRef); + Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); + if (store == null) + { + throw new InvalidStoreRefException(storeRef); + } + // done + return store; + } + + /** + * Fetch the node. If the ID is invalid, we assume that the state of the current session + * is invalid i.e. the data is stale + * + * @param nodeId the node's ID + * @return the node + * @throws AlfrescoRuntimeException if the ID doesn't refer to a node. + */ + private Node getNodeNotNull(Long nodeId) + { + Node node = (Node) getHibernateTemplate().get(NodeImpl.class, nodeId); + if (node == null) + { + throw new AlfrescoRuntimeException("Node ID " + nodeId + " is invalid"); + } + return node; + } + + /** + * Fetch the child assoc. If the ID is invalid, we assume that the state of the current session + * is invalid i.e. the data is stale + * + * @param childAssocId the assoc's ID + * @return the assoc + * @throws AlfrescoRuntimeException if the ID doesn't refer to an assoc. + */ + private ChildAssoc getChildAssocNotNull(Long childAssocId) + { + ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId); + if (assoc == null) + { + throw new AlfrescoRuntimeException("ChildAssoc ID " + childAssocId + " is invalid"); + } + return assoc; + } + +// /** +// * Fetch the child assoc. If the ID is invalid, we assume that the state of the current session +// * is invalid i.e. the data is stale +// * +// * @param nodeAssocId the assoc's ID +// * @return the assoc +// * @throws AlfrescoRuntimeException if the ID doesn't refer to an assoc. +// */ +// private NodeAssoc getNodeAssocNotNull(Long nodeAssocId) +// { +// NodeAssoc assoc = (NodeAssoc) getHibernateTemplate().get(NodeAssocImpl.class, nodeAssocId); +// if (assoc == null) +// { +// throw new AlfrescoRuntimeException("NodeAssoc ID " + nodeAssocId + " is invalid"); +// } +// return assoc; +// } +// /** * @see #QUERY_GET_ALL_STORES */ @SuppressWarnings("unchecked") - public List getStores() + public List getStoreRefs() { HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_ALL_STORES); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; - List queryResults = (List) getHibernateTemplate().execute(callback); + List stores = (List) getHibernateTemplate().execute(callback); + List storeRefs = new ArrayList(stores.size()); + for (Store store : stores) + { + storeRefs.add(store.getStoreRef()); + } // done - return queryResults; + return storeRefs; } + public Pair getRootNode(StoreRef storeRef) + { + Store store = getStore(storeRef); + if (store == null) + { + return null; + } + Node rootNode = store.getRootNode(); + if (rootNode == null) + { + throw new InvalidStoreRefException("Store does not have a root node: " + storeRef, storeRef); + } + // done + return new Pair(rootNode.getId(), rootNode.getNodeRef()); + } + /** * Ensures that the store protocol/identifier combination is unique */ - public Store createStore(String protocol, String identifier) + public Pair createStore(StoreRef storeRef) { - // ensure that the name isn't in use - Store store = getStore(protocol, identifier); + Store store = getStore(storeRef); if (store != null) { - throw new RuntimeException("A store already exists: \n" + - " protocol: " + protocol + "\n" + - " identifier: " + identifier + "\n" + - " store: " + store); + throw new StoreExistsException(storeRef); } store = new StoreImpl(); // set key - store.setKey(new StoreKey(protocol, identifier)); + store.setKey(new StoreKey(storeRef)); // persist so that it is present in the hibernate cache getHibernateTemplate().save(store); // create and assign a root node @@ -431,87 +535,38 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements GUID.generate(), ContentModel.TYPE_STOREROOT); store.setRootNode(rootNode); + // Add the root aspect + QNameEntity rootAspectQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.ASPECT_ROOT); + rootNode.getAspects().add(rootAspectQNameEntity.getId()); + + // Assign permissions to the root node + SimpleAccessControlListProperties properties = DMPermissionsDaoComponentImpl.getDefaultProperties(); + Long id = aclDaoComponent.createAccessControlList(properties); + DbAccessControlList acl = aclDaoComponent.getDbAccessControlList(id); + rootNode.setAccessControlList(acl); + // done - return store; + return new Pair(rootNode.getId(), rootNode.getNodeRef()); + } + + public NodeRef.Status getNodeRefStatus(NodeRef nodeRef) + { + NodeStatus nodeStatus = getNodeStatusOrNull(nodeRef); + if (nodeStatus == null) // node never existed + { + return null; + } + else + { + return new NodeRef.Status( + nodeStatus.getTransaction().getChangeTxnId(), + nodeStatus.isDeleted()); + } } - /** - * Delete store - this is a hard delete. - * - * @param protocol the store protocol - * @param identifier the store identifier - */ - @SuppressWarnings("unchecked") - public void deleteStore(final String protocol, final String identifier) + private NodeStatus getNodeStatusOrNull(NodeRef nodeRef) { - // ensure that the store exists - Store store = getStore(protocol, identifier); - if (store == null) - { - throw new RuntimeException("Store does not exist: \n" + - " protocol: " + protocol + "\n" + - " identifier: " + identifier); - } - - Node rootNode = store.getRootNode(); - - // TODO - convert queries to deletes ? - - // delete node status - Query query = getSession().getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_STATUSES_FOR_STORE); - - query.setParameter("protocol", protocol); - query.setParameter("identifier", identifier); - - List list = (List)query.list(); - getHibernateTemplate().deleteAll(list); - - // delete child assocs - query = getSession().getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS_FOR_STORE); - - query.setParameter("protocol", protocol); - query.setParameter("identifier", identifier); - - list = (List)query.list(); - getHibernateTemplate().deleteAll(list); - - // delete nodes (except root node) - query = getSession().getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_EXCEPT_ROOT_FOR_STORE); - - query.setParameter("nodeProtocol", protocol); - query.setParameter("nodeIdentifier", identifier); - query.setParameter("storeProtocol", protocol); - query.setParameter("storeIdentifier", identifier); - - list = (List)query.list(); - getHibernateTemplate().deleteAll(list); - - store.setRootNode(null); - getHibernateTemplate().update(store); - - // delete root node and store - getHibernateTemplate().delete(rootNode); - getHibernateTemplate().delete(store); - - // done - return; - } - - - public Store getStore(String protocol, String identifier) - { - StoreKey storeKey = new StoreKey(protocol, tenantService.getName(identifier)); - Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); - // done - return store; - } - - /** - * Fetch the node status, if it exists - */ - public NodeStatus getNodeStatus(NodeRef nodeRef, boolean update) - { - NodeKey nodeKey = new NodeKey(tenantService.getName(nodeRef)); + NodeKey nodeKey = new NodeKey(nodeRef); NodeStatus status = null; try { @@ -521,58 +576,70 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { if (e.contains(ObjectDeletedException.class)) { - // the object no longer exists - return null; + throw new StaleStateException("Node status was deleted: " + nodeKey); } throw e; } - // create if necessary - if (status == null && update) - { - status = new NodeStatusImpl(); - status.setKey(nodeKey); - status.setTransaction(getCurrentTransaction()); - getHibernateTemplate().save(status); - } - else if (status != null && update) - { - // update the transaction - status.setTransaction(getCurrentTransaction()); - } - // done return status; } - - public void recordChangeId(NodeRef nodeRef) + + private void recordNodeUpdate(Node node) { - NodeKey key = new NodeKey(tenantService.getName(nodeRef)); - - NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); + NodeRef nodeRef = node.getNodeRef(); + Transaction currentTxn = getCurrentTransaction(); + NodeStatus status = getNodeStatusOrNull(nodeRef); if (status == null) { - // the node never existed or the status was deleted - return; + NodeKey key = new NodeKey(nodeRef); + // We need to to create a status entry for it + status = new NodeStatusImpl(); + status.setKey(key); + status.setNode(node); + status.setTransaction(currentTxn); + getHibernateTemplate().save(status); } else { - // make sure that the status has the latest transaction attached - Transaction currentTxn = getCurrentTransaction(); + status.setNode(node); status.setTransaction(currentTxn); } } - public Node newNode(Store store, String uuid, QName nodeTypeQName) throws InvalidTypeException + private void recordNodeDelete(NodeRef nodeRef) + { + Transaction currentTxn = getCurrentTransaction(); + NodeStatus status = getNodeStatusOrNull(nodeRef); + if (status == null) + { + NodeKey key = new NodeKey(nodeRef); + // We need to to create a status entry for it + status = new NodeStatusImpl(); + status.setKey(key); + status.setNode(null); + status.setTransaction(currentTxn); + getHibernateTemplate().save(status); + } + else + { + status.setNode(null); + status.setTransaction(currentTxn); + } + } + + public Pair newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException + { + Store store = (Store) getHibernateTemplate().load(StoreImpl.class, new StoreKey(storeRef)); + Node newNode = newNode(store, uuid, nodeTypeQName); + return new Pair(newNode.getId(), newNode.getNodeRef()); + } + + private Node newNode(Store store, String uuid, QName nodeTypeQName) throws InvalidTypeException { NodeKey key = new NodeKey(store.getKey(), uuid); // create (or reuse) the mandatory node status NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); - if (status == null) - { - status = new NodeStatusImpl(); - status.setKey(key); - } - else + if (status != null) { // The node existed at some point. // Although unlikely, it is possible that the node was deleted in this transaction. @@ -582,11 +649,19 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements if (status.getTransaction().getChangeTxnId().equals(AlfrescoTransactionSupport.getTransactionId())) { // flush - getHibernateTemplate().flush(); + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) throws HibernateException, SQLException + { + DirtySessionMethodInterceptor.flushSession(session); + return null; + } + }; + getHibernateTemplate().execute(callback); } } - // Get the node's qname + // Get the qname for the node type QNameEntity nodeTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(nodeTypeQName); // build a concrete node based on a bootstrap type @@ -598,24 +673,40 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // persist the node getHibernateTemplate().save(node); - // set required status properties - status.setNode(node); - // assign a transaction - if (status.getTransaction() == null) - { - status.setTransaction(getCurrentTransaction()); - } - // persist the nodestatus - getHibernateTemplate().save(status); - + // Record change ID + recordNodeUpdate(node); + // done return node; } - public Node getNode(NodeRef nodeRef) + public Pair moveNodeToStore(Long nodeId, StoreRef storeRef) + { + Store store = getStoreNotNull(storeRef); + Node node = getNodeNotNull(nodeId); + // Only do anything if the store has changed + Store oldStore = node.getStore(); + if (oldStore.getKey().equals(store.getKey())) + { + // No change + return new Pair(node.getId(), node.getNodeRef()); + } + NodeRef oldNodeRef = node.getNodeRef(); + + // Set the store + node.setStore(store); + + // Record change ID + recordNodeDelete(oldNodeRef); + recordNodeUpdate(node); + + return new Pair(node.getId(), node.getNodeRef()); + } + + public Pair getNodePair(NodeRef nodeRef) { // get it via the node status - NodeStatus status = getNodeStatus(nodeRef, false); + NodeStatus status = getNodeStatusOrNull(nodeRef); if (status == null) { // no status implies no node @@ -625,74 +716,400 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { // a status may have a node Node node = status.getNode(); - return node; + // The node might be null (a deleted node) + if (node != null) + { + return new Pair(node.getId(), nodeRef); + } + else + { + return null; + } } } - /** - * Manually ensures that all cascading of associations is taken care of - */ - public void deleteNode(Node node, boolean cascade) + public Pair getNodePair(Long nodeId) { - Set deletedChildAssocIds = new HashSet(10); - deleteNodeInternal(node, cascade, deletedChildAssocIds); + Node node = (Node) getHibernateTemplate().get(NodeImpl.class, nodeId); + if (node == null) + { + return null; + } + else + { + return new Pair(nodeId, node.getNodeRef()); + } + } + + public QName getNodeType(Long nodeId) + { + Node node = getNodeNotNull(nodeId); + QNameEntity nodeTypeQNameEntity = node.getTypeQName(); + return nodeTypeQNameEntity.getQName(); + } + + public void setNodeStatus(Long nodeId) + { + Node node = getNodeNotNull(nodeId); + recordNodeUpdate(node); + } + + public Long getNodeAccessControlList(Long nodeId) + { + Node node = getNodeNotNull(nodeId); + DbAccessControlList acl = node.getAccessControlList(); + if (acl == null) + { + return null; + } + else + { + return acl.getId(); + } + } + + public void setNodeAccessControlList(Long nodeId, Long aclId) + { + Node node = getNodeNotNull(nodeId); + if (aclId == null) + { + node.setAccessControlList(null); + } + else + { + DbAccessControlList acl = (DbAccessControlList) getHibernateTemplate().get(DbAccessControlListImpl.class, aclId); + if (acl == null) + { + throw new IllegalArgumentException("ACL with ID " + aclId + " doesn't exist."); + } + node.setAccessControlList(acl); + } + } + + public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName) + { + Node node = getNodeNotNull(nodeId); + NodeRef nodeRefBefore = node.getNodeRef(); + if (storeRef != null && storeRef.equals(node.getStore().getStoreRef())) + { + Store store = getStoreNotNull(storeRef); + node.setStore(store); + } + if (uuid != null) + { + node.setUuid(uuid); + } + if (nodeTypeQName != null) + { + QNameEntity nodeTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(nodeTypeQName); + node.setTypeQName(nodeTypeQNameEntity); + } + NodeRef nodeRefAfter = node.getNodeRef(); + + // Record change ID + if (nodeRefBefore.equals(nodeRefAfter)) + { + recordNodeUpdate(node); + } + else + { + recordNodeDelete(nodeRefBefore); + recordNodeUpdate(node); + } + } + + public PropertyValue getNodeProperty(Long nodeId, QName propertyQName) + { + QNameEntity propertyQNameEntity = qnameDAO.getQNameEntity(propertyQName); + if (propertyQNameEntity == null) + { + return null; + } + + Node node = getNodeNotNull(nodeId); + Map nodeProperties = node.getProperties(); + return nodeProperties.get(propertyQNameEntity.getId()); + } + + public Map getNodeProperties(Long nodeId) + { + Node node = getNodeNotNull(nodeId); + Map nodeProperties = node.getProperties(); + + // Convert the QName IDs + Map converted = new HashMap(nodeProperties.size(), 1.0F); + for (Map.Entry entry : nodeProperties.entrySet()) + { + Long qnameEntityId = entry.getKey(); + QName qname = qnameDAO.getQName(qnameEntityId); + converted.put(qname, entry.getValue()); + } + + // Make immutable + return converted; + } + + public void addNodeProperty(Long nodeId, QName qname, PropertyValue propertyValue) + { + Node node = getNodeNotNull(nodeId); + QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(qname); + Map nodeProperties = node.getProperties(); + nodeProperties.put(qnameEntity.getId(), propertyValue); + + // Record change ID + recordNodeUpdate(node); + } + + public void addNodeProperties(Long nodeId, Map properties) + { + Node node = getNodeNotNull(nodeId); + Map nodeProperties = node.getProperties(); + + for (Map.Entry entry : properties.entrySet()) + { + QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(entry.getKey()); + nodeProperties.put(qnameEntity.getId(), entry.getValue()); + } + + // Record change ID + recordNodeUpdate(node); + } + + public void removeNodeProperties(Long nodeId, Set propertyQNames) + { + Node node = getNodeNotNull(nodeId); + Map nodeProperties = node.getProperties(); + + for (QName qname : propertyQNames) + { + QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(qname); + nodeProperties.remove(qnameEntity.getId()); + } + + // Record change ID + recordNodeUpdate(node); + } + + public void setNodeProperties(Long nodeId, Map properties) + { + Node node = getNodeNotNull(nodeId); + Map nodeProperties = node.getProperties(); + + nodeProperties.clear(); + + Set toRemove = new HashSet(nodeProperties.keySet()); + + for (Map.Entry entry : properties.entrySet()) + { + QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(entry.getKey()); + Long qnameEntityId = qnameEntity.getId(); + nodeProperties.put(qnameEntityId, entry.getValue()); + // It's live + toRemove.remove(qnameEntityId); + } + + // Remove all entries that weren't in the updated set + for (Long qnameEntityIdToRemove : toRemove) + { + nodeProperties.remove(qnameEntityIdToRemove); + } + + // Record change ID + recordNodeUpdate(node); + } + + public Set getNodeAspects(Long nodeId) + { + Node node = getNodeNotNull(nodeId); + Set nodeAspects = node.getAspects(); + + // Convert + Set nodeAspectQNames = new HashSet(nodeAspects.size(), 1.0F); + for (Long qnameEntityId : nodeAspects) + { + QName nodeAspectQName = qnameDAO.getQName(qnameEntityId); + nodeAspectQNames.add(nodeAspectQName); + } + + // Add sys:referenceable + nodeAspectQNames.add(ContentModel.ASPECT_REFERENCEABLE); + // Make immutable + return nodeAspectQNames; + } + + public void addNodeAspects(Long nodeId, Set aspectQNames) + { + Node node = getNodeNotNull(nodeId); + + // Remove sys:referenceable + if (aspectQNames.contains(ContentModel.ASPECT_REFERENCEABLE)) + { + aspectQNames = new HashSet(aspectQNames); + aspectQNames.remove(ContentModel.ASPECT_REFERENCEABLE); + } + + Set nodeAspects = node.getAspects(); + + for (QName aspectQName : aspectQNames) + { + QNameEntity aspectQNameEntity = qnameDAO.getOrCreateQNameEntity(aspectQName); + nodeAspects.add(aspectQNameEntity.getId()); + } + + // Record change ID + recordNodeUpdate(node); + } + + public void removeNodeAspects(Long nodeId, Set aspectQNames) + { + Node node = getNodeNotNull(nodeId); + + // Remove sys:referenceable + if (aspectQNames.contains(ContentModel.ASPECT_REFERENCEABLE)) + { + aspectQNames = new HashSet(aspectQNames); + aspectQNames.remove(ContentModel.ASPECT_REFERENCEABLE); + } + + Set nodeAspects = node.getAspects(); + + for (QName aspectQName : aspectQNames) + { + QNameEntity aspectQNameEntity = qnameDAO.getOrCreateQNameEntity(aspectQName); + nodeAspects.remove(aspectQNameEntity.getId()); + } + + // Record change ID + recordNodeUpdate(node); + } + + public boolean hasNodeAspect(Long nodeId, QName aspectQName) + { + Node node = getNodeNotNull(nodeId); + + // Shortcut sys:referenceable + if (aspectQName.equals(ContentModel.ASPECT_REFERENCEABLE)) + { + return true; + } + + QNameEntity aspectQNameEntity = qnameDAO.getQNameEntity(aspectQName); + if (aspectQNameEntity == null) + { + return false; + } + + Set nodeAspects = node.getAspects(); + return nodeAspects.contains(aspectQNameEntity.getId()); } /** - * - * @param node - * @param cascade true to cascade delete - * @param deletedChildAssocIds previously deleted child associations + * Manually ensures that all cascading of associations is taken care of + */ + public void deleteNode(Long nodeId) + { + Node node = getNodeNotNull(nodeId); + Set deletedChildAssocIds = new HashSet(10); + deleteNodeInternal(node, false, deletedChildAssocIds); + + // Record change ID + recordNodeDelete(node.getNodeRef()); + } + + private static final String QUERY_DELETE_PARENT_ASSOCS = "node.DeleteParentAssocs"; + private static final String QUERY_DELETE_CHILD_ASSOCS = "node.DeleteChildAssocs"; + private static final String QUERY_DELETE_NODE_ASSOCS = "node.DeleteNodeAssocs"; + + /** + * @param node the node to delete + * @param cascade true to cascade delete + * @param deletedChildAssocIds previously deleted child associations */ private void deleteNodeInternal(Node node, boolean cascade, Set deletedChildAssocIds) { + final Long nodeId = node.getId(); + // delete all parent assocs if (isDebugEnabled) { - logger.debug("Deleting parent assocs of node " + node.getId()); + logger.debug("Deleting child assocs of node " + nodeId); } - - Collection parentAssocs = getParentAssocsInternal(node); - parentAssocs = new ArrayList(parentAssocs); - for (ChildAssoc assoc : parentAssocs) + HibernateCallback getChildNodeIdsCallback = new HibernateCallback() { - deleteChildAssocInternal(assoc, false, deletedChildAssocIds); // we don't cascade upwards + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_NODE_IDS) + .setLong("parentId", nodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults childNodeIds = (ScrollableResults) getHibernateTemplate().execute(getChildNodeIdsCallback); + while (childNodeIds.next()) + { + Long childNodeId = childNodeIds.getLong(0); + parentAssocsCache.remove(childNodeId); + if (isDebugParentAssocCacheEnabled) + { + loggerParentAssocsCache.debug("\n" + + "Parent associations cache - Removing entry: \n" + + " Node: " + childNodeId); + } } + HibernateCallback deleteParentAssocsCallback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_DELETE_CHILD_ASSOCS) + .setLong("parentId", nodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.executeUpdate(); + } + }; + getHibernateTemplate().execute(deleteParentAssocsCallback); + // delete all child assocs if (isDebugEnabled) { - logger.debug("Deleting child assocs of node " + node.getId()); + logger.debug("Deleting parent assocs of node " + nodeId); } - Collection childAssocs = getChildAssocs(node); - childAssocs = new ArrayList(childAssocs); - for (ChildAssoc assoc : childAssocs) + HibernateCallback deleteChildAssocsCallback = new HibernateCallback() { - deleteChildAssocInternal(assoc, cascade, deletedChildAssocIds); // potentially cascade downwards - } + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_DELETE_PARENT_ASSOCS) + .setLong("childId", nodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.executeUpdate(); + } + }; + getHibernateTemplate().execute(deleteChildAssocsCallback); + // delete all node associations to and from if (isDebugEnabled) { logger.debug("Deleting source and target assocs of node " + node.getId()); } - List nodeAssocs = getNodeAssocsToAndFrom(node); - for (NodeAssoc assoc : nodeAssocs) + HibernateCallback deleteNodeAssocsCallback = new HibernateCallback() { - getHibernateTemplate().delete(assoc); - } + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_DELETE_NODE_ASSOCS) + .setLong("nodeId", nodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.executeUpdate(); + } + }; + getHibernateTemplate().execute(deleteNodeAssocsCallback); - if (isDebugEnabled) - { - logger.debug("Deleting usage deltas of node (if any)" + node.getId()); - } - usageDeltaDao.deleteDeltas(node.getId()); + // Delete deltas + usageDeltaDAO.deleteDeltas(nodeId); - // update the node status - NodeRef nodeRef = node.getNodeRef(); - NodeStatus nodeStatus = getNodeStatus(nodeRef, true); - nodeStatus.setNode(null); - nodeStatus.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); // finally delete the node - Long nodeId = node.getId(); getHibernateTemplate().delete(node); // Remove node from cache parentAssocsCache.remove(nodeId); @@ -728,34 +1145,36 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } } - public ChildAssoc newChildAssoc( - Node parentNode, - Node childNode, + public Pair newChildAssoc( + Long parentNodeId, + Long childNodeId, boolean isPrimary, QName assocTypeQName, - QName qname) + QName assocQName) { - // assign a random name to the node - String randomName = GUID.generate(); - - // Get the association type's qname + Node parentNode = (Node) getSession().get(NodeImpl.class, parentNodeId); + Node childNode = (Node) getSession().get(NodeImpl.class, childNodeId); QNameEntity assocTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(assocTypeQName); - // Get the qname components - NamespaceEntity assocNamespaceEntity = qnameDAO.getOrCreateNamespaceEntity(qname.getNamespaceURI()); + String assocQNameNamespace = assocQName.getNamespaceURI(); + String assocQNameLocalName = assocQName.getLocalName(); + NamespaceEntity assocQNameNamespaceEntity = qnameDAO.getOrCreateNamespaceEntity(assocQNameNamespace); + + // assign a random name to the node + String name = GUID.generate(); ChildAssoc assoc = new ChildAssocImpl(); assoc.setTypeQName(assocTypeQNameEntity); - assoc.setChildNodeName(randomName); + assoc.setChildNodeName(name); assoc.setChildNodeNameCrc(-1L); // random names compete only with each other - assoc.setQnameNamespace(assocNamespaceEntity); - assoc.setQnameLocalName(qname.getLocalName()); + assoc.setQnameNamespace(assocQNameNamespaceEntity); + assoc.setQnameLocalName(assocQNameLocalName); assoc.setIsPrimary(isPrimary); + assoc.setIndex(-1); // maintain inverse sets assoc.buildAssociation(parentNode, childNode); // persist it Long assocId = (Long) getHibernateTemplate().save(assoc); // Add it to the cache - Long childNodeId = childNode.getId(); Set oldParentAssocIds = parentAssocsCache.get(childNode.getId()); if (oldParentAssocIds != null) { @@ -771,23 +1190,66 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements " After: " + newParentAssocIds); } } + + // If this is a primary association then update the permissions + if (isPrimary) + { + DbAccessControlList inherited = parentNode.getAccessControlList(); + if (inherited == null) + { + // not fixde up yet or unset + } + else + { + // Get the parent's inherited ACLs + DbAccessControlList inheritedAcl = aclDaoComponent.getDbAccessControlList( + aclDaoComponent.getInheritedAccessControlList(inherited.getId())); + childNode.setAccessControlList(inheritedAcl); + } + } + + // Record change ID + recordNodeUpdate(childNode); + // done - return assoc; + return new Pair(assocId, assoc.getChildAssocRef()); } - public void setChildNameUnique(final ChildAssoc childAssoc, String childName) + public void setChildNameUnique(final Long childAssocId, String childName) { /* * Work out if there has been any change in the name */ + final ChildAssoc childAssoc = getChildAssocNotNull(childAssocId); + final Node parentNode = childAssoc.getParent(); + String childNameNew = null; long crc = -1; if (childName == null) { + // If the name assigned is null, then the name that will be assigned will + // be random. Ofcourse, if the association already has a random name assigned + // to it then there is no reason to assign a new one. The update of the child + // association is only required if the existing CRC value is not -1. + long existingCrc = childAssoc.getChildNodeNameCrc(); + if (existingCrc == -1L) + { + if (isDebugEnabled) + { + logger.debug( + "Child association name assignment is already random-based (non-clashing): \n" + + " Parent Node: " + parentNode.getId() + "\n" + + " Child Assoc: " + childAssoc.getId()); + } + // Shortcut here + return; + } + // random names compete only with each other, i.e. not at all childNameNew = GUID.generate(); - crc = -1; + // The CRC of -1 indicates that the cm:name equivalent is non-clashing, i.e. a GUID + crc = -1L; } else { @@ -798,52 +1260,42 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements final String childNameNewShort = getShortName(childNameNew); final long childNameNewCrc = crc; - - // check if the name has changed - if (childAssoc.getChildNodeNameCrc() == childNameNewCrc) - { - if (childAssoc.getChildNodeName().equals(childNameNewShort)) - { - // nothing changed - return; - } - } - - final Node parentNode = childAssoc.getParent(); - final QNameEntity assocTypeQName = childAssoc.getTypeQName(); - // We have the lock, so issue the query to check + HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_ID_TYPE_AND_BY_NAME) - .setParameter("parent", parentNode) - .setParameter("typeQName", assocTypeQName) - .setParameter("childNodeName", childNameNewShort) - .setLong("childNodeNameCrc", childNameNewCrc); - return query.uniqueResult(); + // Update the association + childAssoc.setChildNodeName(childNameNewShort); + childAssoc.setChildNodeNameCrc(childNameNewCrc); + // Flush again to force a DB constraint here + DirtySessionMethodInterceptor.flushSession(session, true); + // Done + return null; } }; - Long childAssocIdExisting = (Long) getHibernateTemplate().execute(callback); - if (childAssocIdExisting != null) + // Make sure that all changes to the session are persisted so that we know if any + // failures are from the constraint or not + DirtySessionMethodInterceptor.flushSession(getSession(false)); + try + { + getHibernateTemplate().execute(callback); + } + catch (Throwable e) { // There is already an entity if (isDebugEnabled) { logger.debug( "Duplicate child association detected: \n" + - " Existing Child Assoc: " + childAssocIdExisting + "\n" + - " Child Name: " + childName); + " Parent Node: " + parentNode.getId() + "\n" + + " Child Name: " + childName); } throw new DuplicateChildNodeNameException( parentNode.getNodeRef(), childAssoc.getTypeQName().getQName(), childName); } - // We got past that, so we can just update the entity - childAssoc.setChildNodeName(childNameNewShort); - childAssoc.setChildNodeNameCrc(childNameNewCrc); // Done if (isDebugEnabled) @@ -855,177 +1307,632 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } } - @SuppressWarnings("unchecked") - public Collection getPrimaryChildNodeStatuses(final Node parentNode) + public Pair updateChildAssoc( + Long childAssocId, + Long parentNodeId, + Long childNodeId, + QName assocTypeQName, + QName assocQName, + int index) { - HibernateCallback callback = new HibernateCallback() + final ChildAssoc childAssoc = getChildAssocNotNull(childAssocId); + final boolean isPrimary = childAssoc.getIsPrimary(); + final Node oldParentNode = childAssoc.getParent(); + final Node oldChildNode = childAssoc.getChild(); + final NodeRef oldChildNodeRef = childAssoc.getChild().getNodeRef(); + final Node newParentNode = getNodeNotNull(parentNodeId); + final Node newChildNode = getNodeNotNull(childNodeId); + final NodeRef newChildNodeRef = newChildNode.getNodeRef(); + QNameEntity assocTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(assocTypeQName); + String assocQNameNamespace = assocQName.getNamespaceURI(); + String assocQNameLocalName = assocQName.getLocalName(); + NamespaceEntity assocQNameNamespaceEntity = qnameDAO.getOrCreateNamespaceEntity(assocQNameNamespace); + + // Reset the cm:name duplicate handling. This has to be redone, if required. + String name = GUID.generate(); + childAssoc.setChildNodeName(name); + childAssoc.setChildNodeNameCrc(-1L); + + childAssoc.buildAssociation(newParentNode, newChildNode); + childAssoc.setTypeQName(assocTypeQNameEntity); + childAssoc.setQnameNamespace(assocQNameNamespaceEntity); + childAssoc.setQnameLocalName(assocQNameLocalName); + if (index >= 0) { - public Object doInHibernate(Session session) + childAssoc.setIndex(index); + } + + // Record change ID + if (oldChildNodeRef.equals(newChildNodeRef)) + { + recordNodeUpdate(newChildNode); + } + else + { + recordNodeDelete(oldChildNodeRef); + recordNodeUpdate(newChildNode); + } + + // Update the inherited associations if either the parent or child nodes have changed and + // the association is primary + if (isPrimary && ( + !oldParentNode.getId().equals(parentNodeId) || + !oldChildNode.getId().equals(childNodeId)) + ) + { + if (newChildNode.getAccessControlList() != null) { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_PRIMARY_CHILD_NODE_STATUSES) - .setParameter("parent", parentNode); - return query.list(); + Long targetAclId = newChildNode.getAccessControlList().getId(); + AccessControlListProperties aclProperties = aclDaoComponent.getAccessControlListProperties(targetAclId); + Boolean targetAclInherits = aclProperties.getInherits(); + if ((targetAclInherits != null) && (targetAclInherits.booleanValue())) + { + if (newParentNode.getAccessControlList() != null) + { + Long parentAclId = newParentNode.getAccessControlList().getId(); + Long inheritedAclId = aclDaoComponent.getInheritedAccessControlList(parentAclId); + if (aclProperties.getAclType() == ACLType.DEFINING) + { + aclDaoComponent.enableInheritance(targetAclId, parentAclId); + } + else if (aclProperties.getAclType() == ACLType.SHARED) + { + setFixedAcls(childNodeId, inheritedAclId, true); + } + } + else + { + if (aclProperties.getAclType() == ACLType.DEFINING) + { + // there is nothing to inherit from so clear out any inherited aces + aclDaoComponent.deleteInheritedAccessControlEntries(targetAclId); + } + else if (aclProperties.getAclType() == ACLType.SHARED) + { + // there is nothing to inherit + newChildNode.setAccessControlList(null); + } + + // throw new IllegalStateException("Share bug"); + } + } + } + else + { + if (newChildNode.getAccessControlList() != null) + { + Long parentAcl = newParentNode.getAccessControlList().getId(); + Long inheritedAcl = aclDaoComponent.getInheritedAccessControlList(parentAcl); + setFixedAcls(childNodeId, inheritedAcl, true); + } + } + } + + // Done + return new Pair(childAssocId, childAssoc.getChildAssocRef()); + } + + /** + * This code is here, and not in another DAO, in order to avoid unnecessary circular callbacks + * and cyclical dependencies. It would be nice if the ACL code could be separated (or combined) + * but the node tree walking code is best done right here. + * + * @param nodeRef + * @param mergeFromAclId + * @param set + */ + private void setFixedAcls( + final Long nodeId, + final Long mergeFromAclId, + final boolean set) + { + Node mergeFromNode = getNodeNotNull(nodeId); + + if (set) + { + DbAccessControlList mergeFromAcl = aclDaoComponent.getDbAccessControlList(mergeFromAclId); + mergeFromNode.setAccessControlList(mergeFromAcl); + } + + final List childNodeIds = new ArrayList(100); + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + // Ignore non-primary nodes + if (!childAssocPair.getSecond().isPrimary()) + { + return false; + } + childNodeIds.add(childNodePair.getFirst()); + return false; } }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; + // Get all child associations with the specific qualified name + getChildAssocs(nodeId, callback, false); + for (Long childNodeId : childNodeIds) + { + Node childNode = getNodeNotNull(childNodeId); + DbAccessControlList acl = childNode.getAccessControlList(); + + if (acl == null) + { + setFixedAcls(childNodeId, mergeFromAclId, true); + } + else if (acl.getAclType() == ACLType.LAYERED) + { + logger.error("LAYERED ACL present on ADM node: " + childNode); + continue; + } + else if (acl.getAclType() == ACLType.DEFINING) + { + @SuppressWarnings("unused") + List newChanges = aclDaoComponent.mergeInheritedAccessControlList(mergeFromAclId, acl.getId()); + } + else + { + setFixedAcls(childNodeId, mergeFromAclId, true); + } + } } @SuppressWarnings("unchecked") - public Collection getChildAssocs(final Node parentNode) + public void getChildAssocs(final Long parentNodeId, final ChildAssocRefQueryCallback resultsCallback, final boolean recurse) { - HibernateCallback callback = new HibernateCallback() + Node parentNode = getNodeNotNull(parentNodeId); + + ChildAssocRefQueryCallback queryCallback = resultsCallback; + final List childNodeIds = new ArrayList(100); + if (recurse) { - public Object doInHibernate(Session session) + // In order to recurse, without loading the DB with nested scrollable queries, we have to + // record the IDs of the children coming from the query. This is done by adding our own + // callback to the results iterator and passing values to the client's callback from there. + queryCallback = new ChildAssocRefQueryCallback() { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS) - .setParameter("parent", parentNode); - return query.list(); - } - }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; - } - - @SuppressWarnings("unchecked") - public Collection getChildAssocRefs(final Node parentNode) - { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + // Pass the values to the client code + boolean recurseLocal = resultsCallback.handle(childAssocPair, parentNodePair, childNodePair); + if (recurseLocal) + { + childNodeIds.add(childNodePair.getFirst()); + } + return false; + } + }; + } + HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS) - .setParameter("parent", parentNode); - return query.list(); + .setLong("parentId", parentNodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); } }; - List queryResults = (List) getHibernateTemplate().execute(callback); - Collection refs = convertToChildAssocRefs(parentNode, queryResults); - // done - return refs; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, queryCallback); + + // Now recurse, if required + if (recurse) + { + for (Long childNodeId : childNodeIds) + { + getChildAssocs(childNodeId, resultsCallback, recurse); + } + } + // Done } @SuppressWarnings("unchecked") - public Collection getChildAssocRefs(final Node parentNode, final QName assocQName) + public void getChildAssocs(final Long parentNodeId, final QName assocQName, ChildAssocRefQueryCallback resultsCallback) { + final NamespaceEntity assocQNameNamespaceEntity = qnameDAO.getNamespaceEntity(assocQName.getNamespaceURI()); + final String assocQNameLocalName = assocQName.getLocalName(); + if (assocQNameNamespaceEntity == null) + { + // There can't be any matches + return; + } + Node parentNode = getNodeNotNull(parentNodeId); HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - NamespaceEntity qnameNamespaceEntity = qnameDAO.getNamespaceEntity(assocQName.getNamespaceURI()); - String qnameLocalName = assocQName.getLocalName(); - if (qnameNamespaceEntity == null) - { - // There can be no match; - return Collections.emptyList(); - } + Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME) - .setParameter("parent", parentNode) - .setParameter("qnameNamespace", qnameNamespaceEntity) - .setParameter("qnameLocalName", qnameLocalName); - return query.list(); + .setLong("parentId", parentNodeId) + .setParameter("qnameNamespace", assocQNameNamespaceEntity) + .setString("qnameLocalName", assocQNameLocalName); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); } }; - List queryResults = (List) getHibernateTemplate().execute(callback); - Collection refs = convertToChildAssocRefs(parentNode, queryResults); - // done - return refs; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, resultsCallback); + // Done } - /** - *

-     * assocTypeQName, assocQName, assocIsPrimary, assocIndex, ?, childProtocol, childIdentifier, childUuid
-     * 
- */ - private Collection convertToChildAssocRefs(Node parentNode, List queryResults) + public void getChildAssocsByTypeQNames( + final Long parentNodeId, + final List assocTypeQNames, + ChildAssocRefQueryCallback resultsCallback) { - Collection refs = new ArrayList(queryResults.size()); - NodeRef parentNodeRef = tenantService.getBaseName(parentNode.getNodeRef()); - for (Object[] row : queryResults) + // Convert the type QNames to entities + final List assocTypeQNameIds = new ArrayList(assocTypeQNames.size()); + for (QName assocTypeQName : assocTypeQNames) { - String childProtocol = (String) row[6]; - String childIdentifier = (String) row[7]; - String childUuid = (String) row[8]; - NodeRef childNodeRef = tenantService.getBaseName(new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid)); - QNameEntity assocTypeQNameEntity = (QNameEntity) row[0]; - NamespaceEntity assocQNameNamespace = (NamespaceEntity) row[1]; - String assocQNameLocalName = (String) row[2]; - Boolean assocIsPrimary = (Boolean) row[3]; - Integer assocIndex = (Integer) row[4]; - ChildAssociationRef assocRef = new ChildAssociationRef( - assocTypeQNameEntity.getQName(), - parentNodeRef, - QName.createQName(assocQNameNamespace.getUri(), assocQNameLocalName), - childNodeRef, - assocIsPrimary.booleanValue(), - assocIndex.intValue()); - refs.add(assocRef); + QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); + if (assocTypeQNameEntity == null) + { + continue; + } + assocTypeQNameIds.add(assocTypeQNameEntity.getId()); } - return refs; - } - - public ChildAssoc getChildAssoc( - final Node parentNode, - final Node childNode, - final QName assocTypeQName, - final QName qname) - { + // Shortcut if there are no assoc types + if (assocTypeQNameIds.size() == 0) + { + return; + } + + Node parentNode = getNodeNotNull(parentNodeId); HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - QNameEntity typeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); - NamespaceEntity qnameNamespaceEntity = qnameDAO.getNamespaceEntity(qname.getNamespaceURI()); - String qnameLocalName = qname.getLocalName(); - if (typeQNameEntity == null || qnameNamespaceEntity == null) - { - // There can be no match; - return null; - } Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS_BY_ALL) - .setParameter("parent", parentNode) - .setParameter("child", childNode) - .setParameter("typeQName", typeQNameEntity) - .setParameter("qnameNamespace", qnameNamespaceEntity) - .setParameter("qnameLocalName", qnameLocalName); + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_TYPEQNAMES) + .setLong("parentId", parentNodeId) + .setParameterList("childAssocTypeQNameIds", assocTypeQNameIds); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, resultsCallback); + // Done + } + + public void getChildAssocsByTypeQNameAndQName( + final Long parentNodeId, + final QName assocTypeQName, + final QName assocQName, + ChildAssocRefQueryCallback resultsCallback) + { + Node parentNode = getNodeNotNull(parentNodeId); + + final QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); + final NamespaceEntity assocQNameNamespaceEntity = qnameDAO.getNamespaceEntity(assocQName.getNamespaceURI()); + final String assocQNameLocalName = assocQName.getLocalName(); + // Shortcut if possible + if (assocTypeQNameEntity == null || assocQNameNamespaceEntity == null) + { + return; + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_TYPEQNAME_AND_QNAME) + .setLong("parentId", parentNodeId) + .setParameter("typeQName", assocTypeQNameEntity) + .setParameter("qnameNamespace", assocQNameNamespaceEntity) + .setString("qnameLocalName", assocQNameLocalName); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, resultsCallback); + // Done + } + + public void getPrimaryChildAssocs(final Long parentNodeId, ChildAssocRefQueryCallback resultsCallback) + { + Node parentNode = getNodeNotNull(parentNodeId); + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_PRIMARY_CHILD_ASSOCS) + .setLong("parentId", parentNodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, resultsCallback); + // Done + } + + public void getPrimaryChildAssocsNotInSameStore(final Long parentNodeId, ChildAssocRefQueryCallback resultsCallback) + { + Node parentNode = getNodeNotNull(parentNodeId); + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_PRIMARY_CHILD_ASSOCS_NOT_IN_SAME_STORE) + .setLong("parentId", parentNodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, resultsCallback); + // Done + } + + public Pair getChildAssoc(final Long parentNodeId, final QName assocTypeQName, final String childName) + { + final QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); + // Shortcut + if (assocTypeQNameEntity == null) + { + return null; + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + String childNameLower = childName.toLowerCase(); + String childNameShort = getShortName(childNameLower); + long childNameLowerCrc = getCrc(childNameLower); + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME) + .setLong("parentId", parentNodeId) + .setParameter("typeQName", assocTypeQNameEntity) + .setParameter("childNodeName", childNameShort) + .setLong("childNodeNameCrc", childNameLowerCrc); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; ChildAssoc childAssoc = (ChildAssoc) getHibernateTemplate().execute(callback); - return childAssoc; + if (childAssoc == null) + { + return null; + } + else + { + return new Pair(childAssoc.getId(), childAssoc.getChildAssocRef()); + } + } + + public Pair getChildAssoc( + final Long parentNodeId, + final Long childNodeId, + final QName assocTypeQName, + final QName assocQName) + { + + final QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); + final NamespaceEntity assocQNameNamespaceEntity = qnameDAO.getNamespaceEntity(assocQName.getNamespaceURI()); + final String assocQNameLocalName = assocQName.getLocalName(); + // Shortcut if possible + if (assocTypeQNameEntity == null || assocQNameNamespaceEntity == null) + { + return null; + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS_BY_ALL) + .setLong("parentId", parentNodeId) + .setLong("childId", childNodeId) + .setParameter("typeQName", assocTypeQNameEntity) + .setParameter("qnameNamespace", assocQNameNamespaceEntity) + .setParameter("qnameLocalName", assocQNameLocalName); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.uniqueResult(); + } + }; + ChildAssoc childAssoc = (ChildAssoc) getHibernateTemplate().execute(callback); + if (childAssoc == null) + { + return null; + } + else + { + return new Pair(childAssoc.getId(), childAssoc.getChildAssocRef()); + } } - @SuppressWarnings("unchecked") - public boolean deleteChildAssoc( - final Node parentNode, - final Node childNode, - final QName assocTypeQName, - final QName qname) + /** + * Columns returned are: + *
+         0 assoc.id,
+         1 assoc.typeQName,
+         2 assoc.qnameNamespace,
+         3 assoc.qnameLocalName,
+         4 assoc.isPrimary,
+         5 assoc.index,
+         6 child.id,
+         7 child.store.key.protocol,
+         8 child.store.key.identifier,
+         9 child.uuid
+     * 
+ */ + private void convertToChildAssocRefs(Node parentNode, ScrollableResults results, ChildAssocRefQueryCallback resultsCallback) + { + Long parentNodeId = parentNode.getId(); + NodeRef parentNodeRef = parentNode.getNodeRef(); + Pair parentNodePair = new Pair(parentNodeId, parentNodeRef); + while (results.next()) + { + Object[] row = results.get(); + Long assocId = (Long) row[0]; + QNameEntity assocTypeQNameEntity = (QNameEntity) row[1]; + QName assocTypeQName = assocTypeQNameEntity.getQName(); + NamespaceEntity assocQNameNamespaceEntity = (NamespaceEntity) row[2]; + String assocQNameLocalName = (String) row[3]; + QName assocQName = QName.createQName(assocQNameNamespaceEntity.getUri(), assocQNameLocalName); + Boolean assocIsPrimary = (Boolean) row[4]; + Integer assocIndex = (Integer) row[5]; + Long childNodeId = (Long) row[6]; + String childProtocol = (String) row[7]; + String childIdentifier = (String) row[8]; + String childUuid = (String) row[9]; + NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); + ChildAssociationRef assocRef = new ChildAssociationRef( + assocTypeQName, + parentNodeRef, + assocQName, + childNodeRef, + assocIsPrimary.booleanValue(), + assocIndex.intValue()); + Pair assocPair = new Pair(assocId, assocRef); + Pair childNodePair = new Pair(childNodeId, childNodeRef); + // Call back + resultsCallback.handle(assocPair, parentNodePair, childNodePair); + } + } + + private Collection> convertToAssocRefs(List queryResults) + { + Collection> refs = new ArrayList>(queryResults.size()); + for (NodeAssoc nodeAssoc : queryResults) + { + Long nodeAssocId = nodeAssoc.getId(); + AssociationRef assocRef = nodeAssoc.getNodeAssocRef(); + refs.add(new Pair(nodeAssocId, assocRef)); + } + return refs; + } + + public void getNodesWithAspect( + final QName aspectQName, + final Long minNodeId, + final int count, + NodeRefQueryCallback resultsCallback) + { + final QNameEntity aspectQNameEntity = qnameDAO.getQNameEntity(aspectQName); + // Shortcut + if (aspectQNameEntity == null) + { + return; + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_ASPECT) + .setParameter("aspectQName", aspectQNameEntity) + .setLong("minNodeId", minNodeId) + .setMaxResults(count); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + processNodeResults(queryResults, resultsCallback); + // Done + } + + public void getNodesWithChildrenInDifferentStores(final Long minNodeId, final int count, NodeRefQueryCallback resultsCallback) { HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - QNameEntity typeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); - NamespaceEntity qnameNamespaceEntity = qnameDAO.getNamespaceEntity(qname.getNamespaceURI()); - String qnameLocalName = qname.getLocalName(); - if (typeQNameEntity == null || qnameNamespaceEntity == null) - { - // There can be no match; - return Collections.emptyList(); - } + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_CHILDREN_IN_DIFFERENT_STORES) + .setLong("minNodeId", minNodeId) + .setMaxResults(count); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + processNodeResults(queryResults, resultsCallback); + // Done + } + + /** + *
+            Long parentId = (Long) row[0];
+            String parentProtocol = (String) row[1];
+            String parentIdentifier = (String) row[2];
+            String parentUuid = (String) row[3];
+     * 
+ */ + private void processNodeResults(ScrollableResults queryResults, NodeRefQueryCallback resultsCallback) + { + while (queryResults.next()) + { + Object[] row = queryResults.get(); + Long parentId = (Long) row[0]; + String parentProtocol = (String) row[1]; + String parentIdentifier = (String) row[2]; + String parentUuid = (String) row[3]; + NodeRef parentNodeRef = new NodeRef(parentProtocol, parentIdentifier, parentUuid); + Pair parentNodePair = new Pair(parentId, parentNodeRef); + // Call back + boolean moreRequired = resultsCallback.handle(parentNodePair); + if (!moreRequired) + { + break; + } + } + } + + public void deleteChildAssoc(Long assocId) + { + Set deletedChildAssocIds = new HashSet(10); + ChildAssoc assoc = getChildAssocNotNull(assocId); + deleteChildAssocInternal(assoc, false, deletedChildAssocIds); + } + + @SuppressWarnings("unchecked") + public boolean deleteChildAssoc( + final Long parentNodeId, + final Long childNodeId, + final QName assocTypeQName, + final QName assocQName) + { + final QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); + final NamespaceEntity assocQNameNamespaceEntity = qnameDAO.getNamespaceEntity(assocQName.getNamespaceURI()); + final String assocQNameLocalName = assocQName.getLocalName(); + + // Shortcut + if (assocTypeQNameEntity == null || assocQNameNamespaceEntity == null) + { + return false; + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS_BY_ALL) - .setParameter("parent", parentNode) - .setParameter("child", childNode) - .setParameter("typeQName", typeQNameEntity) - .setParameter("qnameNamespace", qnameNamespaceEntity) - .setParameter("qnameLocalName", qnameLocalName); + .setLong("parentId", parentNodeId) + .setLong("childId", childNodeId) + .setParameter("typeQName", assocTypeQNameEntity) + .setParameter("qnameNamespace", assocQNameNamespaceEntity) + .setParameter("qnameLocalName", assocQNameLocalName); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; @@ -1033,7 +1940,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // Remove each child association with full cascade for (ChildAssoc assoc : childAssocs) { - deleteChildAssoc(assoc, true); + deleteChildAssocInternal(assoc, false, new HashSet(0)); } return (childAssocs.size() > 0); } @@ -1044,21 +1951,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { public Object doInHibernate(Session session) { - QNameEntity typeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); - if (typeQNameEntity == null) - { - // There can be no match; - return null; - } String childNameLower = childName.toLowerCase(); String childNameShort = getShortName(childNameLower); long childNameLowerCrc = getCrc(childNameLower); Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME) - .setParameter("parent", parentNode) - .setParameter("typeQName", typeQNameEntity) + .setLong("parentId", parentNode.getId()) + .setParameter("typeQName", assocTypeQName) .setParameter("childNodeName", childNameShort) .setLong("childNodeNameCrc", childNameLowerCrc); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1066,15 +1968,6 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return childAssoc; } - /** - * Public level entry-point. - */ - public void deleteChildAssoc(ChildAssoc assoc, boolean cascade) - { - Set deletedChildAssocIds = new HashSet(10); - deleteChildAssocInternal(assoc, cascade, deletedChildAssocIds); - } - /** * Cascade deletion of child associations, recording the IDs of deleted assocs. * @@ -1124,19 +2017,19 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements assoc.removeAssociation(); // remove instance getHibernateTemplate().delete(assoc); - // ensure that we don't attempt to delete it twice - deletedChildAssocIds.add(childAssocId); - - if (cascade && assoc.getIsPrimary()) // the assoc is primary - { - // delete the child node - deleteNodeInternal(childNode, cascade, deletedChildAssocIds); - /* - * The child node deletion will cascade delete all assocs to - * and from it, but we have safely removed this one, so no - * duplicate call will be received to do this - */ - } +// // ensure that we don't attempt to delete it twice +// deletedChildAssocIds.add(childAssocId); +// +// if (cascade && assoc.getIsPrimary()) // the assoc is primary +// { +// // delete the child node +// deleteNodeInternal(childNode, cascade, deletedChildAssocIds); +// /* +// * The child node deletion will cascade delete all assocs to +// * and from it, but we have safely removed this one, so no +// * duplicate call will be received to do this +// */ +// } } /** @@ -1144,9 +2037,8 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements * @return Returns the parent associations without any interpretation */ @SuppressWarnings("unchecked") - private Collection getParentAssocsInternal(final Node childNode) + private Collection getParentAssocsInternal(final Long childNodeId) { - final Long childNodeId = childNode.getId(); List parentAssocs = null; // First check the cache Set parentAssocIds = parentAssocsCache.get(childNodeId); @@ -1191,7 +2083,8 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_PARENT_ASSOCS) - .setParameter("child", childNode); + .setLong("childId", childNodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; @@ -1220,46 +2113,23 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements * * This includes a check to ensuret that only root nodes don't have primary parents */ - public Collection getParentAssocs(final Node childNode) + public Collection> getParentAssocs(final Long childNodeId) { - Collection parentAssocs = getParentAssocsInternal(childNode); - - if (parentAssocs.size() == 0) + Collection parentAssocs = getParentAssocsInternal(childNodeId); + Collection> ret = new ArrayList>(parentAssocs.size()); + + for (ChildAssoc childAssoc : parentAssocs) { - // the only condition where this is allowed is if the given node is a root node - Store store = childNode.getStore(); - Node rootNode = store.getRootNode(); - if (rootNode == null) - { - // a store without a root node - the entire store is hosed - throw new DataIntegrityViolationException("Store has no root node: \n" + - " store: " + store); - } - if (!rootNode.equals(childNode)) - { - parentAssocsCache.remove(childNode.getId()); - if (isDebugParentAssocCacheEnabled) - { - loggerParentAssocsCache.debug("\n" + - "Parent associations cache - Removing entry: \n" + - " Node: " + childNode.getId()); - } - parentAssocs = getParentAssocsInternal(childNode); - // Check if it has any parents yet. - if (parentAssocs.size() == 0) - { - // It wasn't the root node and definitely has no parent - throw new DataIntegrityViolationException( - "Non-root node has no primary parent: \n" + - " child: " + childNode); - } - } + Long childAssocId = childAssoc.getId(); + ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); + Pair childAssocPair = new Pair(childAssocId, childAssocRef); + ret.add(childAssocPair); } // Done - return parentAssocs; + return ret; } - private Set warnedDuplicateParents = new HashSet(3); + private Set warnedDuplicateParents = new HashSet(3); /** * {@inheritDoc} * @@ -1268,10 +2138,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements * the error. It is up to the administrator to fix the issue at the moment, but * the server will not stop working. */ - public ChildAssoc getPrimaryParentAssoc(Node childNode) + public Pair getPrimaryParentAssoc(Long childNodeId) { // get the assocs pointing to the node - Collection parentAssocs = getParentAssocs(childNode); + Collection parentAssocs = getParentAssocsInternal(childNodeId); ChildAssoc primaryAssoc = null; for (ChildAssoc assoc : parentAssocs) { @@ -1285,8 +2155,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // We have found one already. synchronized(warnedDuplicateParents) { - NodeRef childNodeRef = childNode.getNodeRef(); - boolean added = warnedDuplicateParents.add(childNodeRef); + boolean added = warnedDuplicateParents.add(childNodeId); if (added) { logger.warn( @@ -1301,21 +2170,48 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // we keep looping to hunt out data integrity issues } // done - return primaryAssoc; + if (primaryAssoc == null) + { + return null; + } + else + { + return new Pair(primaryAssoc.getId(), primaryAssoc.getChildAssocRef()); + } } - public NodeAssoc newNodeAssoc(Node sourceNode, Node targetNode, QName assocTypeQName) + public Pair newNodeAssoc(Long sourceNodeId, Long targetNodeId, final QName assocTypeQName) { - // Get the assoc type QNameEntity - QNameEntity assocTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(assocTypeQName); + final Node sourceNode = getNodeNotNull(sourceNodeId); + final Node targetNode = getNodeNotNull(targetNodeId); + + final QNameEntity assocTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(assocTypeQName); + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + // Force a flush here to ensure that the session is not dirty + DirtySessionMethodInterceptor.flushSession(session, true); + + NodeAssoc assoc = new NodeAssocImpl(); + assoc.setTypeQName(assocTypeQNameEntity); + assoc.buildAssociation(sourceNode, targetNode); + session.save(assoc); + + // Flush to catch integrity violations + DirtySessionMethodInterceptor.flushSession(session, true); + + return assoc; + } + }; - NodeAssoc assoc = new NodeAssocImpl(); - assoc.setTypeQName(assocTypeQNameEntity); - assoc.buildAssociation(sourceNode, targetNode); // persist try { - getHibernateTemplate().save(assoc); + NodeAssoc assoc = (NodeAssoc) getHibernateTemplate().execute(callback); + // done + return new Pair(assoc.getId(), assoc.getNodeAssocRef()); } catch (DataIntegrityViolationException e) { @@ -1325,12 +2221,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements assocTypeQName, e); } - // done - return assoc; } @SuppressWarnings("unchecked") - public List getNodeAssocsToAndFrom(final Node node) + public Collection> getNodeAssocsToAndFrom(final Long nodeId) { HibernateCallback callback = new HibernateCallback() { @@ -1338,43 +2232,48 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOCS_TO_AND_FROM) - .setParameter("node", node); + .setLong("nodeId", nodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; List results = (List) getHibernateTemplate().execute(callback); - return results; + Collection> ret = convertToAssocRefs(results); + return ret; } - public NodeAssoc getNodeAssoc( - final Node sourceNode, - final Node targetNode, + public Pair getNodeAssoc( + final Long sourceNodeId, + final Long targetNodeId, final QName assocTypeQName) { + final QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); + // Shortcut + if (assocTypeQNameEntity == null) + { + return null; + } + HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - QNameEntity assocTypeQNameEntity = qnameDAO.getQNameEntity(assocTypeQName); - if (assocTypeQNameEntity == null) - { - // There can be no match; - return null; - } Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC) - .setParameter("source", sourceNode) - .setParameter("target", targetNode) + .setLong("sourceId", sourceNodeId) + .setLong("targetId", targetNodeId) .setParameter("assocTypeQName", assocTypeQNameEntity); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; NodeAssoc result = (NodeAssoc) getHibernateTemplate().execute(callback); - return result; + Pair ret = new Pair(result.getId(), result.getNodeAssocRef()); + return ret; } @SuppressWarnings("unchecked") - public List getTargetNodeAssocs(final Node sourceNode) + public Collection> getTargetNodeAssocs(final Long sourceNodeId) { HibernateCallback callback = new HibernateCallback() { @@ -1382,16 +2281,18 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_TARGET_ASSOCS) - .setParameter("source", sourceNode); + .setLong("sourceId", sourceNodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; + List results = (List) getHibernateTemplate().execute(callback); + Collection> ret = convertToAssocRefs(results); + return ret; } @SuppressWarnings("unchecked") - public List getSourceNodeAssocs(final Node targetNode) + public Collection> getSourceNodeAssocs(final Long targetNodeId) { HibernateCallback callback = new HibernateCallback() { @@ -1399,32 +2300,82 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SOURCE_ASSOCS) - .setParameter("target", targetNode); + .setLong("targetId", targetNodeId); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; + List results = (List) getHibernateTemplate().execute(callback); + Collection> ret = convertToAssocRefs(results); + return ret; } - public void deleteNodeAssoc(NodeAssoc assoc) + public void deleteNodeAssoc(Long assocId) { - // Remove instance - getHibernateTemplate().delete(assoc); + NodeAssoc assoc = (NodeAssoc) getHibernateTemplate().get(NodeAssocImpl.class, assocId); + if (assoc != null) + { + getHibernateTemplate().delete(assoc); + } + } + + public void getPropertyValuesByPropertyAndValue( + final StoreRef storeRef, + final QName propertyQName, + final String value, + final NodePropertyHandler handler) + { + QNameEntity propQNameEntity = qnameDAO.getQNameEntity(propertyQName); + // Shortcut + if (propQNameEntity == null) + { + return; + } + final Long propQNameEntityId = propQNameEntity.getId(); + // Run the query + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_STRING_AND_STORE) + .setString("protocol", storeRef.getProtocol()) + .setString("identifier", storeRef.getIdentifier()) + .setLong("propQNameId", propQNameEntityId) + .setString("propStringValue", value) + ; + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults results = (ScrollableResults) getHibernateTemplate().execute(callback); + // Callback with the results + Session session = getSession(); + while (results.next()) + { + Node node = (Node) results.get(0); + NodeRef nodeRef = node.getNodeRef(); + QNameEntity nodeTypeQNameEntity = (QNameEntity) results.get(1); + QName nodeTypeQName = nodeTypeQNameEntity.getQName(); + handler.handle(nodeRef, nodeTypeQName, propertyQName, value); + // Flush if required + DirtySessionMethodInterceptor.flushSession(session); + } } public void getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition, NodePropertyHandler handler) { // get the in-database string representation of the actual type QName typeQName = actualDataTypeDefinition.getName(); - final int actualTypeOrdinalNumber = PropertyValue.convertToTypeOrdinal(typeQName); + final int actualTypeOrdinal = PropertyValue.convertToTypeOrdinal(typeQName); HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { Query query = session .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE) - .setInteger("actualType", actualTypeOrdinalNumber); + .setInteger("actualType", actualTypeOrdinal); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.scroll(ScrollMode.FORWARD_ONLY); } }; @@ -1437,8 +2388,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Node node = (Node) results.get()[0]; // loop through all the node properties Map properties = node.getProperties(); - for (PropertyValue propertyValue : properties.values()) + for (Map.Entry entry : properties.entrySet()) { + Long propertyQNameId = entry.getKey(); + PropertyValue propertyValue = entry.getValue(); // ignore nulls if (propertyValue == null) { @@ -1465,7 +2418,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } if (convertedValue != null) { - handler.handle(node, convertedValue); + NodeRef nodeRef = node.getNodeRef(); + QName nodeTypeQName = node.getTypeQName().getQName(); + QName propertyQName = qnameDAO.getQName(propertyQNameId); + handler.handle(nodeRef, nodeTypeQName, propertyQName, convertedValue); } } } @@ -1491,6 +2447,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_GET_NODE_COUNT); query.setMaxResults(1) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1510,9 +2467,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Query query = session.getNamedQuery(QUERY_GET_NODE_COUNT_FOR_STORE); query.setString("protocol", storeRef.getProtocol()) - .setString("identifier", tenantService.getName(storeRef.getIdentifier())) + .setString("identifier", storeRef.getIdentifier()) .setMaxResults(1) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1520,35 +2478,6 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // done return count.intValue(); } - - - @SuppressWarnings("unchecked") - public Collection getNodesWithPropertyStringValueForStore(final StoreRef storeRef, final QName propQName, final String propStringValue) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - QNameEntity propQNameEntity = qnameDAO.getQNameEntity(propQName); - if (propQNameEntity == null) - { - // There is nothing like this - return Collections.emptyList(); - } - Query query = session.getNamedQuery(QUERY_NODES_WITH_PROPERTY_STRING_VALUE_FOR_STORE); - query.setString("protocol", storeRef.getProtocol()) - .setString("identifier", tenantService.getName(storeRef.getIdentifier())) - .setParameter("propQNameId", propQNameEntity.getId()) - .setString("propStringValue", propStringValue) - .setReadOnly(true); - return query.list(); - } - }; - - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; - } - /* * Queries for transactions @@ -1572,6 +2501,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_GET_TXN_BY_ID); query.setLong("txnId", txnId) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1594,6 +2524,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_GET_SELECTED_TXNS_BY_COMMIT_TIME_ASC); query.setParameterList("includeTxnIds", includeTxnIds) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; @@ -1612,6 +2543,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE); query.setLong("txnId", txnId) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1630,6 +2562,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_GET_TXN_DELETE_COUNT_FOR_STORE); query.setLong("txnId", txnId) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1648,6 +2581,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_COUNT_TRANSACTIONS); query.setMaxResults(1) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.uniqueResult(); } }; @@ -1706,6 +2640,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements .setParameterList("excludeServerIds", excludeServerIds) .setMaxResults(count) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; @@ -1762,6 +2697,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements .setParameterList("excludeServerIds", excludeServerIds) .setMaxResults(count) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; @@ -1782,6 +2718,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements .setString("protocol", storeRef.getProtocol()) .setString("identifier", storeRef.getIdentifier()) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; @@ -1807,6 +2744,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Query query = session.getNamedQuery(QUERY_GET_TXN_CHANGES); query.setLong("txnId", txnId) .setReadOnly(true); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; diff --git a/source/java/org/alfresco/repo/node/index/NodeIndexer.java b/source/java/org/alfresco/repo/node/index/NodeIndexer.java index 1169f0d6f1..1a71fca9dd 100644 --- a/source/java/org/alfresco/repo/node/index/NodeIndexer.java +++ b/source/java/org/alfresco/repo/node/index/NodeIndexer.java @@ -25,33 +25,26 @@ package org.alfresco.repo.node.index; import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.search.Indexer; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** - * Handles the node policy callbacks to ensure that the node hierarchy is properly - * indexed. + * Passes index information to the index services. * * @author Derek Hulley */ +@SuppressWarnings("unused") public class NodeIndexer - implements NodeServicePolicies.OnCreateNodePolicy, - NodeServicePolicies.OnUpdateNodePolicy, - NodeServicePolicies.OnDeleteNodePolicy, - NodeServicePolicies.OnCreateChildAssociationPolicy, - NodeServicePolicies.OnDeleteChildAssociationPolicy { - /** the component to register the behaviour with */ - private PolicyComponent policyComponent; + private static Log logger = LogFactory.getLog(NodeIndexer.class); + /** the component to index the node hierarchy */ private Indexer indexer; - private TenantService tenantService; /** enabled or disabled */ private boolean enabled; @@ -60,14 +53,6 @@ public class NodeIndexer enabled = true; } - /** - * @param policyComponent used for registrations - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - /** * @param indexer the indexer that will be index */ @@ -76,80 +61,64 @@ public class NodeIndexer this.indexer = indexer; } - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - /* package */ void setEnabled(boolean enabled) { this.enabled = enabled; } /** - * Registers the policy behaviour methods + * @deprecated */ public void init() { - policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), - this, - new JavaBehaviour(this, "onCreateNode")); - policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), - this, - new JavaBehaviour(this, "onUpdateNode")); - policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), - this, - new JavaBehaviour(this, "onDeleteNode")); - policyComponent.bindAssociationBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateChildAssociation"), - this, - new JavaBehaviour(this, "onCreateChildAssociation")); - policyComponent.bindAssociationBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteChildAssociation"), - this, - new JavaBehaviour(this, "onDeleteChildAssociation")); + logger.warn("NodeIndexer.init() has been deprecated."); } - public void onCreateNode(ChildAssociationRef childAssocRef) + public void indexCreateNode(ChildAssociationRef childAssocRef) { if (enabled) { - indexer.createNode(tenantService.getName(childAssocRef)); + indexer.createNode(childAssocRef); } } - public void onUpdateNode(NodeRef nodeRef) + public void indexUpdateNode(NodeRef nodeRef) { if (enabled) { - indexer.updateNode(tenantService.getName(nodeRef)); + indexer.updateNode(nodeRef); } } - public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isArchivedNode) + public void indexDeleteNode(ChildAssociationRef childAssocRef) { if (enabled) { - indexer.deleteNode(tenantService.getName(childAssocRef)); + indexer.deleteNode(childAssocRef); } } - public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNew) - { - if (!isNew && enabled) - { - indexer.createChildRelationship(tenantService.getName(childAssocRef)); - } - } - - public void onDeleteChildAssociation(ChildAssociationRef childAssocRef) + public void indexCreateChildAssociation(ChildAssociationRef childAssocRef) { if (enabled) { - indexer.deleteChildRelationship(tenantService.getName(childAssocRef)); + indexer.createChildRelationship(childAssocRef); + } + } + + public void indexDeleteChildAssociation(ChildAssociationRef childAssocRef) + { + if (enabled) + { + indexer.deleteChildRelationship(childAssocRef); } } + + public void indexUpdateChildAssociation(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + if (enabled) + { + indexer.updateChildRelationship(oldChildAssocRef, newChildAssocRef); + } + } } diff --git a/source/java/org/alfresco/repo/node/index/NodeIndexerTest.java b/source/java/org/alfresco/repo/node/index/NodeIndexerTest.java deleted file mode 100644 index 1bb4166443..0000000000 --- a/source/java/org/alfresco/repo/node/index/NodeIndexerTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * 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 - * of the License, 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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.node.index; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -import org.alfresco.repo.node.BaseNodeServiceTest; -import org.alfresco.service.cmr.repository.MLText; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.perf.PerformanceMonitor; - -/** - * Checks that the indexing of the node hierarchy is working - * - * @see org.alfresco.repo.node.index.NodeIndexer - * - * @author Derek Hulley - */ -public class NodeIndexerTest extends BaseNodeServiceTest -{ - private SearchService searchService; - private static StoreRef localStoreRef; - private static NodeRef localRootNode; - - @Override - protected NodeService getNodeService() - { - return (NodeService) applicationContext.getBean("NodeService"); - } - - @Override - protected void onSetUpInTransaction() throws Exception - { - super.onSetUpInTransaction(); - searchService = (SearchService) applicationContext.getBean("searchService"); - - if (localStoreRef == null) - { - localStoreRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_Persisted" + System.currentTimeMillis()); - localRootNode = nodeService.getRootNode(localStoreRef); - } - } - - /** - * {@inheritDoc} - * - * This instance modifies the ML text value to be just the default locale string. - */ - protected void getExpectedPropertyValues(Map checkProperties) - { - MLText mlTextValue = (MLText) checkProperties.get(PROP_QNAME_ML_TEXT_VALUE); - String strValue = mlTextValue.getDefaultValue(); - checkProperties.put(PROP_QNAME_ML_TEXT_VALUE, strValue); - } - - public void testCommitQueryData() throws Exception - { - rootNodeRef = localRootNode; - buildNodeGraph(); - setComplete(); - } - - public void testQuery() throws Exception - { - rootNodeRef = localRootNode; - ResultSet results = searchService.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"" + BaseNodeServiceTest.TEST_PREFIX + ":root_p_n1\"", null, null); - assertEquals(1, results.length()); - results.close(); - } - - public void testLikeAndContains() throws Exception - { - rootNodeRef = localRootNode; - - DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(null); - namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); - namespacePrefixResolver.registerNamespace(NamespaceService.CONTENT_MODEL_PREFIX, NamespaceService.CONTENT_MODEL_1_0_URI); - namespacePrefixResolver.registerNamespace(BaseNodeServiceTest.TEST_PREFIX, BaseNodeServiceTest.NAMESPACE); - - PerformanceMonitor selectNodesPerf = new PerformanceMonitor(getClass().getSimpleName(), "selectNodes"); - PerformanceMonitor selectPropertiesPerf = new PerformanceMonitor(getClass().getSimpleName(), "selectProperties"); - - List answer; - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'm_nkey')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'm%key')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'monk__')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'monk%')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'monk\\%')]", null, namespacePrefixResolver, false); - assertEquals(0, answer.size()); - selectNodesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[contains('monkey')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectPropertiesPerf.start(); - List result = searchService.selectProperties(rootNodeRef, "//@*[contains('monkey')]", null, namespacePrefixResolver, false); - assertEquals(2, result.size()); - selectPropertiesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[contains('mon?ey')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectPropertiesPerf.start(); - result = searchService.selectProperties(rootNodeRef, "//@*[contains('mon?ey')]", null, namespacePrefixResolver, false); - assertEquals(2, result.size()); - selectPropertiesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[contains('m*y')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectPropertiesPerf.start(); - result = searchService.selectProperties(rootNodeRef, "//@*[contains('mon*')]", null, namespacePrefixResolver, false); - assertEquals(2, result.size()); - selectPropertiesPerf.stop(); - - selectNodesPerf.start(); - answer = searchService.selectNodes(rootNodeRef, "//*[contains('*nkey')]", null, namespacePrefixResolver, false); - assertEquals(1, answer.size()); - selectNodesPerf.stop(); - - selectPropertiesPerf.start(); - result = searchService.selectProperties(rootNodeRef, "//@*[contains('?onkey')]", null, namespacePrefixResolver, false); - assertEquals(2, result.size()); - selectPropertiesPerf.stop(); - } -} diff --git a/source/java/org/alfresco/repo/search/IndexerComponent.java b/source/java/org/alfresco/repo/search/IndexerComponent.java index 645b20cab6..3655316757 100644 --- a/source/java/org/alfresco/repo/search/IndexerComponent.java +++ b/source/java/org/alfresco/repo/search/IndexerComponent.java @@ -24,8 +24,11 @@ */ package org.alfresco.repo.search; +import org.alfresco.repo.service.StoreRedirectorProxyFactory; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.context.ApplicationEvent; /** * Component API for indexing. Delegates to the real index retrieved from the @@ -38,10 +41,27 @@ import org.alfresco.service.cmr.repository.NodeRef; * @author andyh * */ -public class IndexerComponent implements Indexer +public class IndexerComponent extends AbstractLifecycleBean implements Indexer { + private StoreRedirectorProxyFactory storeRedirectorProxyFactory; private IndexerAndSearcher indexerAndSearcherFactory; + public void setStoreRedirectorProxyFactory(StoreRedirectorProxyFactory storeRedirectorProxyFactory) + { + this.storeRedirectorProxyFactory = storeRedirectorProxyFactory; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + this.indexerAndSearcherFactory = storeRedirectorProxyFactory.getObject(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + public void setIndexerAndSearcherFactory(IndexerAndSearcher indexerAndSearcherFactory) { this.indexerAndSearcherFactory = indexerAndSearcherFactory; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java index 5b9e303b9a..26b76fbbcd 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -25,7 +25,6 @@ package org.alfresco.repo.security.permissions.impl; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -370,7 +369,7 @@ public class PermissionServiceTest extends AbstractPermissionTest private void printPermissions(NodeRef nodeRef, String path) { - Long id = nodeDaoService.getNode(nodeRef).getAccessControlList().getId(); + Long id = nodeDaoService.getNodePair(nodeRef).getFirst(); System.out.println(path + " has "+id); for(AccessControlEntry entry : aclDaoComponent.getAccessControlList(id).getEntries()) { @@ -1094,6 +1093,7 @@ public class PermissionServiceTest extends AbstractPermissionTest NodeRef n7 = nodeService.createNode(n6, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}seven"), ContentModel.TYPE_FOLDER).getChildRef(); NodeRef n8 = nodeService.createNode(n7, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}eight"), ContentModel.TYPE_FOLDER).getChildRef(); NodeRef n9 = nodeService.createNode(n8, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}nine"), ContentModel.TYPE_FOLDER).getChildRef(); + @SuppressWarnings("unused") NodeRef n10 = nodeService.createNode(n9, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}ten"), ContentModel.TYPE_FOLDER).getChildRef(); permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionsDaoComponent.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionsDaoComponent.java index 3822c062b8..d0da2af1df 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionsDaoComponent.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionsDaoComponent.java @@ -45,7 +45,6 @@ public interface PermissionsDaoComponent /** * Get the permissions that have been set on a given node. * - * @param nodeRef * @return the node permission entry */ public NodePermissionEntry getPermissions(NodeRef nodeRef); @@ -60,8 +59,6 @@ public interface PermissionsDaoComponent /** * Remove all permissions for the specified authority - * - * @param authority */ public void deletePermissions(String authority); @@ -91,40 +88,27 @@ public interface PermissionsDaoComponent /** * Set a permission on a node. If the node has no permissions set then a default node permission (allowing * inheritance) will be created to contain the permission entry. - * - * @param nodeRef - * @param authority - * @param perm - * @param allow */ public void setPermission(NodeRef nodeRef, String authority, PermissionReference perm, boolean allow); /** * Create a persisted permission entry given and other representation of a permission entry. - * - * @param permissionEntry */ public void setPermission(PermissionEntry permissionEntry); /** * Create a persisted node permission entry given a template object from which to copy. - * - * @param nodePermissionEntry */ public void setPermission(NodePermissionEntry nodePermissionEntry); /** * Set the inheritance behaviour for permissions on a given node. - * - * @param nodeRef - * @param inheritParentPermissions */ public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions); /** * Return the inheritance behaviour for permissions on a given node. * - * @param nodeRef * @return inheritParentPermissions */ public boolean getInheritParentPermissions(NodeRef nodeRef); @@ -132,7 +116,6 @@ public interface PermissionsDaoComponent /** * Get all the permissions set for the given authority * - * @param authority * @return - the permissions set on all nodes for the given authority. */ public Map> getAllSetPermissions(String authority); @@ -152,42 +135,28 @@ public interface PermissionsDaoComponent /** * Delete entries from a permission mask on a store by authority - * - * @param storeRef - * @param authority */ public void deletePermissions(StoreRef storeRef, String authority); /** * Remove part of a permission mask from a store - * - * @param storeRef - * @param authority - * @param perm */ public void deletePermission(StoreRef storeRef, String authority, PermissionReference perm); /** * Remove all permission masks from a store * - * @param storeRef */ public void deletePermissions(StoreRef storeRef); /** * Set part of a permission mask on a store. - * - * @param storeRef - * @param authority - * @param permission - * @param allow */ public void setPermission(StoreRef storeRef, String authority, PermissionReference permission, boolean allow); /** * Get permission masks set on a store * - * @param storeRef * @return the node permission entry */ public NodePermissionEntry getPermissions(StoreRef storeRef); @@ -195,9 +164,7 @@ public interface PermissionsDaoComponent /** * Get the properties for the access control list * - * @param nodeRef * @return the properties for the access control list */ public AccessControlListProperties getAccessControlListProperties(NodeRef nodeRef); - } diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 68d4ca208d..a98be63d62 100755 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -56,6 +56,7 @@ import org.alfresco.repo.workflow.WorkflowDeployer; import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.view.RepositoryExporterService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -79,7 +80,7 @@ public class MultiTAdminServiceImpl extends AbstractLifecycleBean implements Ten private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); // Dependencies - private DbNodeServiceImpl nodeService; // TODO - replace with NodeService, when deleteStore is exposed via public API + private NodeService nodeService; private DictionaryComponent dictionaryComponent; private RepoAdminService repoAdminService; private AuthenticationComponent authenticationComponent; diff --git a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java index bfba82d915..0fc1d2bfe9 100644 --- a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java +++ b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java @@ -35,6 +35,7 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.cache.TransactionalCache; +import org.alfresco.repo.domain.hibernate.DirtySessionMethodInterceptor; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.search.impl.lucene.LuceneIndexerAndSearcher; import org.alfresco.util.GUID; @@ -174,6 +175,8 @@ public abstract class AlfrescoTransactionSupport * Are there any pending changes which must be synchronized with the store? * * @return true => changes are pending + * + * @deprecated To be replaced by {@link DirtySessionMethodInterceptor} */ public static boolean isDirty() { diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java index 6b6b0ce1e8..2c05f9d42b 100644 --- a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java +++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java @@ -37,7 +37,6 @@ import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -55,7 +54,6 @@ import org.apache.commons.logging.LogFactory; * */ public class ContentUsageImpl implements ContentUsageService, - NodeServicePolicies.OnCreateNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy, NodeServicePolicies.BeforeDeleteNodePolicy { @@ -134,12 +132,6 @@ public class ContentUsageImpl implements ContentUsageService, { if (enabled) { - // Register interest in the onCreateNode policy - for content - policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), - ContentModel.TYPE_CONTENT, - new JavaBehaviour(this, "onCreateNode")); - // Register interest in the onUpdateProperties policy - for content policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), @@ -152,12 +144,6 @@ public class ContentUsageImpl implements ContentUsageService, ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "beforeDeleteNode")); - // Register interest in the onUpdateNode policy - for content - policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), - ContentModel.TYPE_CONTENT, - new JavaBehaviour(this, "onUpdateNode")); - // Register interest in the beforeDeleteNode policy - for folder policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), @@ -208,89 +194,6 @@ public class ContentUsageImpl implements ContentUsageService, updatedNodes.add(tenantService.getName(nodeRef)); } - @SuppressWarnings("unchecked") - private boolean alreadyUpdated(NodeRef nodeRef) - { - Set updatedNodes = (Set)AlfrescoTransactionSupport.getResource(KEY_UPDATED_NODES); - if (updatedNodes != null) - { - for (NodeRef updatedNodeRef : updatedNodes) - { - if (updatedNodeRef.equals(nodeRef)) - { - if (logger.isDebugEnabled()) logger.debug("alreadyUpdated: nodeRef="+nodeRef); - return true; - } - } - } - return false; - } - - /** - * Called when a new node has been created. - * - * @param childAssocRef the created child association reference - */ - public void onCreateNode(ChildAssociationRef childAssocRef) - { - NodeRef nodeRef = childAssocRef.getChildRef(); - if (stores.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString()) && (! alreadyUpdated(nodeRef))) - { - // Get content size - - // TODO use data dictionary to get content property - ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - Long contentSize = (contentData == null ? null : contentData.getSize()); - - // Get owner/creator - String owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER); - if (owner == null) - { - owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR); - } - - if (contentSize != null && contentSize != 0 && owner != null) - { - // new node with non-empty content size - if (logger.isDebugEnabled()) logger.debug("onCreateNode: contentSize="+contentSize+", nodeRef="+nodeRef+", ownerAfter="+owner); - incrementUserUsage(owner, contentSize, nodeRef); - } - } - } - - public void onUpdateNode(NodeRef nodeRef) - { - NodeRef folderNodeRef = (NodeRef)AlfrescoTransactionSupport.getResource(KEY_DELETED_FOLDER); - if (folderNodeRef != null) - { - // TODO partial fix for ETWONINE-43 - // there must be a better way, eg. refine move policies ... currently assumes updated node exists in deleted folder hierarchy and has been moved from workspace to the archive - if (nodeRef.toString().contains("archive://") && (! alreadyDeleted(nodeRef))) - { - ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - if (contentData != null) - { - long contentSize = contentData.getSize(); - - // Get owner/creator - String owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER); - if (owner == null) - { - owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR); - } - - if (contentSize != 0 && owner != null) - { - // decrement usage if node is being deleted - if (logger.isDebugEnabled()) logger.debug("onUpdateNode: folderRef="+folderNodeRef+", archivedContentNodeRef="+nodeRef+", owner="+owner+", contentSize="+contentSize); - decrementUserUsage(owner, contentSize, nodeRef); - recordDelete(nodeRef); - } - } - } - } - } - /** * Called after a node's properties have been changed. * diff --git a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java index 3614ffe776..516f3b01dd 100644 --- a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java +++ b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java @@ -26,6 +26,7 @@ package org.alfresco.repo.usage; import java.util.Set; +import org.alfresco.repo.domain.UsageDeltaDAO; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.usage.UsageService; @@ -37,30 +38,30 @@ import org.alfresco.service.cmr.usage.UsageService; */ public class UsageServiceImpl implements UsageService { - private UsageDeltaDAO usageDeltaDao; + private UsageDeltaDAO usageDeltaDAO; - public void setUsageDeltaDao(UsageDeltaDAO usageDeltaDao) + public void setUsageDeltaDAO(UsageDeltaDAO usageDeltaDAO) { - this.usageDeltaDao = usageDeltaDao; + this.usageDeltaDAO = usageDeltaDAO; } public void insertDelta(NodeRef usageNodeRef, long deltaSize) { - usageDeltaDao.insertDelta(usageNodeRef, deltaSize); + usageDeltaDAO.insertDelta(usageNodeRef, deltaSize); } public long getTotalDeltaSize(NodeRef usageNodeRef) { - return usageDeltaDao.getTotalDeltaSize(usageNodeRef); + return usageDeltaDAO.getTotalDeltaSize(usageNodeRef); } public Set getUsageDeltaNodes() { - return usageDeltaDao.getUsageDeltaNodes(); + return usageDeltaDAO.getUsageDeltaNodes(); } public int deleteDeltas(NodeRef usageNodeRef) { - return usageDeltaDao.deleteDeltas(usageNodeRef); + return usageDeltaDAO.deleteDeltas(usageNodeRef); } } diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java index 7bb142a42b..ca309b8cf6 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java @@ -24,17 +24,14 @@ */ package org.alfresco.repo.usage; -import java.util.Collection; +import java.io.Serializable; import java.util.HashSet; import java.util.List; import java.util.Set; import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.QNameDAO; -import org.alfresco.repo.domain.QNameEntity; import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.node.db.NodeDaoService.NodePropertyHandler; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; @@ -46,9 +43,11 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.usage.UsageService; import org.alfresco.service.namespace.QName; +import org.apache.commons.lang.mutable.MutableLong; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -64,29 +63,17 @@ public class UserUsageTrackingComponent private static boolean busy = false; - private NodeDaoService nodeDaoService; - private QNameDAO qnameDAO; private TransactionServiceImpl transactionService; private ContentUsageImpl contentUsageImpl; private PersonService personService; - private NodeService nodeService; + private NodeService nodeService; + private NodeDaoService nodeDaoService; private UsageService usageService; private TenantDeployerService tenantDeployerService; private boolean enabled = true; - - public void setNodeDaoService(NodeDaoService nodeDaoService) - { - this.nodeDaoService = nodeDaoService; - } - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - public void setTransactionService(TransactionServiceImpl transactionService) { this.transactionService = transactionService; @@ -107,6 +94,11 @@ public class UserUsageTrackingComponent this.nodeService = nodeService; } + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + public void setUsageService(UsageService usageService) { this.usageService = usageService; @@ -273,9 +265,9 @@ public class UserUsageTrackingComponent * have not had their initial usage calculated. In a future release, could also be called explicitly by * a SysAdmin, eg. via a JMX operation. * - * @param userName + * @param username the username to for which calcualte usages */ - public void recalculateUsage(final String userName) + public void recalculateUsage(final String username) { final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); @@ -284,75 +276,80 @@ public class UserUsageTrackingComponent { public Long execute() throws Throwable { - QNameEntity ownerQnameEntity = qnameDAO.getQNameEntity(ContentModel.PROP_OWNER); - QNameEntity contentQnameEntity = qnameDAO.getQNameEntity(ContentModel.PROP_CONTENT); - +// QNameEntity ownerQnameEntity = qnameDAO.getQNameEntity(ContentModel.PROP_OWNER); +// QNameEntity contentQnameEntity = qnameDAO.getQNameEntity(ContentModel.PROP_CONTENT); +// List stores = contentUsageImpl.getStores(); - long totalUsage = 0; + final MutableLong totalUsage = new MutableLong(0L); - if (contentQnameEntity != null) + for (String store : stores) { - for (String store : stores) - { - StoreRef storeRef = new StoreRef(store); - - // get nodes for which user is owner - Collection ownerNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_OWNER, userName); - - if (logger.isDebugEnabled()) - { - logger.debug("Recalc usage ("+ userName+") store="+storeRef+", ownerNodeCount="+ownerNodes.size()); - } - - for (Node ownerNode : ownerNodes) - { - if (ownerNode.getTypeQName().equals(ContentModel.TYPE_CONTENT)) - { - PropertyValue content = ownerNode.getProperties().get(contentQnameEntity.getId()); - if (content != null) - { - ContentData contentData = ContentData.createContentProperty(content.getStringValue()); - totalUsage = totalUsage + contentData.getSize(); - } - } - } - - // get nodes for which user is creator, and then filter out those that have an owner - Collection creatorNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_CREATOR, userName); - - if (logger.isDebugEnabled()) - { - logger.debug("Recalc usage ("+ userName+") store="+storeRef+", creatorNodeCount="+creatorNodes.size()); - } - - for (Node creatorNode : creatorNodes) - { - // note: it is possible for "owner" qname to be null (eg. ownership never taken) - if (creatorNode.getTypeQName().toString().equals(ContentModel.TYPE_CONTENT.toString()) && - ((ownerQnameEntity != null) && creatorNode.getProperties().get(ownerQnameEntity.getId()) == null)) - { - PropertyValue content = creatorNode.getProperties().get(contentQnameEntity.getId()); - if (content != null) - { - ContentData contentData = ContentData.createContentProperty(content.getStringValue()); - totalUsage = totalUsage + contentData.getSize(); - } - } - } - } + final StoreRef storeRef = new StoreRef(store); - if (logger.isDebugEnabled()) + if (logger.isDebugEnabled()) { - long quotaSize = contentUsageImpl.getUserQuota(userName); - logger.debug("Recalc usage ("+ userName+") totalUsage="+totalUsage+", quota="+quotaSize); + logger.debug("Recalc usage (" + username + ") store=" + storeRef); } + + NodePropertyHandler propOwnerHandler = new NodePropertyHandler() + { + public void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value) + { + if (nodeTypeQName.equals(ContentModel.TYPE_CONTENT)) + { + // It is not content + } + ContentData contentData = DefaultTypeConverter.INSTANCE.convert( + ContentData.class, + nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT)); + if (contentData != null) + { + long currentTotalUsage = totalUsage.longValue(); + totalUsage.setValue(currentTotalUsage + contentData.getSize()); + } + } + }; + nodeDaoService.getPropertyValuesByPropertyAndValue(storeRef, ContentModel.PROP_OWNER, username, propOwnerHandler); + + if (logger.isDebugEnabled()) + { + logger.debug("Recalc usage (" + username + ") store=" + storeRef); + } + + NodePropertyHandler propCreatorHandler = new NodePropertyHandler() + { + public void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value) + { + if (!nodeTypeQName.equals(ContentModel.TYPE_CONTENT)) + { + // It is not content + return; + } + if (nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER) != null) + { + // There is an owner property so we will have process this already + return; + } + ContentData contentData = DefaultTypeConverter.INSTANCE.convert( + ContentData.class, + nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT)); + if (contentData != null) + { + long currentTotalUsage = totalUsage.longValue(); + totalUsage.setValue(currentTotalUsage + contentData.getSize()); + } + } + }; + nodeDaoService.getPropertyValuesByPropertyAndValue(storeRef, ContentModel.PROP_OWNER, username, propCreatorHandler); } - else + + if (logger.isDebugEnabled()) { - logger.error("Failed to re-calculate usages - cannot find QName: " + ContentModel.PROP_CONTENT.toString()); + long quotaSize = contentUsageImpl.getUserQuota(username); + logger.debug("Recalc usage ("+ username+") totalUsage="+totalUsage+", quota="+quotaSize); } - return totalUsage; + return totalUsage.longValue(); } }; // execute in READ-ONLY txn @@ -363,7 +360,7 @@ public class UserUsageTrackingComponent { public Object execute() throws Throwable { - NodeRef personNodeRef = personService.getPerson(userName); + NodeRef personNodeRef = personService.getPerson(username); contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); usageService.deleteDeltas(personNodeRef); return null; diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index 4b76d88688..b2bdd56da1 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -126,6 +126,14 @@ public class NodeServiceImpl implements NodeService, VersionModel this.dicitionaryService = dictionaryService; } + /** + * No-op + */ + public List cleanup() + { + return Collections.emptyList(); + } + /** * Delegates to the NodeService used as the version store implementation */ @@ -141,6 +149,15 @@ public class NodeServiceImpl implements NodeService, VersionModel { return dbNodeService.createStore(protocol, identifier); } + + /** + * @throws UnsupportedOperationException always + */ + public void deleteStore(StoreRef storeRef) + { + // This operation is not supported for a version store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } /** * Delegates to the NodeService used as the version store implementation diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 76aaa0c51c..6f8ffb77b2 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -51,6 +51,7 @@ import org.alfresco.service.cmr.version.VersionServiceException; import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.QName; import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; @@ -690,7 +691,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest NodeRef root = this.dbNodeService.getRootNode(spacesStoreRef); HashMap props2 = new HashMap(); - props2.put(ContentModel.PROP_NAME, "test.txt"); + props2.put(ContentModel.PROP_NAME, "test-" + GUID.generate() + ".txt"); final NodeRef nodeRef = this.dbNodeService.createNode( root, ContentModel.ASSOC_CHILDREN, diff --git a/source/java/org/alfresco/service/cmr/repository/CyclicChildRelationshipException.java b/source/java/org/alfresco/service/cmr/repository/CyclicChildRelationshipException.java index ca6cb0f488..31f04cfb4a 100644 --- a/source/java/org/alfresco/service/cmr/repository/CyclicChildRelationshipException.java +++ b/source/java/org/alfresco/service/cmr/repository/CyclicChildRelationshipException.java @@ -24,8 +24,6 @@ */ package org.alfresco.service.cmr.repository; -import org.alfresco.repo.domain.ChildAssoc; - /** * Thrown when a cyclic parent-child relationship is detected. * @@ -35,16 +33,16 @@ public class CyclicChildRelationshipException extends RuntimeException { private static final long serialVersionUID = 3545794381924874036L; - private ChildAssoc assoc; + private ChildAssociationRef assocRef; - public CyclicChildRelationshipException(String msg, ChildAssoc assoc) + public CyclicChildRelationshipException(String msg, ChildAssociationRef assocRef) { super(msg); - this.assoc = assoc; + this.assocRef = assocRef; } - public ChildAssoc getAssoc() + public ChildAssociationRef getAssocRef() { - return assoc; + return assocRef; } } diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java index 76e38fd767..938d59bac9 100644 --- a/source/java/org/alfresco/service/cmr/repository/NodeService.java +++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java @@ -62,6 +62,20 @@ import org.alfresco.service.namespace.QNamePattern; @PublicService public interface NodeService { + /** + * Kick off any cleanup processes relating to the the particular implementation. + *

+ * This must cover cleanup of orphaned data and other housekeeping tasks that may + * be required. + *

+ * NB: Implementations should guard against multithreaded entry without + * blocking. + * + * @return Returns a list of messages detailing what was done. + */ + @Auditable(key = Auditable.Key.NO_KEY) + public List cleanup(); + /** * Gets a list of all available node store references * @@ -75,13 +89,22 @@ public interface NodeService * may create the store in any number of locations, including a database or * Subversion. * - * @param protocol the implementation protocol + * @param protocolthe implementation protocol * @param identifier the protocol-specific identifier * @return Returns a reference to the store * @throws StoreExistsException */ @Auditable(key = Auditable.Key.RETURN, parameters = {"protocol", "identifier"}) public StoreRef createStore(String protocol, String identifier) throws StoreExistsException; + + /** + * Delete a store and all its contents. + * + * @param storeRef the store to delete + * @throws InvalidStoreRefException if the store reference is invalid + */ + @Auditable(key= Auditable.Key.ARG_0, parameters = {"storeRef"}) + public void deleteStore(StoreRef storeRef); /** * @param storeRef a reference to the store to look for diff --git a/source/java/org/alfresco/service/cmr/repository/Path.java b/source/java/org/alfresco/service/cmr/repository/Path.java index 1076b2be43..c2fba32c4d 100644 --- a/source/java/org/alfresco/service/cmr/repository/Path.java +++ b/source/java/org/alfresco/service/cmr/repository/Path.java @@ -29,7 +29,6 @@ import java.util.Iterator; import java.util.LinkedList; import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespacePrefixResolver;