From b8aaa9c372b15e2e667f1cb4a0ae705170b0cdf2 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 26 Oct 2009 15:52:59 +0000 Subject: [PATCH] Merged V3.2 to HEAD 17163: org.alfresco.repo.domain.hibernate.AclDaoComponentImpl.updateAuthority() needs to flush/dirty the session in order to work 17160: Fix HeartBeat - Lazy initialization in scheduled job needed its own transaction 17146: Fix failing unit tests - HibernateNodeDaoServiceImpl.moveNodeToStore() must invalidate parentAssocsCache now that it contains NodeRefs 17145: Fixes to patches for new CRC schema changes - Sequenced patch.fixNameCrcValues-2 before all other patches - Fixed typos in schema upgrade script and added CRCs for the repository descriptor nodes, so that the descriptor service and patch service can boot up - HeartBeat initializes lazily so that it doesn't try to load information before the patch service has bootstrapped - Made FixNameCrcValuesPatch industrial strength by using BatchProcessor to handle multi threading, progress reporting and transaction delineation 17097: Removal of spurious logs directory accidentally introduced in 17096 17096: Performance tuning for improved throughput during high volume import from LDAP directory - Lucene indexer will now no longer index and then reindex the same node in the same transaction - lucene.indexer.mergerTargetOverlaysBlockingFactor reduced to 1 (improves indexing performance and no excessive throttling observed during 10 hour test) - HomeFolderManager fixed so that it pays attention to the eager home folder creation flag - HibernateNodeDaoServiceImpl.parentAssocsCache 'upgraded' to hold information about root nodes and node refs so that recursive methods such as prependPaths can run entirely out of the cache - Boolean argument added to getChildAssocs() so that preloading of all child nodes is optional - qname_crc column added to alf_child_assoc to allow efficient lookup and indexing of child associations by QName. CRC of (qname_namespace, qname_localname). - idx_alf_cass_qnln on qname_localname replaced with idx_alf_cass_qncrc (qname_crc, type_qname_id, parent_node_id) - All node service lookup queries involving qname_localname modified to include qname_crc in WHERE clause - schema patch provided - existing org.alfresco.repo.admin.patch.impl.FixNameCrcValuesPatch extended to also fill in qname_crc column and forced to run on newer schemas - Optimized ChainingUserRegistrySynchronizer so that it doesn't have to look up the entire set of authorities during an 'empty' incremental sync - ChainingUserRegistrySynchronizer no longer starts an outer transaction around all its smaller transactions (used to die due to timeout) - rule service disabled for LDAP batch processing threads - org.alfresco.cache.parentAssocsCache and org.alfresco.cache.storeAndNodeIdCache size increased to 80,000 - Fixed case sensitivity issue with person caching in PersonServiceImpl - Cache the people container in PersonServiceImpl for faster person lookups - PersonDAO removed and replaced with now more efficient node service child assoc lookup methods git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@17168 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 2 + config/alfresco/cache-context.xml | 2 +- .../AlfrescoPostCreate-3.2-Indexes.sql | 11 + .../child-assoc-qname-crc.sql | 44 ++ config/alfresco/ehcache-default.xml | 4 +- .../ehcache-custom.xml.sample.cluster | 4 +- .../messages/patch-service.properties | 8 +- .../alfresco/patch/patch-services-context.xml | 75 ++- config/alfresco/public-services-context.xml | 28 - config/alfresco/repository.properties | 2 +- .../default-synchronization-context.xml | 5 +- config/alfresco/version.properties | 2 +- .../patch/impl/FixNameCrcValuesPatch.java | 162 ++++-- .../org/alfresco/repo/avm/AVMNodeService.java | 8 + .../org/alfresco/repo/domain/ChildAssoc.java | 10 + .../repo/domain/hibernate/ChildAssocImpl.java | 32 ++ .../repo/domain/hibernate/Node.hbm.xml | 45 +- .../repo/node/db/DbNodeServiceImpl.java | 221 +++----- .../alfresco/repo/node/db/NodeDaoService.java | 10 + .../HibernateNodeDaoServiceImpl.java | 519 ++++++++++++++---- ...stractLuceneIndexerAndSearcherFactory.java | 2 +- .../lucene/AbstractLuceneIndexerImpl.java | 26 +- .../search/impl/lucene/index/IndexInfo.java | 2 +- .../security/authority/AuthorityDAOImpl.java | 14 +- .../security/person/HomeFolderManager.java | 13 +- .../security/person/PersonServiceImpl.java | 21 +- .../repo/security/sync/BatchProcessor.java | 38 +- .../ChainingUserRegistrySynchronizer.java | 304 ++++++---- .../ChainingUserRegistrySynchronizerTest.java | 84 +-- .../repo/version/NodeServiceImpl.java | 7 + .../service/cmr/repository/NodeService.java | 26 + .../service/cmr/security/PersonService.java | 14 + source/test-resources/sync-test-context.xml | 5 +- 33 files changed, 1123 insertions(+), 627 deletions(-) create mode 100644 config/alfresco/dbscripts/create/3.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-3.2-Indexes.sql create mode 100644 config/alfresco/dbscripts/upgrade/3.2/org.hibernate.dialect.MySQLInnoDBDialect/child-assoc-qname-crc.sql diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index ba53463410..e64407be4a 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -81,6 +81,7 @@ classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-PropertyValueTables.sql classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-AuditTables.sql classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-AvmTables.sql + classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-Indexes.sql @@ -103,6 +104,7 @@ + diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 40cb54a102..7e5bbf522f 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -311,7 +311,7 @@ org.alfresco.parentAssocsTransactionalCache - 10000 + 80000 diff --git a/config/alfresco/dbscripts/create/3.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-3.2-Indexes.sql b/config/alfresco/dbscripts/create/3.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-3.2-Indexes.sql new file mode 100644 index 0000000000..8af6895fed --- /dev/null +++ b/config/alfresco/dbscripts/create/3.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-3.2-Indexes.sql @@ -0,0 +1,11 @@ +-- +-- Title: Additional Indexes +-- Database: Generic +-- Since: V3.2 schema 2023 +-- Author: davew +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- +-- Additional indexes + +CREATE INDEX idx_alf_cass_qncrc on alf_child_assoc (qname_crc, type_qname_id, parent_node_id); diff --git a/config/alfresco/dbscripts/upgrade/3.2/org.hibernate.dialect.MySQLInnoDBDialect/child-assoc-qname-crc.sql b/config/alfresco/dbscripts/upgrade/3.2/org.hibernate.dialect.MySQLInnoDBDialect/child-assoc-qname-crc.sql new file mode 100644 index 0000000000..3daecc51dd --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/3.2/org.hibernate.dialect.MySQLInnoDBDialect/child-assoc-qname-crc.sql @@ -0,0 +1,44 @@ +-- +-- Title: Upgrade to V3.2 - Add qname_crc column to alf_child_assoc +-- Database: MySQL +-- Since: V3.2 schema 2023 +-- Author: davew +-- +-- Add qname_crc column to alf_child_assoc and change indexes +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +ALTER TABLE alf_child_assoc + ADD COLUMN qname_crc BIGINT NOT NULL DEFAULT 0 AFTER qname_localname +; + +-- Enable additional patches to run by CRC-ing the descriptor nodes +UPDATE alf_child_assoc + SET qname_crc = 147310537 + WHERE qname_ns_id = (SELECT id FROM alf_namespace WHERE uri = 'http://www.alfresco.org/model/system/1.0') + AND qname_localname = 'descriptor'; + +UPDATE alf_child_assoc + SET qname_crc = 369154895 + WHERE qname_ns_id = (SELECT id FROM alf_namespace WHERE uri = 'http://www.alfresco.org/model/system/1.0') + AND qname_localname = 'descriptor-current'; + +ALTER TABLE alf_child_assoc + DROP INDEX idx_alf_cass_qnln, + ALTER COLUMN qname_crc DROP DEFAULT +; + +CREATE INDEX idx_alf_cass_qncrc ON alf_child_assoc (qname_crc, type_qname_id, parent_node_id); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.2-Child-Assoc-QName-CRC'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V3.2-Child-Assoc-QName-CRC', 'Manually executed script upgrade V3.2 to Add qname_crc column to alf_child_assoc and change indexes', + 0, 3005, -1, 3006, null, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 0acd233c2c..90f8318d0b 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -277,13 +277,13 @@ /> diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index e8e3d75ed2..7ef5070ba9 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -537,7 +537,7 @@ @@ -642,7 +642,7 @@ diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index ca512a6f2d..5740253d0c 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -278,10 +278,10 @@ patch.authorityDefaultZonesPatch.result=Unzoned groups and people added to the d patch.authorityDefaultZonesPatch.users= Adding users to zones ... patch.authorityDefaultZonesPatch.groups= Adding groups to zones ... -patch.fixNameCrcValues.description=Fixes name CRC32 values to match UTF-8 encoding. -patch.fixNameCrcValues.result=Fixed {0} name CRC32 values for UTF-8 encoding. See file {1} for details. -patch.fixNameCrcValues.fixed=Updated CRC32 value for node ID {0}, name ''{1}'': {2} -> {3}. -patch.fixNameCrcValues.unableToChange=Failed to update the CRC32 value for node ID {0}: \n Node name: {1} \n CRC old: {2} \n CRC new: {3} \n Error: {4} +patch.fixNameCrcValues.description=Fixes name and qname CRC32 values to match UTF-8 encoding. +patch.fixNameCrcValues.result=Fixed CRC32 values for UTF-8 encoding for {0} node child associations. See file {1} for details. +patch.fixNameCrcValues.fixed=Updated CRC32 values for node ID {0}, name ''{1}'': {2} -> {3}, qname ''{4}'': {5} -> {6}. +patch.fixNameCrcValues.unableToChange=Failed to update the CRC32 value for node ID {0}: \n Node name: {1} \n name CRC old: {2} \n name CRC new: {3} \n qname CRC old: {4} \n qname CRC new: {5} \n Error: {6} patch.personUsagePatch.description=Add person 'cm:sizeCurrent' property (if missing). patch.personUsagePatch.result1=Added 'cm:sizeCurrent' property to {0} people that were missing this property. diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 4a552f085e..c1d4a528ae 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -121,6 +121,11 @@ + + + + + patch.savedSearchesPermission @@ -1790,6 +1795,11 @@ alfresco/bootstrap/alfrescoAuthorityStore.xml + + + + + @@ -1836,30 +1846,6 @@ - - patch.fixNameCrcValues - patch.fixNameCrcValues.description - 0 - 2014 - 2015 - - - - - - - - - - - - - - - - - - patch.db-V3.2-ContentTables patch.schemaUpgradeScript.description @@ -1968,4 +1954,45 @@ + + patch.db-V3.2-Child-Assoc-QName-CRC + patch.schemaUpgradeScript.description + 0 + 3005 + 3006 + + classpath:alfresco/dbscripts/upgrade/3.2/${db.script.dialect}/child-assoc-qname-crc.sql + + + + + + + + + patch.fixNameCrcValues-2 + patch.fixNameCrcValues.description + 0 + 3006 + 3007 + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index 3dbd657ac7..51b528ac9d 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1488,34 +1488,6 @@ - - - - - - - - - synchronize - - - - - - - org.alfresco.repo.security.sync.UserRegistrySynchronizer - - - - - - - userRegistrySynchronizerWriteTxnAdvisor - - checkTxnAdvisor - - - diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 2d5a8f0eff..10c7552b82 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -189,7 +189,7 @@ lucene.indexer.writerMinMergeDocs=1000 # lucene.indexer.mergerTargetIndexCount=5 lucene.indexer.mergerTargetOverlayCount=5 -lucene.indexer.mergerTargetOverlaysBlockingFactor=2 +lucene.indexer.mergerTargetOverlaysBlockingFactor=1 lucene.indexer.maxDocsForInMemoryMerge=10000 # # Other lucene properties diff --git a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml index 8f3b99d6ab..3c481a30bd 100644 --- a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml +++ b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml @@ -16,7 +16,7 @@ - + ${synchronization.synchronizeChangesOnly} @@ -59,6 +59,9 @@ + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 5d2443fa8a..1f11a1f2bd 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=3005 +version.schema=3007 diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java index 7385dda552..5e92ebdc42 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java @@ -43,7 +43,11 @@ import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.QNameDAO; import org.alfresco.repo.domain.hibernate.ChildAssocImpl; import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.security.sync.BatchProcessor; +import org.alfresco.repo.security.sync.BatchProcessor.Worker; import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.namespace.QName; import org.hibernate.SQLQuery; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; @@ -51,17 +55,19 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.type.LongType; import org.hibernate.type.StringType; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** * Fixes ETWOTWO-1133. - * Checks all CRC values for alf_child_assoc.child_node_name_crc. + * Checks all CRC values for alf_child_assoc.child_node_name_crc and alf_child_assoc.qname_crc. * * @author Derek Hulley * @since V2.2SP4 */ -public class FixNameCrcValuesPatch extends AbstractPatch +public class FixNameCrcValuesPatch extends AbstractPatch implements ApplicationEventPublisherAware { private static final String MSG_SUCCESS = "patch.fixNameCrcValues.result"; private static final String MSG_REWRITTEN = "patch.fixNameCrcValues.fixed"; @@ -70,6 +76,8 @@ public class FixNameCrcValuesPatch extends AbstractPatch private SessionFactory sessionFactory; private NodeDaoService nodeDaoService; private QNameDAO qnameDAO; + private RuleService ruleService; + private ApplicationEventPublisher applicationEventPublisher; public FixNameCrcValuesPatch() { @@ -95,6 +103,22 @@ public class FixNameCrcValuesPatch extends AbstractPatch { this.qnameDAO = qnameDAO; } + + /** + * @param ruleService the rule service + */ + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher) + */ + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) + { + this.applicationEventPublisher = applicationEventPublisher; + } @Override protected void checkProperties() @@ -103,6 +127,7 @@ public class FixNameCrcValuesPatch extends AbstractPatch checkPropertyNotNull(sessionFactory, "sessionFactory"); checkPropertyNotNull(nodeDaoService, "nodeDaoService"); checkPropertyNotNull(qnameDAO, "qnameDAO"); + checkPropertyNotNull(applicationEventPublisher, "applicationEventPublisher"); } @Override @@ -111,7 +136,7 @@ public class FixNameCrcValuesPatch extends AbstractPatch // initialise the helper HibernateHelper helper = new HibernateHelper(); helper.setSessionFactory(sessionFactory); - + try { String msg = helper.fixCrcValues(); @@ -161,66 +186,79 @@ public class FixNameCrcValuesPatch extends AbstractPatch public String fixCrcValues() throws Exception { // get the association types to check - @SuppressWarnings("unused") - List childAssocIds = findMismatchedCrcs(); + BatchProcessor batchProcessor = new BatchProcessor(transactionService + .getRetryingTransactionHelper(), ruleService, applicationEventPublisher, findMismatchedCrcs(), + "FixNameCrcValuesPatch", 100, 2, 20); // Precautionary flush and clear so that we have an empty session getSession().flush(); getSession().clear(); - int updated = 0; - for (Long childAssocId : childAssocIds) - { - ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId); - if (assoc == null) + + int updated = batchProcessor.process(new Worker(){ + + public String getIdentifier(Long entry) { - // Missing now ... - continue; + return entry.toString(); } - // Get the old CRC - long oldCrc = assoc.getChildNodeNameCrc(); - // Get the child node - Node childNode = assoc.getChild(); - // Get the name - String childName = (String) nodeDaoService.getNodeProperty(childNode.getId(), ContentModel.PROP_NAME); - if (childName == null) + + public void process(Long childAssocId) throws Throwable { - childName = childNode.getUuid(); - } - // Update the CRC - long crc = getCrc(childName); - // Update the assoc - assoc.setChildNodeNameCrc(crc); - // Persist - updated++; - try - { - getSession().flush(); - } - catch (Throwable e) - { - String msg = I18NUtil.getMessage(MSG_UNABLE_TO_CHANGE, childNode.getId(), childName, oldCrc, crc, e.getMessage()); - // We just log this and add details to the message file - if (logger.isDebugEnabled()) + ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId); + if (assoc == null) { - logger.debug(msg, e); + // Missing now ... + return; } - else + // Get the old CRCs + long oldChildCrc = assoc.getChildNodeNameCrc(); + long oldQNameCrc = assoc.getQnameCrc(); + + // Get the child node + Node childNode = assoc.getChild(); + // Get the name + String childName = (String) nodeDaoService.getNodeProperty(childNode.getId(), ContentModel.PROP_NAME); + if (childName == null) { - logger.warn(msg); + childName = childNode.getUuid(); } - writeLine(msg); - } - getSession().clear(); - // Record - writeLine(I18NUtil.getMessage(MSG_REWRITTEN, childNode.getId(), childName, oldCrc, crc)); - } + // Update the CRCs + long childCrc = getCrc(childName); + long qnameCrc = ChildAssocImpl.getCrc(assoc.getQName(qnameDAO)); + + // Update the assoc + assoc.setChildNodeNameCrc(childCrc); + assoc.setQnameCrc(qnameCrc); + // Persist + try + { + getSession().flush(); + } + catch (Throwable e) + { + String msg = I18NUtil.getMessage(MSG_UNABLE_TO_CHANGE, childNode.getId(), childName, oldChildCrc, + childCrc, oldQNameCrc, qnameCrc, e.getMessage()); + // We just log this and add details to the message file + if (logger.isDebugEnabled()) + { + logger.debug(msg, e); + } + else + { + logger.warn(msg); + } + writeLine(msg); + } + getSession().clear(); + // Record + writeLine(I18NUtil.getMessage(MSG_REWRITTEN, childNode.getId(), childName, oldChildCrc, childCrc, oldQNameCrc, qnameCrc)); + }}, true); + String msg = I18NUtil.getMessage(MSG_SUCCESS, updated, logFile); return msg; } - @SuppressWarnings("unchecked") private List findMismatchedCrcs() throws Exception { final Long qnameId = qnameDAO.getOrCreateQName(ContentModel.PROP_NAME).getFirst(); @@ -236,17 +274,23 @@ public class FixNameCrcValuesPatch extends AbstractPatch " ca.id AS child_assoc_id," + " ca.child_node_name_crc AS child_assoc_crc," + " np.string_value AS node_name," + - " n.uuid as node_uuid" + + " n.uuid as node_uuid," + + " ca.qname_crc AS qname_crc," + + " ca.qname_ns_id AS qname_ns_id," + + " ca.qname_localname AS qname_localname" + " FROM" + " alf_child_assoc ca" + - " JOIN alf_node n ON (ca.child_node_id = n.id AND ca.child_node_name_crc > 0)" + - " JOIN alf_node_properties np on (np.node_id = n.id AND np.qname_id = :qnameId)" + + " JOIN alf_node n ON (ca.child_node_id = n.id)" + + " LEFT OUTER JOIN alf_node_properties np on (np.node_id = n.id AND np.qname_id = :qnameId)" + ""); query.setLong("qnameId", qnameId); query.addScalar("child_assoc_id", new LongType()); query.addScalar("child_assoc_crc", new LongType()); query.addScalar("node_name", new StringType()); query.addScalar("node_uuid", new StringType()); + query.addScalar("qname_crc", new LongType()); + query.addScalar("qname_ns_id", new LongType()); + query.addScalar("qname_localname", new StringType()); return query.scroll(ScrollMode.FORWARD_ONLY); } }; @@ -256,21 +300,31 @@ public class FixNameCrcValuesPatch extends AbstractPatch rs = (ScrollableResults) getHibernateTemplate().execute(callback); while (rs.next()) { + // Compute child name crc Long assocId = (Long) rs.get(0); - Long dbCrc = (Long) rs.get(1); + Long dbChildCrc = (Long) rs.get(1); String name = (String) rs.get(2); String uuid = (String) rs.get(3); - long utf8Crc = -1L; + long utf8ChildCrc; if (name != null) { - utf8Crc = getCrc(name); + utf8ChildCrc = getCrc(name); } else { - utf8Crc = getCrc(uuid); + utf8ChildCrc = getCrc(uuid); } + + // Compute qname crc + Long dbQNameCrc = (Long) rs.get(4); + Long namespaceId = (Long) rs.get(5); + String namespace = qnameDAO.getNamespace(namespaceId).getSecond(); + String localName = (String) rs.get(6); + QName qname = QName.createQName(namespace, localName); + long utf8QNameCrc = ChildAssocImpl.getCrc(qname); + // Check - if (dbCrc != null && utf8Crc == dbCrc.longValue()) + if (dbChildCrc != null && utf8ChildCrc == dbChildCrc.longValue() && dbQNameCrc != null && utf8QNameCrc == dbQNameCrc.longValue()) { // It is a match, so ignore continue; diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 813886f84b..580ac16aec 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -1648,6 +1648,14 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi return result; } + + + public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, + QNamePattern qnamePattern, boolean preload) throws InvalidNodeRefException + { + return getChildAssocs(nodeRef, typeQNamePattern, qnamePattern); + } + public List getChildAssocs(NodeRef nodeRef, Set childNodeTypes) { /* diff --git a/source/java/org/alfresco/repo/domain/ChildAssoc.java b/source/java/org/alfresco/repo/domain/ChildAssoc.java index bf67a03295..36604a0d1e 100644 --- a/source/java/org/alfresco/repo/domain/ChildAssoc.java +++ b/source/java/org/alfresco/repo/domain/ChildAssoc.java @@ -158,6 +158,16 @@ public interface ChildAssoc extends Comparable */ public void setQnameLocalName(String localName); + /** + * @return Returns the crc value for the association's local QName + */ + public long getQnameCrc(); + + /** + * @param crc the crc value + */ + public void setQnameCrc(long crc); + public boolean getIsPrimary(); public void setIsPrimary(boolean isPrimary); diff --git a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java index 2cb47a848c..3b7e04a97c 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java @@ -25,9 +25,11 @@ package org.alfresco.repo.domain.hibernate; import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.zip.CRC32; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; @@ -50,6 +52,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable private Long typeQNameId; private Long qnameNamespaceId; private String qnameLocalName; + private long qnameCrc; private String childNodeName; private long childNodeNameCrc; private boolean isPrimary; @@ -230,18 +233,36 @@ public class ChildAssocImpl implements ChildAssoc, Serializable String assocQNameNamespace = qname.getNamespaceURI(); String assocQNameLocalName = qname.getLocalName(); Long assocQNameNamespaceId = qnameDAO.getOrCreateNamespace(assocQNameNamespace).getFirst(); + Long assocQNameCrc = getCrc(qname); // get write lock refWriteLock.lock(); try { setQnameNamespaceId(assocQNameNamespaceId); setQnameLocalName(assocQNameLocalName); + setQnameCrc(assocQNameCrc); } finally { refWriteLock.unlock(); } } + + public static long getCrc(QName qname) + { + CRC32 crc = new CRC32(); + try + { + crc.update(qname.getNamespaceURI().getBytes("UTF-8")); + crc.update(qname.getLocalName().getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("UTF-8 encoding is not supported"); + } + return crc.getValue(); + + } public boolean equals(Object obj) { @@ -290,6 +311,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable .append(", assoc type=").append(typeQNameId) .append(", assoc qname ns=").append(qnameNamespaceId) .append(", assoc qname localname=").append(qnameLocalName) + .append(", assoc qname crc=").append(qnameCrc) .append(", isPrimary=").append(isPrimary) .append("]"); return sb.toString(); @@ -461,6 +483,16 @@ public class ChildAssocImpl implements ChildAssoc, Serializable refWriteLock.unlock(); } } + + public long getQnameCrc() + { + return qnameCrc; + } + + public void setQnameCrc(long crc) + { + this.qnameCrc = crc; + } public String getChildNodeName() { 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 064acdd3d9..bb24c541f9 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -185,7 +185,8 @@ - + + @@ -260,14 +261,12 @@ select assoc, - parent, - child + parent from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent - join assoc.child as child where - child.id = :childId + assoc.child.id = :childId @@ -317,7 +316,8 @@ assoc.child.id = :childId and assoc.typeQNameId = :typeQNameId and assoc.qnameNamespaceId = :qnameNamespaceId and - assoc.qnameLocalName = :qnameLocalName + assoc.qnameLocalName = :qnameLocalName and + assoc.qnameCrc = :qnameCrc order by assoc.index, assoc.id @@ -356,6 +356,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -366,11 +367,10 @@ child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.parent as parent join assoc.child as child join child.store as store where - parent.id = :parentId and + assoc.parent.id = :parentId and assoc.typeQNameId = :typeQNameId and assoc.childNodeName in (:childNodeNames) order by @@ -384,6 +384,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -394,11 +395,10 @@ child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.parent as parent join assoc.child as child join child.store as store where - parent.id = :parentId + assoc.parent.id = :parentId order by assoc.index, assoc.id @@ -410,6 +410,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -420,13 +421,13 @@ child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.parent as parent join assoc.child as child join child.store as store where - parent.id = :parentId and + assoc.parent.id = :parentId and assoc.qnameNamespaceId = :qnameNamespaceId and - assoc.qnameLocalName = :qnameLocalName + assoc.qnameLocalName = :qnameLocalName and + assoc.qnameCrc = :qnameCrc order by assoc.index, assoc.id @@ -437,6 +438,7 @@ + @@ -450,6 +452,7 @@ a.type_qname_id, a.qname_ns_id, a.qname_localname, + a.qname_crc, a.child_node_name, a.child_node_name_crc, a.is_primary, @@ -471,6 +474,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -481,14 +485,14 @@ child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.parent as parent join assoc.child as child join child.store as store where - parent.id = :parentId and + assoc.parent.id = :parentId and assoc.typeQNameId = :typeQNameId and assoc.qnameNamespaceId = :qnameNamespaceId and - assoc.qnameLocalName = :qnameLocalName + assoc.qnameLocalName = :qnameLocalName and + assoc.qnameCrc = :qnameCrc order by assoc.index, assoc.id @@ -500,6 +504,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -510,11 +515,10 @@ child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.parent as parent join assoc.child as child join child.store as store where - parent.id = :parentId and + assoc.parent.id = :parentId and child.typeQNameId in (:childTypeQNameIds) order by assoc.index, @@ -527,6 +531,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -537,7 +542,6 @@ child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.parent as parent join assoc.child as child join child.store as store where @@ -554,6 +558,7 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.qnameCrc, assoc.childNodeName, assoc.childNodeNameCrc, assoc.isPrimary, @@ -834,6 +839,7 @@ + @@ -847,6 +853,7 @@ z1.type_qname_id, z1.qname_ns_id, z1.qname_localname, + z1.qname_crc, z1.child_node_name, z1.child_node_name_crc, z1.is_primary, diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 06b2346752..e221616a18 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 @@ -18,7 +18,7 @@ * 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 + * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ @@ -59,7 +59,6 @@ import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.AssociationExistsException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; import org.alfresco.service.cmr.repository.InvalidChildAssociationRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; @@ -655,6 +654,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // No recurse return false; } + + public boolean preLoadNodes() + { + return true; + } }; // Get all the QNames to remove List assocTypeQNamesToRemove = new ArrayList(aspectDef.getChildAssociations().keySet()); @@ -825,8 +829,14 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // No recurse return false; } - }; - // Get all the QNames to remove + + public boolean preLoadNodes() + { + return true; + } + }; + + // Get all the QNames to remove nodeDaoService.getPrimaryChildAssocs(nodeId, callback); // Each child must be deleted for (Pair childNodePair : childNodePairs) @@ -951,6 +961,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // No recurse return false; } + + public boolean preLoadNodes() + { + return true; + } }; nodeDaoService.getChildAssocs(parentNodeId, callback, false); @@ -1465,19 +1480,35 @@ 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, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern) + { + return getChildAssocs(nodeRef, typeQNamePattern, qnamePattern, true) ; + } + + /** + * Filters out any associations if their qname is not a match to the given pattern. + */ + public List getChildAssocs(NodeRef nodeRef, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern, final boolean preload) { // Get the node Pair nodePair = getNodePairNotNull(nodeRef); Long nodeId = nodePair.getFirst(); final List results = new ArrayList(100); + + abstract class BaseCallback implements NodeDaoService.ChildAssocRefQueryCallback + { + public boolean preLoadNodes() + { + return preload; + } + } if (qnamePattern instanceof QName) { // Both explicit QNames if (typeQNamePattern instanceof QName) { - NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + NodeDaoService.ChildAssocRefQueryCallback callback = new BaseCallback() { public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) @@ -1496,7 +1527,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl NodeDaoService.ChildAssocRefQueryCallback callback; if (typeQNamePattern.equals(RegexQNamePattern.MATCH_ALL)) { - callback = new NodeDaoService.ChildAssocRefQueryCallback() + callback = new BaseCallback() { public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) @@ -1508,7 +1539,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } else { - callback = new NodeDaoService.ChildAssocRefQueryCallback() + callback = new BaseCallback() { public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) @@ -1540,7 +1571,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // if the type is the wildcard type, and the qname is not a search, then use a shortcut query if (qnamePattern.equals(RegexQNamePattern.MATCH_ALL)) { - callback = new NodeDaoService.ChildAssocRefQueryCallback() + callback = new BaseCallback() { public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) @@ -1553,7 +1584,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl else { - callback = new NodeDaoService.ChildAssocRefQueryCallback() + callback = new BaseCallback() { public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) @@ -1579,7 +1610,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Local qname is pattern, type name is pattern else { - NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + NodeDaoService.ChildAssocRefQueryCallback callback = new BaseCallback() { public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) @@ -1625,6 +1656,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl results.add(childAssocPair.getSecond()); return false; } + + public boolean preLoadNodes() + { + return true; + } }; // Get all child associations with the specific qualified name nodeDaoService.getChildAssocsByChildTypes(nodeId, childNodeTypeQNames, callback); @@ -1693,6 +1729,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl results.add(childAssocPair.getSecond()); return false; } + + public boolean preLoadNodes() + { + return true; + } }; // Get all child associations with the specific qualified name nodeDaoService.getChildAssocs(nodeId, assocTypeQName, childNames, callback); @@ -1761,6 +1802,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl results.add(childAssocPair.getSecond()); return true; } + + public boolean preLoadNodes() + { + return false; + } }; // Get the child associations that meet the criteria @@ -1838,146 +1884,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl 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. - * - * @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( - Pair currentNodePair, - Pair currentRootNodePair, - Path currentPath, - Collection completedPaths, - Stack assocIdStack, - boolean primaryOnly) - throws CyclicChildRelationshipException - { - 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> parentAssocPairs = nodeDaoService.getParentAssocs(currentNodeId); - // does the node have parents - boolean hasParents = parentAssocPairs.size() > 0; - // does the current node have a root aspect? - 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, - 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) - { - if (first == null) - { - first = (Path.ChildAssocElement) element; - } - else - { - pathToSave.append(element); - } - } - if (first != null) - { - // mimic an association that would appear if the current node was below the root node - // or if first beneath the root node it will make the real thing - ChildAssociationRef updateAssocRef = new ChildAssociationRef( - isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(), - 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 (parentAssocPairs.size() == 0 && !isRoot) - { - throw new RuntimeException("Node without parents does not have root aspect: " + - currentNodeRef); - } - // walk up each parent association - for (Pair assocPair : parentAssocPairs) - { - Long assocId = assocPair.getFirst(); - ChildAssociationRef assocRef = assocPair.getSecond(); - // do we consider only primary assocs? - if (primaryOnly && !assocRef.isPrimary()) - { - continue; - } - // Ordering is meaningless here as we are constructing a path upwards - // and have no idea where the node comes in the sibling order or even - // if there are like-pathed siblings. - assocRef.setNthSibling(-1); - // build a path element - Path.Element element = new Path.ChildAssocElement(assocRef); - // create a new path that builds on the current path - Path path = new Path(); - path.append(currentPath); - // prepend element - path.prepend(element); - // get parent node - 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 - assocIdStack.push(assocId); - prependPaths(parentNodePair, currentRootNodePair, path, completedPaths, assocIdStack, primaryOnly); - assocIdStack.pop(); - } - // done - } - /** * @see #getPaths(NodeRef, boolean) * @see #prependPaths(Node, Path, Collection, Stack, boolean) @@ -2008,7 +1914,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // create storage for touched associations Stack assocIdStack = new Stack(); // call recursive method to sort it out - prependPaths(nodePair, null, currentPath, paths, assocIdStack, primaryOnly); + nodeDaoService.prependPaths(nodePair, null, currentPath, paths, assocIdStack, primaryOnly); // check that for the primary only case we have exactly one path if (primaryOnly && paths.size() != 1) @@ -2320,6 +2226,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl childNodePairs.add(childNodePair); return false; } + + public boolean preLoadNodes() + { + return true; + } }; // We only need to move child nodes that are not already in the same store nodeDaoService.getPrimaryChildAssocsNotInSameStore(nodeId, callback); @@ -2392,7 +2303,13 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl childNodePairs.add(childNodePair); return false; } + + public boolean preLoadNodes() + { + return true; + } }; + nodeDaoService.getPrimaryChildAssocs(nodeId, callback); // Each child must be moved to the same store as the parent for (Pair oldChildNodePair : childNodePairs) diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 612104add0..161b70d34d 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Stack; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.NodeAssoc; @@ -40,7 +41,9 @@ 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.CyclicChildRelationshipException; import org.alfresco.service.cmr.repository.NodeRef; +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.namespace.QName; @@ -243,6 +246,8 @@ public interface NodeDaoService Pair parentNodePair, Pair childNodePair ); + + boolean preLoadNodes(); } /** @@ -652,4 +657,9 @@ public interface NodeDaoService @DirtySessionAnnotation(markDirty=false) public Long getMaxTxnCommitTime(); + + @DirtySessionAnnotation(markDirty=false) + public void prependPaths(Pair currentNodePair, Pair currentRootNodePair, + Path currentPath, Collection completedPaths, Stack assocIdStack, boolean primaryOnly) + throws CyclicChildRelationshipException; } 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 90d8852cbb..a37a568b3a 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -26,6 +26,7 @@ package org.alfresco.repo.node.db.hibernate; import java.io.Serializable; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.SQLException; @@ -41,6 +42,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedMap; +import java.util.Stack; import java.util.TreeMap; import java.util.zip.CRC32; @@ -99,12 +101,14 @@ 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.ContentData; +import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.EntityRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; +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.datatype.DefaultTypeConverter; @@ -207,7 +211,7 @@ public class HibernateNodeDaoServiceImpl /** A cache mapping StoreRef and NodeRef instances to the entity IDs (primary key) */ private SimpleCache storeAndNodeIdCache; /** A cache for more performant lookups of the parent associations */ - private SimpleCache> parentAssocsCache; + private SimpleCache parentAssocsCache; private boolean isDebugEnabled = logger.isDebugEnabled(); private boolean isDebugParentAssocCacheEnabled = loggerParentAssocsCache.isDebugEnabled(); @@ -357,7 +361,7 @@ public class HibernateNodeDaoServiceImpl * * @param parentAssocsCache the cache */ - public void setParentAssocsCache(SimpleCache> parentAssocsCache) + public void setParentAssocsCache(SimpleCache parentAssocsCache) { this.parentAssocsCache = parentAssocsCache; } @@ -646,7 +650,7 @@ public class HibernateNodeDaoServiceImpl ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId); if (assoc == null) { - throw new AlfrescoRuntimeException("ChildAssoc ID " + childAssocId + " is invalid"); + throw new ObjectNotFoundException(childAssocId, ChildAssocImpl.class.getName()); } return assoc; } @@ -737,6 +741,7 @@ public class HibernateNodeDaoServiceImpl // Add the root aspect Pair rootAspectQNamePair = qnameDAO.getOrCreateQName(ContentModel.ASPECT_ROOT); rootNode.getAspects().add(rootAspectQNamePair.getFirst()); + parentAssocsCache.remove(rootNode.getId()); // Assign permissions to the root node SimpleAccessControlListProperties properties = DMPermissionsDaoComponentImpl.getDefaultProperties(); @@ -955,8 +960,8 @@ public class HibernateNodeDaoServiceImpl return; } Long nodeId = node.getId(); - Collection parentAssocs = getParentAssocsInternal(nodeId); - for (ChildAssoc parentAssoc : parentAssocs) + NodeInfo parentAssocs = getParentAssocsInternal(nodeId); + for (ParentAssocInfo parentAssoc : parentAssocs.getParentAssocs().values()) { propagateTimestamps(parentAssoc); } @@ -1004,7 +1009,6 @@ public class HibernateNodeDaoServiceImpl } } - public static final String QUERY_UPDATE_AUDITABLE_MODIFIED = "node.UpdateAuditableModified"; public Integer execute() throws Throwable { long now = System.currentTimeMillis(); @@ -1060,14 +1064,14 @@ public class HibernateNodeDaoServiceImpl * Ensures that the timestamps are propogated to the parent node of the association, but only * if the association requires it. */ - private void propagateTimestamps(ChildAssoc parentAssoc) + private void propagateTimestamps(ParentAssocInfo parentAssocPair) { // Shortcut if (!enableTimestampPropagation) { return; } - QName assocTypeQName = parentAssoc.getTypeQName(qnameDAO); + QName assocTypeQName = parentAssocPair.getChildAssociationRef().getTypeQName(); AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); if (assocDef == null) { @@ -1093,7 +1097,7 @@ public class HibernateNodeDaoServiceImpl propagator = new TimestampPropagator(); AlfrescoTransactionSupport.bindListener(propagator); } - propagator.addNode(parentAssoc.getParent().getId()); + propagator.addNode(parentAssocPair.getParentNodeId()); } public Pair newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException @@ -1165,6 +1169,7 @@ public class HibernateNodeDaoServiceImpl // Update the node updateNode(nodeId, storeRef, null, null); NodeRef nodeRef = node.getNodeRef(); + this.parentAssocsCache.remove(nodeId); return new Pair(node.getId(), nodeRef); } @@ -1511,7 +1516,6 @@ public class HibernateNodeDaoServiceImpl Collections.singletonMap(qname, propertyValue)); } - @SuppressWarnings("unchecked") public void addNodeProperties(Long nodeId, Map properties) { Node node = getNodeNotNull(nodeId); @@ -1681,6 +1685,11 @@ public class HibernateNodeDaoServiceImpl // Add them Set nodeAspects = node.getAspects(); nodeAspects.addAll(aspectQNameIds); + + if (hasNodeAspect(node, ContentModel.ASPECT_ROOT)) + { + parentAssocsCache.remove(nodeId); + } // Record change ID recordNodeUpdate(node); @@ -1702,6 +1711,11 @@ public class HibernateNodeDaoServiceImpl // Remove them Set nodeAspects = node.getAspects(); nodeAspects.removeAll(aspectQNameIds); + + if (aspectQNames.contains(ContentModel.ASPECT_ROOT)) + { + parentAssocsCache.remove(nodeId); + } // Record change ID recordNodeUpdate(node); @@ -1719,6 +1733,11 @@ public class HibernateNodeDaoServiceImpl } private boolean hasNodeAspect(Node node, QName aspectQName) + { + return hasNodeAspect(qnameDAO, node, aspectQName); + } + + private static boolean hasNodeAspect(QNameDAO qnameDAO, Node node, QName aspectQName) { Pair aspectQNamePair = qnameDAO.getQName(aspectQName); if (aspectQNamePair == null) @@ -2055,30 +2074,24 @@ public class HibernateNodeDaoServiceImpl childNameUnique.getFirst()); // Add it to the cache - Set parentAssocIds = parentAssocsCache.get(childNodeId); - if (parentAssocIds == null) + NodeInfo nodeInfo = parentAssocsCache.get(childNodeId); + if (nodeInfo == null) { // There isn't an entry in the cache, so go and make one - Collection parentAssocs = getParentAssocsInternal(childNodeId); - parentAssocIds = new HashSet(3); - for (ChildAssoc childAssoc : parentAssocs) - { - parentAssocIds.add(childAssoc.getId()); - } + nodeInfo = getParentAssocsInternal(childNodeId); } else { // Copy the list when we add to it - parentAssocIds = new HashSet(parentAssocIds); - parentAssocIds.add(assocId); + nodeInfo = nodeInfo.addAssoc(assocId, assoc, qnameDAO); + parentAssocsCache.put(childNodeId, nodeInfo); } - parentAssocsCache.put(childNodeId, parentAssocIds); if (isDebugParentAssocCacheEnabled) { loggerParentAssocsCache.debug("\n" + "Parent associations cache - Updating entry: \n" + " Node: " + childNodeId + "\n" + - " Assocs: " + parentAssocIds); + " Assocs: " + nodeInfo.getParentAssocs().keySet()); } // If this is a primary association then update the permissions @@ -2294,7 +2307,10 @@ public class HibernateNodeDaoServiceImpl } // Done - return new Pair(childAssocId, childAssoc.getChildAssocRef(qnameDAO)); + parentAssocsCache.remove(oldChildNode.getId()); + parentAssocsCache.remove(childNodeId); + ParentAssocInfo parentAssocInfo = new ParentAssocInfo(childAssoc, qnameDAO); + return new Pair(childAssocId, parentAssocInfo.getChildAssociationRef()); } /** @@ -2335,6 +2351,11 @@ public class HibernateNodeDaoServiceImpl childNodeIds.add(childNodePair.getFirst()); return false; } + + public boolean preLoadNodes() + { + return true; + } }; // Get all child associations with the specific qualified name getChildAssocs(nodeId, callback, false); @@ -2364,7 +2385,6 @@ public class HibernateNodeDaoServiceImpl } } - @SuppressWarnings("unchecked") public void getChildAssocs(final Long parentNodeId, final ChildAssocRefQueryCallback resultsCallback, final boolean recurse) { Node parentNode = getNodeNotNull(parentNodeId); @@ -2391,6 +2411,11 @@ public class HibernateNodeDaoServiceImpl } return false; } + + public boolean preLoadNodes() + { + return resultsCallback.preLoadNodes(); + } }; } @@ -2431,7 +2456,6 @@ public class HibernateNodeDaoServiceImpl // Done } - @SuppressWarnings("unchecked") public void getChildAssocs(final Long parentNodeId, final QName assocQName, ChildAssocRefQueryCallback resultsCallback) { final Pair assocQNameNamespacePair = qnameDAO.getNamespace(assocQName.getNamespaceURI()); @@ -2451,7 +2475,8 @@ public class HibernateNodeDaoServiceImpl .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME) .setLong("parentId", parentNodeId) .setLong("qnameNamespaceId", assocQNameNamespacePair.getFirst()) - .setString("qnameLocalName", assocQNameLocalName); + .setString("qnameLocalName", assocQNameLocalName) + .setLong("qnameCrc", ChildAssocImpl.getCrc(assocQName)); DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.scroll(ScrollMode.FORWARD_ONLY); } @@ -2541,6 +2566,11 @@ public class HibernateNodeDaoServiceImpl { return resultsCallback.handle(childAssocPair, parentNodePair, childNodePair); } + + public boolean preLoadNodes() + { + return resultsCallback.preLoadNodes(); + } }; ScrollableResults queryResults = null; @@ -2628,7 +2658,8 @@ public class HibernateNodeDaoServiceImpl .setLong("parentId", parentNodeId) .setLong("typeQNameId", assocTypeQNamePair.getFirst()) .setLong("qnameNamespaceId", assocQNameNamespacePair.getFirst()) - .setString("qnameLocalName", assocQNameLocalName); + .setString("qnameLocalName", assocQNameLocalName) + .setLong("qnameCrc", ChildAssocImpl.getCrc(assocQName)); DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.scroll(ScrollMode.FORWARD_ONLY); } @@ -2816,12 +2847,12 @@ public class HibernateNodeDaoServiceImpl .setLong("childId", childNodeId) .setLong("typeQNameId", assocTypeQNamePair.getFirst()) .setParameter("qnameNamespaceId", assocQNameNamespacePair.getFirst()) - .setParameter("qnameLocalName", assocQNameLocalName); + .setParameter("qnameLocalName", assocQNameLocalName) + .setLong("qnameCrc", ChildAssocImpl.getCrc(assocQName)); DirtySessionMethodInterceptor.setQueryFlushMode(session, query); return query.list(); } }; - @SuppressWarnings("unchecked") List childAssocs = (List) getHibernateTemplate().execute(callback); Pair ret = null; for (ChildAssoc childAssoc : childAssocs) @@ -2912,16 +2943,19 @@ public class HibernateNodeDaoServiceImpl /** * 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
+          0 assoc.id,
+          1 assoc.typeQName,
+          2 assoc.qnameNamespace,
+          3 assoc.qnameLocalName,
+          4 assoc.qnameCrc,
+          5 assoc.childNodeName
+          6 assoc.childNodeNameCrc
+          7 assoc.isPrimary,
+          8 assoc.index,
+          9 child.id,
+         10 child.store.key.protocol,
+         11 child.store.key.identifier,
+         12 child.uuid
      * 
*/ @SuppressWarnings("unchecked") @@ -2942,14 +2976,14 @@ public class HibernateNodeDaoServiceImpl String assocQNameNamespace = qnameDAO.getNamespace((Long) row[2]).getSecond(); String assocQNameLocalName = (String) row[3]; QName assocQName = QName.createQName(assocQNameNamespace, assocQNameLocalName); - String assocChildNodeName = (String) row[4]; - Long assocChildNodeNameCrc = (Long) row[5]; - Boolean assocIsPrimary = (Boolean) row[6]; - Integer assocIndex = (Integer) row[7]; - Long childNodeId = (Long) row[8]; - String childProtocol = (String) row[9]; - String childIdentifier = (String) row[10]; - String childUuid = (String) row[11]; + String assocChildNodeName = (String) row[5]; + Long assocChildNodeNameCrc = (Long) row[6]; + Boolean assocIsPrimary = (Boolean) row[7]; + Integer assocIndex = (Integer) row[8]; + Long childNodeId = (Long) row[9]; + String childProtocol = (String) row[10]; + String childIdentifier = (String) row[11]; + String childUuid = (String) row[12]; NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); ChildAssociationRef assocRef = new ChildAssociationRef( assocTypeQName, @@ -2982,7 +3016,10 @@ public class HibernateNodeDaoServiceImpl } // Cache the nodes - cacheNodes(childNodeRefs); + if (resultsCallback.preLoadNodes() && !childNodeRefs.isEmpty()) + { + cacheNodes(childNodeRefs); + } // Pass results to callback for (Object[] callbackResult : callbackResults) @@ -3094,7 +3131,7 @@ public class HibernateNodeDaoServiceImpl Long nodeId = node.getId(); storeAndNodeIdCache.put(node.getNodeRef(), nodeId); nodeIds.add(nodeId); - } + } if (nodeIds.size() == 0) { @@ -3108,20 +3145,17 @@ public class HibernateNodeDaoServiceImpl criteria.setCacheMode(CacheMode.PUT); criteria.setFlushMode(FlushMode.MANUAL); List parentAssocs = criteria.list(); + Map> parentAssocMap = new HashMap>(nodeIds.size() * 2); for (ChildAssoc parentAssoc : parentAssocs) { Long nodeId = parentAssoc.getChild().getId(); - Set parentAssocsOfNode = parentAssocsCache.get(nodeId); + List parentAssocsOfNode = parentAssocMap.get(nodeId); if (parentAssocsOfNode == null) { - parentAssocsOfNode = new HashSet(3); + parentAssocsOfNode = new ArrayList(3); + parentAssocMap.put(nodeId, parentAssocsOfNode); } - else - { - parentAssocsOfNode = new HashSet(parentAssocsOfNode); - } - parentAssocsOfNode.add(parentAssoc.getId()); - parentAssocsCache.put(nodeId, parentAssocsOfNode); + parentAssocsOfNode.add(parentAssoc); if (isDebugParentAssocCacheEnabled) { loggerParentAssocsCache.debug("\n" + @@ -3130,6 +3164,17 @@ public class HibernateNodeDaoServiceImpl " Assocs: " + parentAssocsOfNode); } } + // Cache NodeInfo for each node + for (Node node : nodeList) + { + Long nodeId = node.getId(); + List parentAsssocsOfNode = parentAssocMap.get(nodeId); + if (parentAsssocsOfNode == null) + { + parentAsssocsOfNode = Collections.emptyList(); + } + parentAssocsCache.put(nodeId, new NodeInfo(node, qnameDAO, parentAsssocsOfNode)); + } } private Collection> convertToAssocRefs(List queryResults) @@ -3364,19 +3409,18 @@ public class HibernateNodeDaoServiceImpl Long childNodeId = childNode.getId(); // Add remove the child association from the cache - Set oldParentAssocIds = parentAssocsCache.get(childNodeId); - if (oldParentAssocIds != null) + NodeInfo oldNodeInfo = parentAssocsCache.get(childNodeId); + if (oldNodeInfo != null) { - Set newParentAssocIds = new HashSet(oldParentAssocIds); - newParentAssocIds.remove(childAssocId); - parentAssocsCache.put(childNodeId, newParentAssocIds); + NodeInfo newNodeInfo = oldNodeInfo.removeAssoc(childAssocId); + parentAssocsCache.put(childNodeId, newNodeInfo); if (this.isDebugParentAssocCacheEnabled) { loggerParentAssocsCache.debug("\n" + - "Parent associations cache - Updating entry: \n" + - " Node: " + childNodeId + "\n" + - " Before: " + oldParentAssocIds + "\n" + - " After: " + newParentAssocIds); + "Parent associations cache - Updating entry: \n" + + " Node: " + childNodeId + "\n" + + " Before: " + oldNodeInfo.getParentAssocs().keySet() + "\n" + + " After: " + newNodeInfo.getParentAssocs().keySet()); } } @@ -3404,45 +3448,47 @@ public class HibernateNodeDaoServiceImpl * @return Returns the parent associations without any interpretation */ @SuppressWarnings("unchecked") - private Collection getParentAssocsInternal(final Long childNodeId) + private NodeInfo getParentAssocsInternal(final Long childNodeId) { - List parentAssocs = null; // First check the cache - Set parentAssocIds = parentAssocsCache.get(childNodeId); - if (parentAssocIds != null) + NodeInfo nodeInfo = parentAssocsCache.get(childNodeId); + if (nodeInfo != null) { - if (isDebugParentAssocCacheEnabled) + // Let's ensure this ref hasn't become stale due to a concurrent cascade delete + try { - loggerParentAssocsCache.debug("\n" + - "Parent associations cache - Hit: \n" + - " Node: " + childNodeId + "\n" + - " Assocs: " + parentAssocIds); - } - parentAssocs = new ArrayList(parentAssocIds.size()); - for (Long parentAssocId : parentAssocIds) - { - ChildAssoc parentAssoc = (ChildAssoc) getSession().get(ChildAssocImpl.class, parentAssocId); - if (parentAssoc == null) + for (Long assocId : nodeInfo.getParentAssocs().keySet()) { - // The cache is out of date, so just repopulate it - parentAssocs = null; - break; + getChildAssocNotNull(assocId); } - else + if (isDebugParentAssocCacheEnabled) { - parentAssocs.add(parentAssoc); + loggerParentAssocsCache.debug("\n" + "Parent associations cache - Hit: \n" + " Node: " + + childNodeId + "\n" + " Assocs: " + nodeInfo.getParentAssocs().keySet()); } } + catch (ObjectNotFoundException e) + { + parentAssocsCache.remove(childNodeId); + nodeInfo = null; + } } // Did we manage to get the parent assocs - if (parentAssocs == null) + if (nodeInfo == null) { + // Assume stale data if the node has been deleted + Node node = getNodeNotNull(childNodeId); + if (node.getDeleted()) + { + throw new ObjectNotFoundException(childNodeId, NodeImpl.class.getName()); + } + if (isDebugParentAssocCacheEnabled) { loggerParentAssocsCache.debug("\n" + "Parent associations cache - Miss: \n" + " Node: " + childNodeId + "\n" + - " Assocs: " + parentAssocIds); + " Assocs: null"); } HibernateCallback callback = new HibernateCallback() { @@ -3456,29 +3502,163 @@ public class HibernateNodeDaoServiceImpl } }; List rows = (List) getHibernateTemplate().execute(callback); - parentAssocs = new ArrayList(rows.size()); - parentAssocIds = new HashSet(parentAssocs.size()); - for (Object[] row : rows) - { - ChildAssoc parentAssoc = (ChildAssoc) row[0]; - // Populate the results - parentAssocs.add(parentAssoc); - parentAssocIds.add(parentAssoc.getId()); - } + + nodeInfo = new NodeInfo(node, qnameDAO, rows); // Populate the cache - parentAssocsCache.put(childNodeId, parentAssocIds); + parentAssocsCache.put(childNodeId, nodeInfo); if (isDebugParentAssocCacheEnabled) { loggerParentAssocsCache.debug("\n" + "Parent associations cache - Adding entry: \n" + " Node: " + childNodeId + "\n" + - " Assocs: " + parentAssocIds); + " Assocs: " + nodeInfo.getParentAssocs().keySet()); } } + // Done - return parentAssocs; + return nodeInfo; } + /** + * 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. + * + * @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 + */ + public void prependPaths( + Pair currentNodePair, + Pair currentRootNodePair, + Path currentPath, + Collection completedPaths, + Stack assocIdStack, + boolean primaryOnly) + throws CyclicChildRelationshipException + { + 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 = getRootNode(currentStoreRef); + currentRootNodePair = new Pair(currentStoreRef, rootNodePair.getSecond()); + } + + // get the parent associations of the given node + NodeInfo nodeInfo = getParentAssocsInternal(currentNodeId); + + // does the node have parents + boolean hasParents = nodeInfo.getParentAssocs().size() > 0; + // does the current node have a root aspect? + + // look for a root. If we only want the primary root, then ignore all but the top-level root. + if (!(primaryOnly && hasParents) && nodeInfo.isRoot()) // 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, + 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) + { + if (first == null) + { + first = (Path.ChildAssocElement) element; + } + else + { + pathToSave.append(element); + } + } + if (first != null) + { + // mimic an association that would appear if the current node was below the root node + // or if first beneath the root node it will make the real thing + ChildAssociationRef updateAssocRef = new ChildAssociationRef( + nodeInfo.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 (!hasParents && !nodeInfo.isRoot()) + { + throw new RuntimeException("Node without parents does not have root aspect: " + + currentNodeRef); + } + // walk up each parent association + for (Map.Entry entry: nodeInfo.getParentAssocs().entrySet()) + { + Long assocId = entry.getKey(); + ParentAssocInfo parentAssocInfo = entry.getValue(); + ChildAssociationRef assocRef = parentAssocInfo.getChildAssociationRef(); + // do we consider only primary assocs? + if (primaryOnly && !assocRef.isPrimary()) + { + continue; + } + // Ordering is meaningless here as we are constructing a path upwards + // and have no idea where the node comes in the sibling order or even + // if there are like-pathed siblings. + assocRef.setNthSibling(-1); + // build a path element + Path.Element element = new Path.ChildAssocElement(assocRef); + // create a new path that builds on the current path + Path path = new Path(); + path.append(currentPath); + // prepend element + path.prepend(element); + // get parent node pair + Pair parentNodePair = new Pair(parentAssocInfo.getParentNodeId(), assocRef.getParentRef()); + + // 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 + assocIdStack.push(assocId); + prependPaths(parentNodePair, currentRootNodePair, path, completedPaths, assocIdStack, primaryOnly); + assocIdStack.pop(); + } + // done + } + /** * {@inheritDoc} * @@ -3486,14 +3666,14 @@ public class HibernateNodeDaoServiceImpl */ public Collection> getParentAssocs(final Long childNodeId) { - Collection parentAssocs = getParentAssocsInternal(childNodeId); + NodeInfo nodeInfo = getParentAssocsInternal(childNodeId); + Map parentAssocs = nodeInfo.getParentAssocs(); Collection> ret = new ArrayList>(parentAssocs.size()); - for (ChildAssoc childAssoc : parentAssocs) + for (Map.Entry entry : parentAssocs.entrySet()) { - Long childAssocId = childAssoc.getId(); - ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(qnameDAO); - Pair childAssocPair = new Pair(childAssocId, childAssocRef); + Pair childAssocPair = new Pair(entry.getKey(), entry + .getValue().getChildAssociationRef()); ret.add(childAssocPair); } // Done @@ -3512,12 +3692,13 @@ public class HibernateNodeDaoServiceImpl public Pair getPrimaryParentAssoc(Long childNodeId) { // get the assocs pointing to the node - Collection parentAssocs = getParentAssocsInternal(childNodeId); - ChildAssoc primaryAssoc = null; - for (ChildAssoc assoc : parentAssocs) + NodeInfo nodeInfo = getParentAssocsInternal(childNodeId); + Pair primaryAssoc = null; + for (Map.Entry entry : nodeInfo.getParentAssocs().entrySet()) { + ChildAssociationRef assoc = entry.getValue().getChildAssociationRef(); // ignore non-primary assocs - if (!assoc.getIsPrimary()) + if (!assoc.isPrimary()) { continue; } @@ -3537,7 +3718,7 @@ public class HibernateNodeDaoServiceImpl } } } - primaryAssoc = assoc; + primaryAssoc = new Pair(entry.getKey(), assoc); // we keep looping to hunt out data integrity issues } // done @@ -3547,7 +3728,7 @@ public class HibernateNodeDaoServiceImpl } else { - return new Pair(primaryAssoc.getId(), primaryAssoc.getChildAssocRef(qnameDAO)); + return primaryAssoc; } } @@ -4092,7 +4273,6 @@ public class HibernateNodeDaoServiceImpl } } - @SuppressWarnings("unchecked") public void getNodesDeletedInOldTxns( final Long minNodeId, long maxCommitTime, @@ -4243,7 +4423,6 @@ public class HibernateNodeDaoServiceImpl return txns; } - @SuppressWarnings("unchecked") public int getTxnUpdateCount(final long txnId) { HibernateCallback callback = new HibernateCallback() @@ -4262,7 +4441,6 @@ public class HibernateNodeDaoServiceImpl return count.intValue(); } - @SuppressWarnings("unchecked") public int getTxnDeleteCount(final long txnId) { HibernateCallback callback = new HibernateCallback() @@ -4281,7 +4459,6 @@ public class HibernateNodeDaoServiceImpl return count.intValue(); } - @SuppressWarnings("unchecked") public int getTransactionCount() { HibernateCallback callback = new HibernateCallback() @@ -4669,7 +4846,7 @@ public class HibernateNodeDaoServiceImpl if (propertyTypeQName.equals(DataTypeDefinition.ANY)) { // It is multi-valued if required (we are not in a collection and the property is a new collection) - isMultiValued = (value != null) && (value instanceof Collection) && (collectionIndex == IDX_NO_COLLECTION); + isMultiValued = (value != null) && (value instanceof Collection) && (collectionIndex == IDX_NO_COLLECTION); } else { @@ -4679,7 +4856,7 @@ public class HibernateNodeDaoServiceImpl // Handle different scenarios. // - Do we need to explode a collection? // - Does the property allow collections? - if (collectionIndex == IDX_NO_COLLECTION && isMultiValued && !(value instanceof Collection)) + if (collectionIndex == IDX_NO_COLLECTION && isMultiValued && !(value instanceof Collection)) { // We are not (yet) processing a collection but the property should be part of a collection HibernateNodeDaoServiceImpl.addValueToPersistedProperties( @@ -4692,7 +4869,7 @@ public class HibernateNodeDaoServiceImpl localeDAO, contentDataDAO); } - else if (collectionIndex == IDX_NO_COLLECTION && value instanceof Collection) + else if (collectionIndex == IDX_NO_COLLECTION && value instanceof Collection) { // We are not (yet) processing a collection and the property is a collection i.e. needs exploding // Check that multi-valued properties are supported if the property is a collection @@ -4762,7 +4939,7 @@ public class HibernateNodeDaoServiceImpl { // We are either processing collection elements OR the property is not a collection // Collections of collections are only supported by type d:any - if (value instanceof Collection && !propertyTypeQName.equals(DataTypeDefinition.ANY)) + if (value instanceof Collection && !propertyTypeQName.equals(DataTypeDefinition.ANY)) { throw new DictionaryException( "Collections of collections (Serializable) are only supported by type 'd:any': \n" + @@ -4959,7 +5136,7 @@ public class HibernateNodeDaoServiceImpl // If the property is multi-valued then the output property must be a collection if (currentPropertyDef != null && currentPropertyDef.isMultiValued()) { - if (collapsedValue != null && !(collapsedValue instanceof Collection)) + if (collapsedValue != null && !(collapsedValue instanceof Collection)) { // Can't use Collections.singletonList: ETHREEOH-1172 ArrayList collection = new ArrayList(1); @@ -5059,7 +5236,7 @@ public class HibernateNodeDaoServiceImpl } } // Make sure that multi-valued properties are returned as a collection - if (propertyDef != null && propertyDef.isMultiValued() && result != null && !(result instanceof Collection)) + if (propertyDef != null && propertyDef.isMultiValued() && result != null && !(result instanceof Collection)) { // Can't use Collections.singletonList: ETHREEOH-1172 ArrayList collection = new ArrayList(1); @@ -5198,4 +5375,104 @@ public class HibernateNodeDaoServiceImpl } } + + private static class NodeInfo implements Serializable + { + private static final long serialVersionUID = -2167221525380802365L; + private final boolean isRoot; + private final boolean isStoreRoot; + private final Map parentAssocInfo; + + public NodeInfo(Node node, QNameDAO qnameDAO, List parents) + { + this.isRoot = hasNodeAspect(qnameDAO, node, ContentModel.ASPECT_ROOT); + this.isStoreRoot = node.getTypeQName(qnameDAO).equals(ContentModel.TYPE_STOREROOT); + this.parentAssocInfo = new HashMap(5); + for (Object parent : parents) + { + ChildAssoc parentAssoc = null; + if (parent instanceof ChildAssoc) + { + parentAssoc = (ChildAssoc) parent; + } + else if (parent.getClass().isArray()) + { + parentAssoc = (ChildAssoc) Array.get(parent, 0); + } + if (parentAssoc != null) + { + // Populate the results + parentAssocInfo.put(parentAssoc.getId(), new ParentAssocInfo(parentAssoc, qnameDAO)); + } + } + } + + private NodeInfo(NodeInfo copy) + { + this.isRoot = copy.isRoot; + this.isStoreRoot = copy.isStoreRoot; + this.parentAssocInfo = new HashMap(copy.parentAssocInfo); + } + + public boolean isRoot() + { + return isRoot; + } + + public boolean isStoreRoot() + { + return isStoreRoot; + } + + public Map getParentAssocs() + { + return parentAssocInfo; + } + + public NodeInfo addAssoc(Long assocId, ChildAssoc parentAssoc, QNameDAO qnameDAO) + { + return addAssoc(assocId, new ParentAssocInfo(parentAssoc, qnameDAO)); + } + + public NodeInfo addAssoc(Long assocId, ParentAssocInfo parentAssocInfo) + { + NodeInfo copy = new NodeInfo(this); + copy.parentAssocInfo.put(assocId, parentAssocInfo); + return copy; + } + + public NodeInfo removeAssoc(Long assocId) + { + NodeInfo copy = new NodeInfo(this); + copy.parentAssocInfo.remove(assocId); + return copy; + } + + } + + private static class ParentAssocInfo implements Serializable + { + private static final long serialVersionUID = -3888870827401574704L; + private final ChildAssociationRef childAssociationRef; + private final Long parentNodeId; + + public ParentAssocInfo(ChildAssoc parentAssoc, QNameDAO qnameDAO) + { + this.childAssociationRef = parentAssoc.getChildAssocRef(qnameDAO); + this.parentNodeId = parentAssoc.getParent().getId(); + } + + public ChildAssociationRef getChildAssociationRef() + { + // Return a copy, as it's mutated by prependPaths + return new ChildAssociationRef(childAssociationRef.getTypeQName(), childAssociationRef.getParentRef(), + childAssociationRef.getQName(), childAssociationRef.getChildRef(), childAssociationRef.isPrimary(), + childAssociationRef.getNthSibling()); + } + + public Long getParentNodeId() + { + return parentNodeId; + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java index dd52954162..73e0205943 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java @@ -179,7 +179,7 @@ public abstract class AbstractLuceneIndexerAndSearcherFactory implements LuceneI private int mergerTargetOverlayCount = 5; - private int mergerTargetOverlaysBlockingFactor = 2; + private int mergerTargetOverlaysBlockingFactor = 1; private int termIndexInterval =IndexWriter.DEFAULT_TERM_INDEX_INTERVAL; diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java index 3130313646..1e8bf47477 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 @@ -677,7 +677,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase { if (commandList.size() > 0) { - Command last = commandList.get(commandList.size() - 1); + Command last = commandList.get(commandList.size() - 1); if ((last.action == command.action) && (last.ref.equals(command.ref))) { return; @@ -692,7 +692,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase } } - private void purgeCommandList(Command command) + private void purgeCommandList(Command command) { if (command.action == Action.DELETE) { @@ -712,17 +712,27 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase } } - private void removeFromCommandList(Command command, boolean matchExact) + private void removeFromCommandList(Command command, boolean matchExact) { for (ListIterator> it = commandList.listIterator(commandList.size()); it.hasPrevious(); /**/) { Command current = it.previous(); if (matchExact) { - if ((current.action == command.action) && (current.ref.equals(command.ref))) + if (current.ref.equals(command.ref)) { - it.remove(); - return; + if ((current.action == command.action)) + { + it.remove(); + return; + } + // If there is an INDEX in this same transaction and the current command is a reindex, remove it and + // replace the current command with it + else if (command.action != Action.DELETE && current.action == Action.INDEX) + { + it.remove(); + command.action = Action.INDEX; + } } } else @@ -746,7 +756,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase mainReader = getReader(); Set forIndex = new LinkedHashSet(); - for (Command command : commandList) + for (Command command : commandList) { if (command.action == Action.INDEX) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index cc3c8a05c2..2fb1bf9708 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -323,7 +323,7 @@ public class IndexInfo implements IndexMonitor private int mergerTargetOverlays = 5; - private int mergerTargetOverlaysBlockingFactor = 2; + private int mergerTargetOverlaysBlockingFactor = 1; // Common properties for indexers diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index 3677b10718..033a044c7a 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -251,7 +251,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor if (container != null) { for (ChildAssociationRef childRef : nodeService.getChildAssocs(container, - ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL)) + ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL, false)) { addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, pattern); } @@ -267,7 +267,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor if (container != null) { for (ChildAssociationRef childRef : nodeService.getChildAssocs(container, - ContentModel.ASSOC_IN_ZONE, RegexQNamePattern.MATCH_ALL)) + ContentModel.ASSOC_IN_ZONE, RegexQNamePattern.MATCH_ALL, false)) { addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, pattern); @@ -427,7 +427,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor else { List results = nodeService.getChildAssocs(getAuthorityContainer(), - ContentModel.ASSOC_CHILDREN, QName.createQName("cm", name, namespacePrefixResolver)); + ContentModel.ASSOC_CHILDREN, QName.createQName("cm", name, namespacePrefixResolver), false); return results.isEmpty() ? null : results.get(0).getChildRef(); } } @@ -468,13 +468,13 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor if (systemContainerRef == null) { NodeRef rootNodeRef = nodeService.getRootNode(this.storeRef); - List results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem); + List results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem, false); if (results.size() == 0) { throw new AlfrescoRuntimeException("Required system path not found: " + qnameAssocSystem); } NodeRef sysNodeRef = results.get(0).getChildRef(); - results = nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, assocQName); + results = nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, assocQName, false); if (results.size() == 0) { throw new AlfrescoRuntimeException("Required path not found: " + assocQName); @@ -543,7 +543,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor { NodeRef zoneContainerRef = getZoneContainer(); QName zoneQName = QName.createQName("cm", zoneName, namespacePrefixResolver); - List results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName); + List results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName, false); if (results.isEmpty()) { if (create) @@ -605,7 +605,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor NodeRef zoneRef = getZone(zoneName); if (zoneRef != null) { - for (ChildAssociationRef childRef : nodeService.getChildAssocs(zoneRef, ContentModel.ASSOC_IN_ZONE, RegexQNamePattern.MATCH_ALL)) + for (ChildAssociationRef childRef : nodeService.getChildAssocs(zoneRef, ContentModel.ASSOC_IN_ZONE, RegexQNamePattern.MATCH_ALL, false)) { addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, null); } diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderManager.java b/source/java/org/alfresco/repo/security/person/HomeFolderManager.java index 863eee99bb..450f90c0ca 100644 --- a/source/java/org/alfresco/repo/security/person/HomeFolderManager.java +++ b/source/java/org/alfresco/repo/security/person/HomeFolderManager.java @@ -116,9 +116,20 @@ public class HomeFolderManager implements NodeServicePolicies.OnCreateNodePolicy } /** - * Find the provider and call. + * Find the provider and call if eager home folder creation is enabled. */ public void onCreateNode(ChildAssociationRef childAssocRef) + { + if (enableHomeFolderCreationAsPeopleAreCreated) + { + makeHomeFolder(childAssocRef); + } + } + + /** + * Find the provider and call. + */ + public void makeHomeFolder(ChildAssociationRef childAssocRef) { HomeFolderProvider provider = defaultProvider; String providerName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(childAssocRef diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index c926db1662..ba45abb9d5 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -350,7 +350,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { List childRefs = nodeService.getChildAssocs(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, QName.createQName("cm", searchUserName.toLowerCase(), - namespacePrefixResolver)); + namespacePrefixResolver), false); allRefs = new LinkedHashMap(childRefs.size() * 2); // add to cache personCache.put(cacheKey, allRefs); @@ -569,6 +569,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } public void setPersonProperties(String userName, Map properties) + { + setPersonProperties(userName, properties, true); + } + + public void setPersonProperties(String userName, Map properties, boolean autoCreate) { NodeRef personNode = getPersonOrNull(userName); if (personNode == null) @@ -584,7 +589,10 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } else { - makeHomeFolderIfRequired(personNode); + if (autoCreate) + { + makeHomeFolderIfRequired(personNode); + } String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, ContentModel.PROP_USERNAME)); properties.put(ContentModel.PROP_USERNAME, realUserName); } @@ -603,7 +611,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { HashMap properties = getDefaultProperties(userName); NodeRef person = createPerson(properties); - makeHomeFolderIfRequired(person); return person; } @@ -619,7 +626,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { public Object execute() throws Throwable { - homeFolderManager.onCreateNode(ref); + homeFolderManager.makeHomeFolder(ref); return null; } }, transactionService.isReadOnly(), false); @@ -688,7 +695,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { NodeRef rootNodeRef = nodeService.getRootNode(tenantService.getName(storeRef)); List children = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, - QName.createQName(SYSTEM_FOLDER_SHORT_QNAME, namespacePrefixResolver)); + QName.createQName(SYSTEM_FOLDER_SHORT_QNAME, namespacePrefixResolver), false); if (children.size() == 0) { @@ -699,7 +706,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per NodeRef systemNodeRef = children.get(0).getChildRef(); children = nodeService.getChildAssocs(systemNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName( - PEOPLE_FOLDER_SHORT_QNAME, namespacePrefixResolver)); + PEOPLE_FOLDER_SHORT_QNAME, namespacePrefixResolver), false); if (children.size() == 0) { @@ -763,7 +770,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per public Set getAllPeople() { List childRefs = nodeService.getChildAssocs(getPeopleContainer(), - ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL); + ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL, false); Set refs = new HashSet(childRefs.size()*2); for (ChildAssociationRef childRef : childRefs) { diff --git a/source/java/org/alfresco/repo/security/sync/BatchProcessor.java b/source/java/org/alfresco/repo/security/sync/BatchProcessor.java index 3a73b3b9d8..316040c8a9 100644 --- a/source/java/org/alfresco/repo/security/sync/BatchProcessor.java +++ b/source/java/org/alfresco/repo/security/sync/BatchProcessor.java @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.rule.RuleService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEventPublisher; @@ -65,6 +66,9 @@ public class BatchProcessor implements BatchMonitor /** The retrying transaction helper. */ private RetryingTransactionHelper retryingTransactionHelper; + /** The rule service. */ + private RuleService ruleService; + /** The collection. */ private Collection collection; @@ -106,6 +110,8 @@ public class BatchProcessor implements BatchMonitor * * @param retryingTransactionHelper * the retrying transaction helper + * @param ruleService + * the rule service * @param collection * the collection * @param processName @@ -119,11 +125,12 @@ public class BatchProcessor implements BatchMonitor * @param batchSize * the number of entries we process at a time in a transaction */ - public BatchProcessor(RetryingTransactionHelper retryingTransactionHelper, + public BatchProcessor(RetryingTransactionHelper retryingTransactionHelper, RuleService ruleService, ApplicationEventPublisher applicationEventPublisher, Collection collection, String processName, int loggingInterval, int workerThreads, int batchSize) { this.retryingTransactionHelper = retryingTransactionHelper; + this.ruleService = ruleService; this.collection = collection; this.processName = processName; this.loggingInterval = loggingInterval; @@ -272,7 +279,7 @@ public class BatchProcessor implements BatchMonitor // Create a thread pool executor with the specified number of threads and a finite blocking queue of jobs ExecutorService executorService = splitTxns && this.workerThreads > 1 ? new ThreadPoolExecutor( this.workerThreads, this.workerThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue( - this.workerThreads * 10) + this.workerThreads * batchSize * 10) { // Add blocking behaviour to work queue @Override @@ -378,10 +385,10 @@ public class BatchProcessor implements BatchMonitor NumberFormat.getPercentInstance().format( totalResults == 0 ? 1.0F : (float) processed / totalResults)).append(" complete"); } - long duration = System.currentTimeMillis() - startTime.getTime(); + long duration = System.currentTimeMillis() - this.startTime.getTime(); if (duration > 0) { - message.append(". Rate: ").append(processed * 1000 / duration).append(" per second"); + message.append(". Rate: ").append(processed * 1000L / duration).append(" per second"); } message.append(". " + this.totalErrors + " failures detected."); BatchProcessor.logger.info(message); @@ -454,13 +461,13 @@ public class BatchProcessor implements BatchMonitor /** The current entry being processed in the transaction */ private String txnEntryId; - + /** The last error. */ private Throwable txnLastError; - + /** The last error entry id. */ private String txnLastErrorEntryId; - + /* * (non-Javadoc) * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute () @@ -473,7 +480,7 @@ public class BatchProcessor implements BatchMonitor this.txnEntryId = this.worker.getIdentifier(entry); synchronized (BatchProcessor.this) { - BatchProcessor.this.currentEntryId = txnEntryId; + BatchProcessor.this.currentEntryId = this.txnEntryId; } try { @@ -486,11 +493,11 @@ public class BatchProcessor implements BatchMonitor { if (BatchProcessor.logger.isWarnEnabled()) { - BatchProcessor.logger.warn(getProcessName() + ": Failed to process entry \"" + txnEntryId - + "\".", t); + BatchProcessor.logger.warn(getProcessName() + ": Failed to process entry \"" + + this.txnEntryId + "\".", t); } this.txnLastError = t; - this.txnLastErrorEntryId = txnEntryId; + this.txnLastErrorEntryId = this.txnEntryId; this.txnErrors++; } else @@ -508,6 +515,8 @@ public class BatchProcessor implements BatchMonitor */ public void run() { + // Disable rules for this thread + BatchProcessor.this.ruleService.disableRules(); try { BatchProcessor.this.retryingTransactionHelper.doInTransaction(this, false, this.splitTxns); @@ -540,6 +549,12 @@ public class BatchProcessor implements BatchMonitor throw new AlfrescoRuntimeException("Transactional error during " + getProcessName(), t); } } + finally + { + // Re-enable rules + BatchProcessor.this.ruleService.enableRules(); + } + commitProgress(); } @@ -606,6 +621,7 @@ public class BatchProcessor implements BatchMonitor BatchProcessor.this.lastError = this.txnLastError; BatchProcessor.this.lastErrorEntryId = this.txnLastErrorEntryId; } + reset(); } } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index 569393ddfa..a3ab3e8134 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -53,6 +53,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PersonService; @@ -134,6 +135,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl /** The retrying transaction helper. */ private RetryingTransactionHelper retryingTransactionHelper; + /** The rule service. */ + private RuleService ruleService; + /** The job lock service. */ private JobLockService jobLockService; @@ -221,6 +225,17 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl this.retryingTransactionHelper = retryingTransactionHelper; } + /** + * Sets the rule service. + * + * @param ruleService + * the new rule service + */ + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + /** * Sets the job lock service. * @@ -304,6 +319,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl */ public void synchronize(boolean force, boolean splitTxns) { + String lockToken = null; + // Let's ensure all exceptions get logged try { @@ -314,8 +331,16 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl { // If this is an automated sync on startup or scheduled sync, don't even wait around for the lock. // Assume the sync will be completed on another node. - this.jobLockService.getTransactionalLock(ChainingUserRegistrySynchronizer.LOCK_QNAME, - ChainingUserRegistrySynchronizer.LOCK_TTL, 0, 1); + lockToken = this.retryingTransactionHelper.doInTransaction( + new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + return ChainingUserRegistrySynchronizer.this.jobLockService.getLock( + ChainingUserRegistrySynchronizer.LOCK_QNAME, + ChainingUserRegistrySynchronizer.LOCK_TTL, 0, 1); + } + }, false, splitTxns); } else { @@ -382,6 +407,23 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl ChainingUserRegistrySynchronizer.logger.error("Synchronization aborted due to error", e); throw e; } + // Release the lock if necessary + finally + { + if (lockToken != null) + { + final String token = lockToken; + this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + ChainingUserRegistrySynchronizer.this.jobLockService.releaseLock(token, + ChainingUserRegistrySynchronizer.LOCK_QNAME); + return null; + } + }, false, splitTxns); + } + } } /* @@ -458,7 +500,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl final Set zoneSet = getZones(zoneId); long lastModifiedMillis = getMostRecentUpdateTime( - ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId); + ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns); Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) @@ -474,33 +516,22 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } } - // Get current set of known authorities - Set allZoneAuthorities = this.retryingTransactionHelper.doInTransaction( - new RetryingTransactionCallback>() - { - public Set execute() throws Throwable - { - return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId, - null); - } - }, false, splitTxns); - // First, analyze the group structure. Create maps of authorities to their parents for associations to create // and delete. Also deal with 'overlaps' with other zones in the authentication chain. final BatchProcessor groupProcessor = new BatchProcessor( - this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getGroups(lastModified), - zone + " Group Analysis", this.loggingInterval, this.workerThreads, 20); + this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher, userRegistry + .getGroups(lastModified), zone + " Group Analysis", this.loggingInterval, this.workerThreads, + 20); class Analyzer implements Worker { - private final Set allZoneAuthorities; + private final Set allZoneAuthorities = new TreeSet(); private final Set groupsToCreate = new TreeSet(); private final Map> groupAssocsToCreate = new TreeMap>(); private final Map> groupAssocsToDelete = new TreeMap>(); private long latestTime; - public Analyzer(final Set allZoneAuthorities, final long latestTime) + public Analyzer(final long latestTime) { - this.allZoneAuthorities = allZoneAuthorities; this.latestTime = latestTime; } @@ -509,6 +540,11 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl return this.latestTime; } + public Set getAllZoneAuthorities() + { + return this.allZoneAuthorities; + } + public Set getGroupsToCreate() { return this.groupsToCreate; @@ -665,105 +701,129 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } } - Analyzer groupAnalyzer = new Analyzer(allZoneAuthorities, lastModifiedMillis); + Analyzer groupAnalyzer = new Analyzer(lastModifiedMillis); int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns); final Map> groupAssocsToCreate = groupAnalyzer.getGroupAssocsToCreate(); final Map> groupAssocsToDelete = groupAnalyzer.getGroupAssocsToDelete(); - - // Prune our set of authorities according to deletions Set deletionCandidates = null; - if (force) + + // If we got back some groups, we have to cross reference them with the set of known authorities + if (force || !groupAssocsToCreate.isEmpty()) { - deletionCandidates = new TreeSet(allZoneAuthorities); - userRegistry.processDeletions(deletionCandidates); - allZoneAuthorities.removeAll(deletionCandidates); - groupAssocsToCreate.keySet().removeAll(deletionCandidates); - groupAssocsToDelete.keySet().removeAll(deletionCandidates); - } - - // Sort the group associations in depth-first order (root groups first) - Map> sortedGroupAssociations = new LinkedHashMap>(groupAssocsToCreate - .size() * 2); - List authorityPath = new ArrayList(5); - for (String authority : groupAssocsToCreate.keySet()) - { - if (allZoneAuthorities.contains(authority)) - { - authorityPath.add(authority); - visitGroupAssociations(authorityPath, allZoneAuthorities, groupAssocsToCreate, sortedGroupAssociations); - authorityPath.clear(); - } - } - - // Add the groups and their parent associations in depth-first order - final Set groupsToCreate = groupAnalyzer.getGroupsToCreate(); - BatchProcessor>> groupCreator = new BatchProcessor>>( - this.retryingTransactionHelper, this.applicationEventPublisher, sortedGroupAssociations.entrySet(), - zone + " Group Creation and Association", this.loggingInterval, 1, 20); - groupCreator.process(new Worker>>() - { - - public String getIdentifier(Map.Entry> entry) - { - return entry.getKey() + " " + entry.getValue(); - } - - public void process(Map.Entry> entry) throws Throwable - { - Set parents = entry.getValue(); - String child = entry.getKey(); - - if (groupsToCreate.contains(child)) - { - String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child); - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + // Get current set of known authorities + Set allZoneAuthorities = this.retryingTransactionHelper.doInTransaction( + new RetryingTransactionCallback>() { - ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'"); - } - // create the group - ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType - .getAuthorityType(child), groupShortName, groupShortName, zoneSet); - } - if (!parents.isEmpty()) - { - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) - { - for (String groupName : parents) + public Set execute() throws Throwable { - ChainingUserRegistrySynchronizer.logger.debug("Adding '" - + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) - + "' to group '" - + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName) - + "'"); + return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone( + zoneId, null); } - } - ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, child); - } - Set parentsToDelete = groupAssocsToDelete.get(child); - if (parentsToDelete != null && !parentsToDelete.isEmpty()) + }, true, splitTxns); + // Add in those that will be created or moved + allZoneAuthorities.addAll(groupAnalyzer.getAllZoneAuthorities()); + + // Prune our set of authorities according to deletions + if (force) + { + deletionCandidates = new TreeSet(allZoneAuthorities); + userRegistry.processDeletions(deletionCandidates); + allZoneAuthorities.removeAll(deletionCandidates); + groupAssocsToCreate.keySet().removeAll(deletionCandidates); + groupAssocsToDelete.keySet().removeAll(deletionCandidates); + } + + if (!groupAssocsToCreate.isEmpty()) + { + // Sort the group associations in depth-first order (root groups first) + Map> sortedGroupAssociations = new LinkedHashMap>( + groupAssocsToCreate.size() * 2); + List authorityPath = new ArrayList(5); + for (String authority : groupAssocsToCreate.keySet()) { - for (String parent : parentsToDelete) + if (allZoneAuthorities.contains(authority)) { - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + authorityPath.add(authority); + visitGroupAssociations(authorityPath, allZoneAuthorities, groupAssocsToCreate, + sortedGroupAssociations); + authorityPath.clear(); + } + } + + // Add the groups and their parent associations in depth-first order + final Set groupsToCreate = groupAnalyzer.getGroupsToCreate(); + BatchProcessor>> groupCreator = new BatchProcessor>>( + this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher, + sortedGroupAssociations.entrySet(), zone + " Group Creation and Association", + this.loggingInterval, this.workerThreads, 20); + groupCreator.process(new Worker>>() + { + + public String getIdentifier(Map.Entry> entry) + { + return entry.getKey() + " " + entry.getValue(); + } + + public void process(Map.Entry> entry) throws Throwable + { + Set parents = entry.getValue(); + String child = entry.getKey(); + + if (groupsToCreate.contains(child)) { - ChainingUserRegistrySynchronizer.logger - .debug("Removing '" + String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(child); + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + ChainingUserRegistrySynchronizer.logger + .debug("Creating group '" + groupShortName + "'"); + } + // create the group + ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType + .getAuthorityType(child), groupShortName, groupShortName, zoneSet); + } + if (!parents.isEmpty()) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + for (String groupName : parents) + { + ChainingUserRegistrySynchronizer.logger.debug("Adding '" + + ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(child) + + "' to group '" + + ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(groupName) + "'"); + } + } + ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, child); + } + Set parentsToDelete = groupAssocsToDelete.get(child); + if (parentsToDelete != null && !parentsToDelete.isEmpty()) + { + for (String parent : parentsToDelete) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + ChainingUserRegistrySynchronizer.logger.debug("Removing '" + ChainingUserRegistrySynchronizer.this.authorityService .getShortName(child) + "' from group '" + ChainingUserRegistrySynchronizer.this.authorityService .getShortName(parent) + "'"); + } + ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child); + } } - ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child); } - } + }, splitTxns); } - }, splitTxns); + } // Process persons and their parent associations lastModifiedMillis = getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, - zoneId); + zoneId, splitTxns); lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { @@ -778,8 +838,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } } final BatchProcessor personProcessor = new BatchProcessor( - this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getPersons(lastModified), - zone + " User Creation and Association", this.loggingInterval, this.workerThreads, 10); + this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher, userRegistry + .getPersons(lastModified), zone + " User Creation and Association", this.loggingInterval, + this.workerThreads, 10); class PersonWorker implements Worker { private long latestTime; @@ -822,7 +883,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl ChainingUserRegistrySynchronizer.logger.debug("Updating user '" + personName + "'"); } ChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, - personProperties); + personProperties, false); } else { @@ -936,8 +997,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl if (force) { BatchProcessor authorityDeletionProcessor = new BatchProcessor( - this.retryingTransactionHelper, this.applicationEventPublisher, deletionCandidates, zone - + " Authority Deletion", this.loggingInterval, this.workerThreads, 10); + this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher, + deletionCandidates, zone + " Authority Deletion", this.loggingInterval, this.workerThreads, 10); class AuthorityDeleter implements Worker { private int personProcessedCount; @@ -1062,11 +1123,18 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl * the zone id * @return the most recent update time in milliseconds */ - private long getMostRecentUpdateTime(String label, String zoneId) + private long getMostRecentUpdateTime(final String label, final String zoneId, boolean splitTxns) { - Attribute attribute = this.attributeService.getAttribute(ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH - + '/' + label + '/' + zoneId); - return attribute == null ? -1 : attribute.getLongValue(); + return this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + + public Long execute() throws Throwable + { + Attribute attribute = ChainingUserRegistrySynchronizer.this.attributeService + .getAttribute(ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH + '/' + label + '/' + zoneId); + return attribute == null ? -1 : attribute.getLongValue(); + } + }, true, splitTxns); } /** @@ -1144,24 +1212,16 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl { public Object doWork() throws Exception { - return ChainingUserRegistrySynchronizer.this.retryingTransactionHelper - .doInTransaction(new RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - try - { - synchronize(false, true); - } - catch (Exception e) - { - ChainingUserRegistrySynchronizer.logger.warn( - "Failed initial synchronize with user registries", e); - } - return null; - } - }); + try + { + synchronize(false, true); + } + catch (Exception e) + { + ChainingUserRegistrySynchronizer.logger.warn("Failed initial synchronize with user registries", + e); + } + return null; } }, AuthenticationUtil.getSystemUserName()); } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index 0d4940ba14..0927c25d8d 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -171,15 +171,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase { newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5") })); - this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); - return null; - } - }); + this.synchronizer.synchronize(true, true); this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() { @@ -200,7 +192,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase assertExists("Z2", "G7", "U5"); return null; } - }); + }, false, true); } /** @@ -209,22 +201,14 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase * @throws Exception * the exception */ - private void tearDownTestUsersAndGroups() throws Exception + public void tearDownTestUsersAndGroups() throws Exception { // Wipe out everything that was in Z1 and Z2 this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[] {}, new NodeDescription[] {}), new MockUserRegistry("Z1", new NodeDescription[] {}, new NodeDescription[] {}), new MockUserRegistry("Z2", new NodeDescription[] {}, new NodeDescription[] {})); - this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); - return null; - } - }); + this.synchronizer.synchronize(true, true); this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() { @@ -245,7 +229,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase assertNotExists("G7"); return null; } - }); + }, false, true); } /** @@ -348,15 +332,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase { newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U4", "U5") })); - this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); - return null; - } - }); + this.synchronizer.synchronize(true, true); this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() { @@ -378,7 +354,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase assertExists("Z2", "G7"); return null; } - }); + }, false, true); tearDownTestUsersAndGroups(); } @@ -393,15 +369,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase List persons = new ArrayList(new RandomPersonCollection(100)); List groups = new ArrayList(new RandomGroupCollection(100, persons)); this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", persons, groups)); - this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); - return null; - } - }); + this.synchronizer.synchronize(true, true); tearDownTestUsersAndGroups(); } @@ -413,20 +381,20 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase */ public void dontTestAssocs() throws Exception { - this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - List groups = new ArrayList(new RandomGroupCollection(1000, - ChainingUserRegistrySynchronizerTest.this.authorityService.getAllAuthoritiesInZone( - AuthorityService.ZONE_AUTH_EXT_PREFIX + "Z0", null))); - ChainingUserRegistrySynchronizerTest.this.applicationContextManager - .setUserRegistries(new MockUserRegistry("Z0", Collections. emptyList(), groups)); - ; - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); - return null; - } - }); + List groups = this.retryingTransactionHelper.doInTransaction( + new RetryingTransactionCallback>() + { + + public List execute() throws Throwable + { + return new ArrayList(new RandomGroupCollection(1000, + ChainingUserRegistrySynchronizerTest.this.authorityService.getAllAuthoritiesInZone( + AuthorityService.ZONE_AUTH_EXT_PREFIX + "Z0", null))); + } + }, true, true); + ChainingUserRegistrySynchronizerTest.this.applicationContextManager.setUserRegistries(new MockUserRegistry( + "Z0", Collections. emptyList(), groups)); + ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); tearDownTestUsersAndGroups(); } @@ -631,10 +599,9 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase { return this.zoneId; } - - - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set) */ public void processDeletions(Set candidateAuthoritiesForDeletion) @@ -649,7 +616,8 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase } } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date) */ public Collection getGroups(Date modifiedSince) diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index 24b194f08b..824774e44d 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -489,6 +489,13 @@ public class NodeServiceImpl implements NodeService, VersionModel return getChildAssocs(VersionUtil.convertNodeRef(nodeRef), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL); } + + public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, + QNamePattern qnamePattern, boolean preload) throws InvalidNodeRefException + { + return getChildAssocs(nodeRef, typeQNamePattern, qnamePattern); + } + /** * Performs conversion from version store properties to real associations */ diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java index d003ae02fd..6791b90371 100644 --- a/source/java/org/alfresco/service/cmr/repository/NodeService.java +++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java @@ -519,6 +519,32 @@ public interface NodeService QNamePattern qnamePattern) throws InvalidNodeRefException; + /** + * Gets all child associations where the pattern of the association qualified + * name is a match. Using a {@link org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL wildcard} + * for the type and a specific {@link QName qualified name} for the association is + * akin to using the XPath browse expression ./{url}localname in the context of the + * parent node. + * + * @param nodeRef the parent node - usually a container + * @param typeQNamePattern the pattern that the type qualified name of the association must match + * @param qnamePattern the pattern that the qnames of the assocs must match + * @param preload should the nodes be preloaded into the cache? + * @return Returns a list of ChildAssociationRef instances. If the + * node is not a container then the result will be empty. + * @throws InvalidNodeRefException if the node could not be found + * + * @see QName + * @see org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL + */ + @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"nodeRef", "typeQNamePattern", "qnamePattern"}) + public List getChildAssocs( + NodeRef nodeRef, + QNamePattern typeQNamePattern, + QNamePattern qnamePattern, + boolean preload) + throws InvalidNodeRefException; + /** * Retrieve immediate children of a given node where the child nodes are in the given inclusive list * and not in the given exclusive list. diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index f37abb6d1e..d2873424d8 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -137,6 +137,20 @@ public interface PersonService @Auditable(parameters = {"userName", "properties"}) public void setPersonProperties(String userName, Map properties); + + /** + * Set the properties on a person - some of these may be persisted in different locations. + * + * @param userName + * - the user for which the properties should be set. + * @param properties + * - the map of properties to set (as the NodeService) + * @param autoCreate + * should we auto-create the home folder if it doesn't exist? (and configuration allows us to) + */ + @Auditable(parameters = {"userName", "properties", "autoCreate"}) + public void setPersonProperties(String userName, Map properties, boolean autoCreate); + /** * Can this service create, delete and update person information? * diff --git a/source/test-resources/sync-test-context.xml b/source/test-resources/sync-test-context.xml index b5595d2747..3c3e6eb342 100644 --- a/source/test-resources/sync-test-context.xml +++ b/source/test-resources/sync-test-context.xml @@ -18,6 +18,9 @@ + + + @@ -25,7 +28,7 @@ userRegistry - 10 + 100