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
This commit is contained in:
Dave Ward
2009-10-26 15:52:59 +00:00
parent 104d6258a5
commit b8aaa9c372
33 changed files with 1123 additions and 627 deletions

View File

@@ -81,6 +81,7 @@
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-PropertyValueTables.sql</value> <value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-PropertyValueTables.sql</value>
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-AuditTables.sql</value> <value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-AuditTables.sql</value>
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-AvmTables.sql</value> <value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-AvmTables.sql</value>
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-Indexes.sql</value>
</list> </list>
</property> </property>
<property name="validateUpdateScriptPatches"> <property name="validateUpdateScriptPatches">
@@ -103,6 +104,7 @@
<ref bean="patch.db-V3.2-ContentTables" /> <ref bean="patch.db-V3.2-ContentTables" />
<ref bean="patch.db-V3.2-PropertyValueTables" /> <ref bean="patch.db-V3.2-PropertyValueTables" />
<ref bean="patch.db-V3.2-AuditTables" /> <ref bean="patch.db-V3.2-AuditTables" />
<ref bean="patch.db-V3.2-Child-Assoc-QName-CRC" />
</list> </list>
</property> </property>
<property name="postUpdateScriptPatches"> <property name="postUpdateScriptPatches">

View File

@@ -311,7 +311,7 @@
<value>org.alfresco.parentAssocsTransactionalCache</value> <value>org.alfresco.parentAssocsTransactionalCache</value>
</property> </property>
<property name="maxCacheSize"> <property name="maxCacheSize">
<value>10000</value> <value>80000</value>
</property> </property>
</bean> </bean>

View File

@@ -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);

View File

@@ -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'
);

View File

@@ -277,13 +277,13 @@
/> />
<cache <cache
name="org.alfresco.cache.storeAndNodeIdCache" name="org.alfresco.cache.storeAndNodeIdCache"
maxElementsInMemory="50000" maxElementsInMemory="80000"
eternal="true" eternal="true"
overflowToDisk="false" overflowToDisk="false"
/> />
<cache <cache
name="org.alfresco.cache.parentAssocsCache" name="org.alfresco.cache.parentAssocsCache"
maxElementsInMemory="50000" maxElementsInMemory="80000"
eternal="true" eternal="true"
overflowToDisk="false" overflowToDisk="false"
/> />

View File

@@ -537,7 +537,7 @@
<cache <cache
name="org.alfresco.cache.storeAndNodeIdCache" name="org.alfresco.cache.storeAndNodeIdCache"
maxElementsInMemory="50000" maxElementsInMemory="80000"
eternal="true" eternal="true"
overflowToDisk="false"> overflowToDisk="false">
@@ -642,7 +642,7 @@
<cache <cache
name="org.alfresco.cache.parentAssocsCache" name="org.alfresco.cache.parentAssocsCache"
maxElementsInMemory="50000" maxElementsInMemory="80000"
eternal="true" eternal="true"
overflowToDisk="false"> overflowToDisk="false">

View File

@@ -278,10 +278,10 @@ patch.authorityDefaultZonesPatch.result=Unzoned groups and people added to the d
patch.authorityDefaultZonesPatch.users= Adding users to zones ... patch.authorityDefaultZonesPatch.users= Adding users to zones ...
patch.authorityDefaultZonesPatch.groups= Adding groups to zones ... patch.authorityDefaultZonesPatch.groups= Adding groups to zones ...
patch.fixNameCrcValues.description=Fixes name CRC32 values to match UTF-8 encoding. patch.fixNameCrcValues.description=Fixes name and qname 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.result=Fixed CRC32 values for UTF-8 encoding for {0} node child associations. See file {1} for details.
patch.fixNameCrcValues.fixed=Updated CRC32 value for node ID {0}, name ''{1}'': {2} -> {3}. 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 CRC old: {2} \n CRC new: {3} \n Error: {4} 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.description=Add person 'cm:sizeCurrent' property (if missing).
patch.personUsagePatch.result1=Added 'cm:sizeCurrent' property to {0} people that were missing this property. patch.personUsagePatch.result1=Added 'cm:sizeCurrent' property to {0} people that were missing this property.

View File

@@ -121,6 +121,11 @@
<property name="messageSource"> <property name="messageSource">
<ref bean="bootstrapSpacesMessageSource" /> <ref bean="bootstrapSpacesMessageSource" />
</property> </property>
<property name="dependsOn" >
<list>
<ref bean="patch.fixNameCrcValues-2" />
</list>
</property>
</bean> </bean>
<bean id="patch.savedSearchesPermission" class="org.alfresco.repo.admin.patch.impl.SavedSearchPermissionPatch" parent="basePatch" > <bean id="patch.savedSearchesPermission" class="org.alfresco.repo.admin.patch.impl.SavedSearchPermissionPatch" parent="basePatch" >
<property name="id"><value>patch.savedSearchesPermission</value></property> <property name="id"><value>patch.savedSearchesPermission</value></property>
@@ -1790,6 +1795,11 @@
<prop key="location">alfresco/bootstrap/alfrescoAuthorityStore.xml</prop> <prop key="location">alfresco/bootstrap/alfrescoAuthorityStore.xml</prop>
</props> </props>
</property> </property>
<property name="dependsOn" >
<list>
<ref bean="patch.fixNameCrcValues-2" />
</list>
</property>
</bean> </bean>
<bean id="patch.authorityMigration" class="org.alfresco.repo.admin.patch.impl.AuthorityMigrationPatch" parent="basePatch" > <bean id="patch.authorityMigration" class="org.alfresco.repo.admin.patch.impl.AuthorityMigrationPatch" parent="basePatch" >
@@ -1836,30 +1846,6 @@
</property> </property>
</bean> </bean>
<bean id="patch.fixNameCrcValues" class="org.alfresco.repo.admin.patch.impl.FixNameCrcValuesPatch" parent="basePatch" >
<property name="id"><value>patch.fixNameCrcValues</value></property>
<property name="description"><value>patch.fixNameCrcValues.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>2014</value></property>
<property name="targetSchema"><value>2015</value></property>
<property name="dependsOn" >
<list>
<ref bean="patch.uniqueChildName" />
<ref bean="patch.InvalidNameEnding" />
</list>
</property>
<!-- helper beans -->
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
<property name="nodeDaoService">
<ref bean="nodeDaoService" />
</property>
<property name="qnameDAO">
<ref bean="qnameDAO" />
</property>
</bean>
<bean id="patch.db-V3.2-ContentTables" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch"> <bean id="patch.db-V3.2-ContentTables" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
<property name="id"><value>patch.db-V3.2-ContentTables</value></property> <property name="id"><value>patch.db-V3.2-ContentTables</value></property>
<property name="description"><value>patch.schemaUpgradeScript.description</value></property> <property name="description"><value>patch.schemaUpgradeScript.description</value></property>
@@ -1968,4 +1954,45 @@
</property> </property>
</bean> </bean>
<bean id="patch.db-V3.2-Child-Assoc-QName-CRC" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
<property name="id"><value>patch.db-V3.2-Child-Assoc-QName-CRC</value></property>
<property name="description"><value>patch.schemaUpgradeScript.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>3005</value></property>
<property name="targetSchema"><value>3006</value></property>
<property name="scriptUrl">
<value>classpath:alfresco/dbscripts/upgrade/3.2/${db.script.dialect}/child-assoc-qname-crc.sql</value>
</property>
</bean>
<!-- -->
<!-- Patch definitions -->
<!-- -->
<bean id="patch.fixNameCrcValues-2" class="org.alfresco.repo.admin.patch.impl.FixNameCrcValuesPatch" parent="basePatch" >
<property name="id"><value>patch.fixNameCrcValues-2</value></property>
<property name="description"><value>patch.fixNameCrcValues.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>3006</value></property>
<property name="targetSchema"><value>3007</value></property>
<property name="dependsOn" >
<list>
<ref bean="patch.uniqueChildName" />
<ref bean="patch.InvalidNameEnding" />
</list>
</property>
<!-- helper beans -->
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
<property name="nodeDaoService">
<ref bean="nodeDaoService" />
</property>
<property name="qnameDAO">
<ref bean="qnameDAO" />
</property>
<property name="ruleService">
<ref bean="ruleService" />
</property>
</bean>
</beans> </beans>

View File

@@ -1488,34 +1488,6 @@
</props> </props>
</property> </property>
</bean> </bean>
<!-- The public user registry synchronizer. -->
<bean id="userRegistrySynchronizerWriteTxnAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref bean="retryingWriteTxnAdvice"/>
</property>
<property name="mappedNames">
<list>
<value>synchronize</value>
</list>
</property>
</bean>
<bean id="UserRegistrySynchronizer" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.alfresco.repo.security.sync.UserRegistrySynchronizer</value>
</property>
<property name="target">
<ref bean="userRegistrySynchronizer" />
</property>
<property name="interceptorNames">
<list>
<value>userRegistrySynchronizerWriteTxnAdvisor</value>
<!--value>userRegistrySynchronizerReadTxnAdvisor</value-->
<value>checkTxnAdvisor</value>
</list>
</property>
</bean>
<bean id="PublicServiceAccessService" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="PublicServiceAccessService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"> <property name="proxyInterfaces">

View File

@@ -189,7 +189,7 @@ lucene.indexer.writerMinMergeDocs=1000
# #
lucene.indexer.mergerTargetIndexCount=5 lucene.indexer.mergerTargetIndexCount=5
lucene.indexer.mergerTargetOverlayCount=5 lucene.indexer.mergerTargetOverlayCount=5
lucene.indexer.mergerTargetOverlaysBlockingFactor=2 lucene.indexer.mergerTargetOverlaysBlockingFactor=1
lucene.indexer.maxDocsForInMemoryMerge=10000 lucene.indexer.maxDocsForInMemoryMerge=10000
# #
# Other lucene properties # Other lucene properties

View File

@@ -16,7 +16,7 @@
<property name="jobDataAsMap"> <property name="jobDataAsMap">
<map> <map>
<entry key="userRegistrySynchronizer"> <entry key="userRegistrySynchronizer">
<ref bean="UserRegistrySynchronizer" /> <ref bean="userRegistrySynchronizer" />
</entry> </entry>
<entry key="synchronizeChangesOnly"> <entry key="synchronizeChangesOnly">
<value>${synchronization.synchronizeChangesOnly}</value> <value>${synchronization.synchronizeChangesOnly}</value>
@@ -59,6 +59,9 @@
<property name="retryingTransactionHelper"> <property name="retryingTransactionHelper">
<ref bean="retryingTransactionHelper" /> <ref bean="retryingTransactionHelper" />
</property> </property>
<property name="ruleService">
<ref bean="ruleService" />
</property>
<property name="jobLockService"> <property name="jobLockService">
<ref bean="jobLockService" /> <ref bean="jobLockService" />
</property> </property>

View File

@@ -19,4 +19,4 @@ version.build=@build-number@
# Schema number # Schema number
version.schema=3005 version.schema=3007

View File

@@ -43,7 +43,11 @@ import org.alfresco.repo.domain.Node;
import org.alfresco.repo.domain.QNameDAO; import org.alfresco.repo.domain.QNameDAO;
import org.alfresco.repo.domain.hibernate.ChildAssocImpl; import org.alfresco.repo.domain.hibernate.ChildAssocImpl;
import org.alfresco.repo.node.db.NodeDaoService; 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.admin.PatchException;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.namespace.QName;
import org.hibernate.SQLQuery; import org.hibernate.SQLQuery;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
@@ -51,17 +55,19 @@ import org.hibernate.Session;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.type.LongType; import org.hibernate.type.LongType;
import org.hibernate.type.StringType; 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.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
/** /**
* Fixes <a href=https://issues.alfresco.com/jira/browse/ETWOTWO-1133>ETWOTWO-1133</a>. * Fixes <a href=https://issues.alfresco.com/jira/browse/ETWOTWO-1133>ETWOTWO-1133</a>.
* Checks all CRC values for <b>alf_child_assoc.child_node_name_crc</b>. * Checks all CRC values for <b>alf_child_assoc.child_node_name_crc and alf_child_assoc.qname_crc</b>.
* *
* @author Derek Hulley * @author Derek Hulley
* @since V2.2SP4 * @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_SUCCESS = "patch.fixNameCrcValues.result";
private static final String MSG_REWRITTEN = "patch.fixNameCrcValues.fixed"; private static final String MSG_REWRITTEN = "patch.fixNameCrcValues.fixed";
@@ -70,6 +76,8 @@ public class FixNameCrcValuesPatch extends AbstractPatch
private SessionFactory sessionFactory; private SessionFactory sessionFactory;
private NodeDaoService nodeDaoService; private NodeDaoService nodeDaoService;
private QNameDAO qnameDAO; private QNameDAO qnameDAO;
private RuleService ruleService;
private ApplicationEventPublisher applicationEventPublisher;
public FixNameCrcValuesPatch() public FixNameCrcValuesPatch()
{ {
@@ -95,6 +103,22 @@ public class FixNameCrcValuesPatch extends AbstractPatch
{ {
this.qnameDAO = qnameDAO; 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 @Override
protected void checkProperties() protected void checkProperties()
@@ -103,6 +127,7 @@ public class FixNameCrcValuesPatch extends AbstractPatch
checkPropertyNotNull(sessionFactory, "sessionFactory"); checkPropertyNotNull(sessionFactory, "sessionFactory");
checkPropertyNotNull(nodeDaoService, "nodeDaoService"); checkPropertyNotNull(nodeDaoService, "nodeDaoService");
checkPropertyNotNull(qnameDAO, "qnameDAO"); checkPropertyNotNull(qnameDAO, "qnameDAO");
checkPropertyNotNull(applicationEventPublisher, "applicationEventPublisher");
} }
@Override @Override
@@ -111,7 +136,7 @@ public class FixNameCrcValuesPatch extends AbstractPatch
// initialise the helper // initialise the helper
HibernateHelper helper = new HibernateHelper(); HibernateHelper helper = new HibernateHelper();
helper.setSessionFactory(sessionFactory); helper.setSessionFactory(sessionFactory);
try try
{ {
String msg = helper.fixCrcValues(); String msg = helper.fixCrcValues();
@@ -161,66 +186,79 @@ public class FixNameCrcValuesPatch extends AbstractPatch
public String fixCrcValues() throws Exception public String fixCrcValues() throws Exception
{ {
// get the association types to check // get the association types to check
@SuppressWarnings("unused") BatchProcessor<Long> batchProcessor = new BatchProcessor<Long>(transactionService
List<Long> childAssocIds = findMismatchedCrcs(); .getRetryingTransactionHelper(), ruleService, applicationEventPublisher, findMismatchedCrcs(),
"FixNameCrcValuesPatch", 100, 2, 20);
// Precautionary flush and clear so that we have an empty session // Precautionary flush and clear so that we have an empty session
getSession().flush(); getSession().flush();
getSession().clear(); getSession().clear();
int updated = 0;
for (Long childAssocId : childAssocIds) int updated = batchProcessor.process(new Worker<Long>(){
{
ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId); public String getIdentifier(Long entry)
if (assoc == null)
{ {
// Missing now ... return entry.toString();
continue;
} }
// Get the old CRC
long oldCrc = assoc.getChildNodeNameCrc(); public void process(Long childAssocId) throws Throwable
// Get the child node
Node childNode = assoc.getChild();
// Get the name
String childName = (String) nodeDaoService.getNodeProperty(childNode.getId(), ContentModel.PROP_NAME);
if (childName == null)
{ {
childName = childNode.getUuid(); ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId);
} if (assoc == null)
// 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())
{ {
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); // Update the CRCs
} long childCrc = getCrc(childName);
getSession().clear(); long qnameCrc = ChildAssocImpl.getCrc(assoc.getQName(qnameDAO));
// Record
writeLine(I18NUtil.getMessage(MSG_REWRITTEN, childNode.getId(), childName, oldCrc, crc)); // 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); String msg = I18NUtil.getMessage(MSG_SUCCESS, updated, logFile);
return msg; return msg;
} }
@SuppressWarnings("unchecked")
private List<Long> findMismatchedCrcs() throws Exception private List<Long> findMismatchedCrcs() throws Exception
{ {
final Long qnameId = qnameDAO.getOrCreateQName(ContentModel.PROP_NAME).getFirst(); 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.id AS child_assoc_id," +
" ca.child_node_name_crc AS child_assoc_crc," + " ca.child_node_name_crc AS child_assoc_crc," +
" np.string_value AS node_name," + " 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" + " FROM" +
" alf_child_assoc ca" + " 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 n ON (ca.child_node_id = n.id)" +
" JOIN alf_node_properties np on (np.node_id = n.id AND np.qname_id = :qnameId)" + " LEFT OUTER JOIN alf_node_properties np on (np.node_id = n.id AND np.qname_id = :qnameId)" +
""); "");
query.setLong("qnameId", qnameId); query.setLong("qnameId", qnameId);
query.addScalar("child_assoc_id", new LongType()); query.addScalar("child_assoc_id", new LongType());
query.addScalar("child_assoc_crc", new LongType()); query.addScalar("child_assoc_crc", new LongType());
query.addScalar("node_name", new StringType()); query.addScalar("node_name", new StringType());
query.addScalar("node_uuid", 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); return query.scroll(ScrollMode.FORWARD_ONLY);
} }
}; };
@@ -256,21 +300,31 @@ public class FixNameCrcValuesPatch extends AbstractPatch
rs = (ScrollableResults) getHibernateTemplate().execute(callback); rs = (ScrollableResults) getHibernateTemplate().execute(callback);
while (rs.next()) while (rs.next())
{ {
// Compute child name crc
Long assocId = (Long) rs.get(0); 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 name = (String) rs.get(2);
String uuid = (String) rs.get(3); String uuid = (String) rs.get(3);
long utf8Crc = -1L; long utf8ChildCrc;
if (name != null) if (name != null)
{ {
utf8Crc = getCrc(name); utf8ChildCrc = getCrc(name);
} }
else 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 // Check
if (dbCrc != null && utf8Crc == dbCrc.longValue()) if (dbChildCrc != null && utf8ChildCrc == dbChildCrc.longValue() && dbQNameCrc != null && utf8QNameCrc == dbQNameCrc.longValue())
{ {
// It is a match, so ignore // It is a match, so ignore
continue; continue;

View File

@@ -1648,6 +1648,14 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
return result; return result;
} }
public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern,
QNamePattern qnamePattern, boolean preload) throws InvalidNodeRefException
{
return getChildAssocs(nodeRef, typeQNamePattern, qnamePattern);
}
public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, Set<QName> childNodeTypes) public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, Set<QName> childNodeTypes)
{ {
/* /*

View File

@@ -158,6 +158,16 @@ public interface ChildAssoc extends Comparable<ChildAssoc>
*/ */
public void setQnameLocalName(String localName); 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 boolean getIsPrimary();
public void setIsPrimary(boolean isPrimary); public void setIsPrimary(boolean isPrimary);

View File

@@ -25,9 +25,11 @@
package org.alfresco.repo.domain.hibernate; package org.alfresco.repo.domain.hibernate;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.zip.CRC32;
import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.ChildAssoc;
import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.Node;
@@ -50,6 +52,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
private Long typeQNameId; private Long typeQNameId;
private Long qnameNamespaceId; private Long qnameNamespaceId;
private String qnameLocalName; private String qnameLocalName;
private long qnameCrc;
private String childNodeName; private String childNodeName;
private long childNodeNameCrc; private long childNodeNameCrc;
private boolean isPrimary; private boolean isPrimary;
@@ -230,18 +233,36 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
String assocQNameNamespace = qname.getNamespaceURI(); String assocQNameNamespace = qname.getNamespaceURI();
String assocQNameLocalName = qname.getLocalName(); String assocQNameLocalName = qname.getLocalName();
Long assocQNameNamespaceId = qnameDAO.getOrCreateNamespace(assocQNameNamespace).getFirst(); Long assocQNameNamespaceId = qnameDAO.getOrCreateNamespace(assocQNameNamespace).getFirst();
Long assocQNameCrc = getCrc(qname);
// get write lock // get write lock
refWriteLock.lock(); refWriteLock.lock();
try try
{ {
setQnameNamespaceId(assocQNameNamespaceId); setQnameNamespaceId(assocQNameNamespaceId);
setQnameLocalName(assocQNameLocalName); setQnameLocalName(assocQNameLocalName);
setQnameCrc(assocQNameCrc);
} }
finally finally
{ {
refWriteLock.unlock(); 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) public boolean equals(Object obj)
{ {
@@ -290,6 +311,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
.append(", assoc type=").append(typeQNameId) .append(", assoc type=").append(typeQNameId)
.append(", assoc qname ns=").append(qnameNamespaceId) .append(", assoc qname ns=").append(qnameNamespaceId)
.append(", assoc qname localname=").append(qnameLocalName) .append(", assoc qname localname=").append(qnameLocalName)
.append(", assoc qname crc=").append(qnameCrc)
.append(", isPrimary=").append(isPrimary) .append(", isPrimary=").append(isPrimary)
.append("]"); .append("]");
return sb.toString(); return sb.toString();
@@ -461,6 +483,16 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
refWriteLock.unlock(); refWriteLock.unlock();
} }
} }
public long getQnameCrc()
{
return qnameCrc;
}
public void setQnameCrc(long crc)
{
this.qnameCrc = crc;
}
public String getChildNodeName() public String getChildNodeName()
{ {

View File

@@ -185,7 +185,8 @@
<column name="child_node_id" not-null="true"/> <column name="child_node_id" not-null="true"/>
</many-to-one> </many-to-one>
<property name="qnameNamespaceId" column="qname_ns_id" type="long" not-null="true" /> <!-- fk_alf_cass_qnns --> <property name="qnameNamespaceId" column="qname_ns_id" type="long" not-null="true" /> <!-- fk_alf_cass_qnns -->
<property name="qnameLocalName" column="qname_localname" type="string" length="255" not-null="true" index="idx_alf_cass_qnln" /> <property name="qnameLocalName" column="qname_localname" type="string" length="255" not-null="true" />
<property name="qnameCrc" column="qname_crc" type="long" not-null="true"/>
<property name="isPrimary" column="is_primary" /> <property name="isPrimary" column="is_primary" />
<property name="index" column="assoc_index" /> <property name="index" column="assoc_index" />
</class> </class>
@@ -260,14 +261,12 @@
<query name="node.GetParentAssocs"> <query name="node.GetParentAssocs">
select select
assoc, assoc,
parent, parent
child
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent join assoc.parent as parent
join assoc.child as child
where where
child.id = :childId assoc.child.id = :childId
</query> </query>
<query name="node.DeleteParentAssocs"> <query name="node.DeleteParentAssocs">
@@ -317,7 +316,8 @@
assoc.child.id = :childId and assoc.child.id = :childId and
assoc.typeQNameId = :typeQNameId and assoc.typeQNameId = :typeQNameId and
assoc.qnameNamespaceId = :qnameNamespaceId and assoc.qnameNamespaceId = :qnameNamespaceId and
assoc.qnameLocalName = :qnameLocalName assoc.qnameLocalName = :qnameLocalName and
assoc.qnameCrc = :qnameCrc
order by order by
assoc.index, assoc.index,
assoc.id assoc.id
@@ -356,6 +356,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -366,11 +367,10 @@
child.uuid child.uuid
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent
join assoc.child as child join assoc.child as child
join child.store as store join child.store as store
where where
parent.id = :parentId and assoc.parent.id = :parentId and
assoc.typeQNameId = :typeQNameId and assoc.typeQNameId = :typeQNameId and
assoc.childNodeName in (:childNodeNames) assoc.childNodeName in (:childNodeNames)
order by order by
@@ -384,6 +384,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -394,11 +395,10 @@
child.uuid child.uuid
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent
join assoc.child as child join assoc.child as child
join child.store as store join child.store as store
where where
parent.id = :parentId assoc.parent.id = :parentId
order by order by
assoc.index, assoc.index,
assoc.id assoc.id
@@ -410,6 +410,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -420,13 +421,13 @@
child.uuid child.uuid
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent
join assoc.child as child join assoc.child as child
join child.store as store join child.store as store
where where
parent.id = :parentId and assoc.parent.id = :parentId and
assoc.qnameNamespaceId = :qnameNamespaceId and assoc.qnameNamespaceId = :qnameNamespaceId and
assoc.qnameLocalName = :qnameLocalName assoc.qnameLocalName = :qnameLocalName and
assoc.qnameCrc = :qnameCrc
order by order by
assoc.index, assoc.index,
assoc.id assoc.id
@@ -437,6 +438,7 @@
<return-scalar column="type_qname_id" type="long"/> <return-scalar column="type_qname_id" type="long"/>
<return-scalar column="qname_ns_id" type="long"/> <return-scalar column="qname_ns_id" type="long"/>
<return-scalar column="qname_localname" type="string"/> <return-scalar column="qname_localname" type="string"/>
<return-scalar column="qname_crc" type="long"/>
<return-scalar column="child_node_name" type="string"/> <return-scalar column="child_node_name" type="string"/>
<return-scalar column="child_node_name_crc" type="long"/> <return-scalar column="child_node_name_crc" type="long"/>
<return-scalar column="is_primary" type="boolean"/> <return-scalar column="is_primary" type="boolean"/>
@@ -450,6 +452,7 @@
a.type_qname_id, a.type_qname_id,
a.qname_ns_id, a.qname_ns_id,
a.qname_localname, a.qname_localname,
a.qname_crc,
a.child_node_name, a.child_node_name,
a.child_node_name_crc, a.child_node_name_crc,
a.is_primary, a.is_primary,
@@ -471,6 +474,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -481,14 +485,14 @@
child.uuid child.uuid
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent
join assoc.child as child join assoc.child as child
join child.store as store join child.store as store
where where
parent.id = :parentId and assoc.parent.id = :parentId and
assoc.typeQNameId = :typeQNameId and assoc.typeQNameId = :typeQNameId and
assoc.qnameNamespaceId = :qnameNamespaceId and assoc.qnameNamespaceId = :qnameNamespaceId and
assoc.qnameLocalName = :qnameLocalName assoc.qnameLocalName = :qnameLocalName and
assoc.qnameCrc = :qnameCrc
order by order by
assoc.index, assoc.index,
assoc.id assoc.id
@@ -500,6 +504,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -510,11 +515,10 @@
child.uuid child.uuid
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent
join assoc.child as child join assoc.child as child
join child.store as store join child.store as store
where where
parent.id = :parentId and assoc.parent.id = :parentId and
child.typeQNameId in (:childTypeQNameIds) child.typeQNameId in (:childTypeQNameIds)
order by order by
assoc.index, assoc.index,
@@ -527,6 +531,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -537,7 +542,6 @@
child.uuid child.uuid
from from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
join assoc.parent as parent
join assoc.child as child join assoc.child as child
join child.store as store join child.store as store
where where
@@ -554,6 +558,7 @@
assoc.typeQNameId, assoc.typeQNameId,
assoc.qnameNamespaceId, assoc.qnameNamespaceId,
assoc.qnameLocalName, assoc.qnameLocalName,
assoc.qnameCrc,
assoc.childNodeName, assoc.childNodeName,
assoc.childNodeNameCrc, assoc.childNodeNameCrc,
assoc.isPrimary, assoc.isPrimary,
@@ -834,6 +839,7 @@
<return-scalar column="type_qname_id" type="long"/> <return-scalar column="type_qname_id" type="long"/>
<return-scalar column="qname_ns_id" type="long"/> <return-scalar column="qname_ns_id" type="long"/>
<return-scalar column="qname_localname" type="string"/> <return-scalar column="qname_localname" type="string"/>
<return-scalar column="qname_crc" type="long"/>
<return-scalar column="child_node_name" type="string"/> <return-scalar column="child_node_name" type="string"/>
<return-scalar column="child_node_name_crc" type="long"/> <return-scalar column="child_node_name_crc" type="long"/>
<return-scalar column="is_primary" type="boolean"/> <return-scalar column="is_primary" type="boolean"/>
@@ -847,6 +853,7 @@
z1.type_qname_id, z1.type_qname_id,
z1.qname_ns_id, z1.qname_ns_id,
z1.qname_localname, z1.qname_localname,
z1.qname_crc,
z1.child_node_name, z1.child_node_name,
z1.child_node_name_crc, z1.child_node_name_crc,
z1.is_primary, z1.is_primary,

View File

@@ -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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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 * 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 * the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's * 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: * the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing" * 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.AssociationExistsException;
import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef; 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.InvalidChildAssociationRefException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException;
@@ -655,6 +654,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// No recurse // No recurse
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
// Get all the QNames to remove // Get all the QNames to remove
List<QName> assocTypeQNamesToRemove = new ArrayList<QName>(aspectDef.getChildAssociations().keySet()); List<QName> assocTypeQNamesToRemove = new ArrayList<QName>(aspectDef.getChildAssociations().keySet());
@@ -825,8 +829,14 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// No recurse // No recurse
return false; return false;
} }
};
// Get all the QNames to remove public boolean preLoadNodes()
{
return true;
}
};
// Get all the QNames to remove
nodeDaoService.getPrimaryChildAssocs(nodeId, callback); nodeDaoService.getPrimaryChildAssocs(nodeId, callback);
// Each child must be deleted // Each child must be deleted
for (Pair<Long, NodeRef> childNodePair : childNodePairs) for (Pair<Long, NodeRef> childNodePair : childNodePairs)
@@ -951,6 +961,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// No recurse // No recurse
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
nodeDaoService.getChildAssocs(parentNodeId, callback, false); 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. * Filters out any associations if their qname is not a match to the given pattern.
*/ */
public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern) public List<ChildAssociationRef> 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<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern, final boolean preload)
{ {
// Get the node // Get the node
Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef); Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst(); Long nodeId = nodePair.getFirst();
final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(100); final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(100);
abstract class BaseCallback implements NodeDaoService.ChildAssocRefQueryCallback
{
public boolean preLoadNodes()
{
return preload;
}
}
if (qnamePattern instanceof QName) if (qnamePattern instanceof QName)
{ {
// Both explicit QNames // Both explicit QNames
if (typeQNamePattern instanceof QName) if (typeQNamePattern instanceof QName)
{ {
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() NodeDaoService.ChildAssocRefQueryCallback callback = new BaseCallback()
{ {
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair) Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
@@ -1496,7 +1527,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
NodeDaoService.ChildAssocRefQueryCallback callback; NodeDaoService.ChildAssocRefQueryCallback callback;
if (typeQNamePattern.equals(RegexQNamePattern.MATCH_ALL)) if (typeQNamePattern.equals(RegexQNamePattern.MATCH_ALL))
{ {
callback = new NodeDaoService.ChildAssocRefQueryCallback() callback = new BaseCallback()
{ {
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair) Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
@@ -1508,7 +1539,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
} }
else else
{ {
callback = new NodeDaoService.ChildAssocRefQueryCallback() callback = new BaseCallback()
{ {
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair) Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> 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 the type is the wildcard type, and the qname is not a search, then use a shortcut query
if (qnamePattern.equals(RegexQNamePattern.MATCH_ALL)) if (qnamePattern.equals(RegexQNamePattern.MATCH_ALL))
{ {
callback = new NodeDaoService.ChildAssocRefQueryCallback() callback = new BaseCallback()
{ {
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair) Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
@@ -1553,7 +1584,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
else else
{ {
callback = new NodeDaoService.ChildAssocRefQueryCallback() callback = new BaseCallback()
{ {
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair) Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
@@ -1579,7 +1610,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// Local qname is pattern, type name is pattern // Local qname is pattern, type name is pattern
else else
{ {
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() NodeDaoService.ChildAssocRefQueryCallback callback = new BaseCallback()
{ {
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair) Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
@@ -1625,6 +1656,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
results.add(childAssocPair.getSecond()); results.add(childAssocPair.getSecond());
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
// Get all child associations with the specific qualified name // Get all child associations with the specific qualified name
nodeDaoService.getChildAssocsByChildTypes(nodeId, childNodeTypeQNames, callback); nodeDaoService.getChildAssocsByChildTypes(nodeId, childNodeTypeQNames, callback);
@@ -1693,6 +1729,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
results.add(childAssocPair.getSecond()); results.add(childAssocPair.getSecond());
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
// Get all child associations with the specific qualified name // Get all child associations with the specific qualified name
nodeDaoService.getChildAssocs(nodeId, assocTypeQName, childNames, callback); nodeDaoService.getChildAssocs(nodeId, assocTypeQName, childNames, callback);
@@ -1761,6 +1802,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
results.add(childAssocPair.getSecond()); results.add(childAssocPair.getSecond());
return true; return true;
} }
public boolean preLoadNodes()
{
return false;
}
}; };
// Get the child associations that meet the criteria // Get the child associations that meet the criteria
@@ -1838,146 +1884,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
return nodeAssocRefs; return nodeAssocRefs;
} }
/**
* Recursive method used to build up paths from a given node to the root.
* <p>
* Whilst walking up the hierarchy to the root, some nodes may have a <b>root</b> aspect.
* Everytime one of these is encountered, a new path is farmed off, but the method
* continues to walk up the hierarchy.
*
* @param currentNode the node to start from, i.e. the child node to work upwards from
* @param currentPath the path from the current node to the descendent that we started from
* @param completedPaths paths that have reached the root are added to this collection
* @param assocStack the parent-child relationships traversed whilst building the path.
* Used to detected cyclic relationships.
* @param primaryOnly true if only the primary parent association must be traversed.
* If this is true, then the only root is the top level node having no parents.
* @throws CyclicChildRelationshipException
*/
private void prependPaths(
Pair<Long, NodeRef> currentNodePair,
Pair<StoreRef, NodeRef> currentRootNodePair,
Path currentPath,
Collection<Path> completedPaths,
Stack<Long> 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<Long, NodeRef> rootNodePair = nodeDaoService.getRootNode(currentStoreRef);
currentRootNodePair = new Pair<StoreRef, NodeRef>(currentStoreRef, rootNodePair.getSecond());
}
// get the parent associations of the given node
Collection<Pair<Long, ChildAssociationRef>> 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<Long, ChildAssociationRef> 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<Long, NodeRef> 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 #getPaths(NodeRef, boolean)
* @see #prependPaths(Node, Path, Collection, Stack, boolean) * @see #prependPaths(Node, Path, Collection, Stack, boolean)
@@ -2008,7 +1914,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// create storage for touched associations // create storage for touched associations
Stack<Long> assocIdStack = new Stack<Long>(); Stack<Long> assocIdStack = new Stack<Long>();
// call recursive method to sort it out // 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 // check that for the primary only case we have exactly one path
if (primaryOnly && paths.size() != 1) if (primaryOnly && paths.size() != 1)
@@ -2320,6 +2226,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
childNodePairs.add(childNodePair); childNodePairs.add(childNodePair);
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
// We only need to move child nodes that are not already in the same store // We only need to move child nodes that are not already in the same store
nodeDaoService.getPrimaryChildAssocsNotInSameStore(nodeId, callback); nodeDaoService.getPrimaryChildAssocsNotInSameStore(nodeId, callback);
@@ -2392,7 +2303,13 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
childNodePairs.add(childNodePair); childNodePairs.add(childNodePair);
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
nodeDaoService.getPrimaryChildAssocs(nodeId, callback); nodeDaoService.getPrimaryChildAssocs(nodeId, callback);
// Each child must be moved to the same store as the parent // Each child must be moved to the same store as the parent
for (Pair<Long, NodeRef> oldChildNodePair : childNodePairs) for (Pair<Long, NodeRef> oldChildNodePair : childNodePairs)

View File

@@ -29,6 +29,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack;
import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.ChildAssoc;
import org.alfresco.repo.domain.NodeAssoc; 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.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef; 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.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreExistsException;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -243,6 +246,8 @@ public interface NodeDaoService
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> childNodePair Pair<Long, NodeRef> childNodePair
); );
boolean preLoadNodes();
} }
/** /**
@@ -652,4 +657,9 @@ public interface NodeDaoService
@DirtySessionAnnotation(markDirty=false) @DirtySessionAnnotation(markDirty=false)
public Long getMaxTxnCommitTime(); public Long getMaxTxnCommitTime();
@DirtySessionAnnotation(markDirty=false)
public void prependPaths(Pair<Long, NodeRef> currentNodePair, Pair<StoreRef, NodeRef> currentRootNodePair,
Path currentPath, Collection<Path> completedPaths, Stack<Long> assocIdStack, boolean primaryOnly)
throws CyclicChildRelationshipException;
} }

View File

@@ -26,6 +26,7 @@ package org.alfresco.repo.node.db.hibernate;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -41,6 +42,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.zip.CRC32; 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.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData; 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.DuplicateChildNodeNameException;
import org.alfresco.service.cmr.repository.EntityRef; import org.alfresco.service.cmr.repository.EntityRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException;
import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef; 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.StoreExistsException;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; 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) */ /** A cache mapping StoreRef and NodeRef instances to the entity IDs (primary key) */
private SimpleCache<EntityRef, Long> storeAndNodeIdCache; private SimpleCache<EntityRef, Long> storeAndNodeIdCache;
/** A cache for more performant lookups of the parent associations */ /** A cache for more performant lookups of the parent associations */
private SimpleCache<Long, Set<Long>> parentAssocsCache; private SimpleCache<Long, NodeInfo> parentAssocsCache;
private boolean isDebugEnabled = logger.isDebugEnabled(); private boolean isDebugEnabled = logger.isDebugEnabled();
private boolean isDebugParentAssocCacheEnabled = loggerParentAssocsCache.isDebugEnabled(); private boolean isDebugParentAssocCacheEnabled = loggerParentAssocsCache.isDebugEnabled();
@@ -357,7 +361,7 @@ public class HibernateNodeDaoServiceImpl
* *
* @param parentAssocsCache the cache * @param parentAssocsCache the cache
*/ */
public void setParentAssocsCache(SimpleCache<Long, Set<Long>> parentAssocsCache) public void setParentAssocsCache(SimpleCache<Long, NodeInfo> parentAssocsCache)
{ {
this.parentAssocsCache = parentAssocsCache; this.parentAssocsCache = parentAssocsCache;
} }
@@ -646,7 +650,7 @@ public class HibernateNodeDaoServiceImpl
ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId); ChildAssoc assoc = (ChildAssoc) getHibernateTemplate().get(ChildAssocImpl.class, childAssocId);
if (assoc == null) if (assoc == null)
{ {
throw new AlfrescoRuntimeException("ChildAssoc ID " + childAssocId + " is invalid"); throw new ObjectNotFoundException(childAssocId, ChildAssocImpl.class.getName());
} }
return assoc; return assoc;
} }
@@ -737,6 +741,7 @@ public class HibernateNodeDaoServiceImpl
// Add the root aspect // Add the root aspect
Pair<Long, QName> rootAspectQNamePair = qnameDAO.getOrCreateQName(ContentModel.ASPECT_ROOT); Pair<Long, QName> rootAspectQNamePair = qnameDAO.getOrCreateQName(ContentModel.ASPECT_ROOT);
rootNode.getAspects().add(rootAspectQNamePair.getFirst()); rootNode.getAspects().add(rootAspectQNamePair.getFirst());
parentAssocsCache.remove(rootNode.getId());
// Assign permissions to the root node // Assign permissions to the root node
SimpleAccessControlListProperties properties = DMPermissionsDaoComponentImpl.getDefaultProperties(); SimpleAccessControlListProperties properties = DMPermissionsDaoComponentImpl.getDefaultProperties();
@@ -955,8 +960,8 @@ public class HibernateNodeDaoServiceImpl
return; return;
} }
Long nodeId = node.getId(); Long nodeId = node.getId();
Collection<ChildAssoc> parentAssocs = getParentAssocsInternal(nodeId); NodeInfo parentAssocs = getParentAssocsInternal(nodeId);
for (ChildAssoc parentAssoc : parentAssocs) for (ParentAssocInfo parentAssoc : parentAssocs.getParentAssocs().values())
{ {
propagateTimestamps(parentAssoc); propagateTimestamps(parentAssoc);
} }
@@ -1004,7 +1009,6 @@ public class HibernateNodeDaoServiceImpl
} }
} }
public static final String QUERY_UPDATE_AUDITABLE_MODIFIED = "node.UpdateAuditableModified";
public Integer execute() throws Throwable public Integer execute() throws Throwable
{ {
long now = System.currentTimeMillis(); 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 * Ensures that the timestamps are propogated to the parent node of the association, but only
* if the association requires it. * if the association requires it.
*/ */
private void propagateTimestamps(ChildAssoc parentAssoc) private void propagateTimestamps(ParentAssocInfo parentAssocPair)
{ {
// Shortcut // Shortcut
if (!enableTimestampPropagation) if (!enableTimestampPropagation)
{ {
return; return;
} }
QName assocTypeQName = parentAssoc.getTypeQName(qnameDAO); QName assocTypeQName = parentAssocPair.getChildAssociationRef().getTypeQName();
AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName);
if (assocDef == null) if (assocDef == null)
{ {
@@ -1093,7 +1097,7 @@ public class HibernateNodeDaoServiceImpl
propagator = new TimestampPropagator(); propagator = new TimestampPropagator();
AlfrescoTransactionSupport.bindListener(propagator); AlfrescoTransactionSupport.bindListener(propagator);
} }
propagator.addNode(parentAssoc.getParent().getId()); propagator.addNode(parentAssocPair.getParentNodeId());
} }
public Pair<Long, NodeRef> newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException public Pair<Long, NodeRef> newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException
@@ -1165,6 +1169,7 @@ public class HibernateNodeDaoServiceImpl
// Update the node // Update the node
updateNode(nodeId, storeRef, null, null); updateNode(nodeId, storeRef, null, null);
NodeRef nodeRef = node.getNodeRef(); NodeRef nodeRef = node.getNodeRef();
this.parentAssocsCache.remove(nodeId);
return new Pair<Long, NodeRef>(node.getId(), nodeRef); return new Pair<Long, NodeRef>(node.getId(), nodeRef);
} }
@@ -1511,7 +1516,6 @@ public class HibernateNodeDaoServiceImpl
Collections.singletonMap(qname, propertyValue)); Collections.singletonMap(qname, propertyValue));
} }
@SuppressWarnings("unchecked")
public void addNodeProperties(Long nodeId, Map<QName, Serializable> properties) public void addNodeProperties(Long nodeId, Map<QName, Serializable> properties)
{ {
Node node = getNodeNotNull(nodeId); Node node = getNodeNotNull(nodeId);
@@ -1681,6 +1685,11 @@ public class HibernateNodeDaoServiceImpl
// Add them // Add them
Set<Long> nodeAspects = node.getAspects(); Set<Long> nodeAspects = node.getAspects();
nodeAspects.addAll(aspectQNameIds); nodeAspects.addAll(aspectQNameIds);
if (hasNodeAspect(node, ContentModel.ASPECT_ROOT))
{
parentAssocsCache.remove(nodeId);
}
// Record change ID // Record change ID
recordNodeUpdate(node); recordNodeUpdate(node);
@@ -1702,6 +1711,11 @@ public class HibernateNodeDaoServiceImpl
// Remove them // Remove them
Set<Long> nodeAspects = node.getAspects(); Set<Long> nodeAspects = node.getAspects();
nodeAspects.removeAll(aspectQNameIds); nodeAspects.removeAll(aspectQNameIds);
if (aspectQNames.contains(ContentModel.ASPECT_ROOT))
{
parentAssocsCache.remove(nodeId);
}
// Record change ID // Record change ID
recordNodeUpdate(node); recordNodeUpdate(node);
@@ -1719,6 +1733,11 @@ public class HibernateNodeDaoServiceImpl
} }
private boolean hasNodeAspect(Node node, QName aspectQName) private boolean hasNodeAspect(Node node, QName aspectQName)
{
return hasNodeAspect(qnameDAO, node, aspectQName);
}
private static boolean hasNodeAspect(QNameDAO qnameDAO, Node node, QName aspectQName)
{ {
Pair<Long, QName> aspectQNamePair = qnameDAO.getQName(aspectQName); Pair<Long, QName> aspectQNamePair = qnameDAO.getQName(aspectQName);
if (aspectQNamePair == null) if (aspectQNamePair == null)
@@ -2055,30 +2074,24 @@ public class HibernateNodeDaoServiceImpl
childNameUnique.getFirst()); childNameUnique.getFirst());
// Add it to the cache // Add it to the cache
Set<Long> parentAssocIds = parentAssocsCache.get(childNodeId); NodeInfo nodeInfo = parentAssocsCache.get(childNodeId);
if (parentAssocIds == null) if (nodeInfo == null)
{ {
// There isn't an entry in the cache, so go and make one // There isn't an entry in the cache, so go and make one
Collection<ChildAssoc> parentAssocs = getParentAssocsInternal(childNodeId); nodeInfo = getParentAssocsInternal(childNodeId);
parentAssocIds = new HashSet<Long>(3);
for (ChildAssoc childAssoc : parentAssocs)
{
parentAssocIds.add(childAssoc.getId());
}
} }
else else
{ {
// Copy the list when we add to it // Copy the list when we add to it
parentAssocIds = new HashSet<Long>(parentAssocIds); nodeInfo = nodeInfo.addAssoc(assocId, assoc, qnameDAO);
parentAssocIds.add(assocId); parentAssocsCache.put(childNodeId, nodeInfo);
} }
parentAssocsCache.put(childNodeId, parentAssocIds);
if (isDebugParentAssocCacheEnabled) if (isDebugParentAssocCacheEnabled)
{ {
loggerParentAssocsCache.debug("\n" + loggerParentAssocsCache.debug("\n" +
"Parent associations cache - Updating entry: \n" + "Parent associations cache - Updating entry: \n" +
" Node: " + childNodeId + "\n" + " Node: " + childNodeId + "\n" +
" Assocs: " + parentAssocIds); " Assocs: " + nodeInfo.getParentAssocs().keySet());
} }
// If this is a primary association then update the permissions // If this is a primary association then update the permissions
@@ -2294,7 +2307,10 @@ public class HibernateNodeDaoServiceImpl
} }
// Done // Done
return new Pair<Long, ChildAssociationRef>(childAssocId, childAssoc.getChildAssocRef(qnameDAO)); parentAssocsCache.remove(oldChildNode.getId());
parentAssocsCache.remove(childNodeId);
ParentAssocInfo parentAssocInfo = new ParentAssocInfo(childAssoc, qnameDAO);
return new Pair<Long, ChildAssociationRef>(childAssocId, parentAssocInfo.getChildAssociationRef());
} }
/** /**
@@ -2335,6 +2351,11 @@ public class HibernateNodeDaoServiceImpl
childNodeIds.add(childNodePair.getFirst()); childNodeIds.add(childNodePair.getFirst());
return false; return false;
} }
public boolean preLoadNodes()
{
return true;
}
}; };
// Get all child associations with the specific qualified name // Get all child associations with the specific qualified name
getChildAssocs(nodeId, callback, false); 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) public void getChildAssocs(final Long parentNodeId, final ChildAssocRefQueryCallback resultsCallback, final boolean recurse)
{ {
Node parentNode = getNodeNotNull(parentNodeId); Node parentNode = getNodeNotNull(parentNodeId);
@@ -2391,6 +2411,11 @@ public class HibernateNodeDaoServiceImpl
} }
return false; return false;
} }
public boolean preLoadNodes()
{
return resultsCallback.preLoadNodes();
}
}; };
} }
@@ -2431,7 +2456,6 @@ public class HibernateNodeDaoServiceImpl
// Done // Done
} }
@SuppressWarnings("unchecked")
public void getChildAssocs(final Long parentNodeId, final QName assocQName, ChildAssocRefQueryCallback resultsCallback) public void getChildAssocs(final Long parentNodeId, final QName assocQName, ChildAssocRefQueryCallback resultsCallback)
{ {
final Pair<Long, String> assocQNameNamespacePair = qnameDAO.getNamespace(assocQName.getNamespaceURI()); final Pair<Long, String> assocQNameNamespacePair = qnameDAO.getNamespace(assocQName.getNamespaceURI());
@@ -2451,7 +2475,8 @@ public class HibernateNodeDaoServiceImpl
.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME) .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME)
.setLong("parentId", parentNodeId) .setLong("parentId", parentNodeId)
.setLong("qnameNamespaceId", assocQNameNamespacePair.getFirst()) .setLong("qnameNamespaceId", assocQNameNamespacePair.getFirst())
.setString("qnameLocalName", assocQNameLocalName); .setString("qnameLocalName", assocQNameLocalName)
.setLong("qnameCrc", ChildAssocImpl.getCrc(assocQName));
DirtySessionMethodInterceptor.setQueryFlushMode(session, query); DirtySessionMethodInterceptor.setQueryFlushMode(session, query);
return query.scroll(ScrollMode.FORWARD_ONLY); return query.scroll(ScrollMode.FORWARD_ONLY);
} }
@@ -2541,6 +2566,11 @@ public class HibernateNodeDaoServiceImpl
{ {
return resultsCallback.handle(childAssocPair, parentNodePair, childNodePair); return resultsCallback.handle(childAssocPair, parentNodePair, childNodePair);
} }
public boolean preLoadNodes()
{
return resultsCallback.preLoadNodes();
}
}; };
ScrollableResults queryResults = null; ScrollableResults queryResults = null;
@@ -2628,7 +2658,8 @@ public class HibernateNodeDaoServiceImpl
.setLong("parentId", parentNodeId) .setLong("parentId", parentNodeId)
.setLong("typeQNameId", assocTypeQNamePair.getFirst()) .setLong("typeQNameId", assocTypeQNamePair.getFirst())
.setLong("qnameNamespaceId", assocQNameNamespacePair.getFirst()) .setLong("qnameNamespaceId", assocQNameNamespacePair.getFirst())
.setString("qnameLocalName", assocQNameLocalName); .setString("qnameLocalName", assocQNameLocalName)
.setLong("qnameCrc", ChildAssocImpl.getCrc(assocQName));
DirtySessionMethodInterceptor.setQueryFlushMode(session, query); DirtySessionMethodInterceptor.setQueryFlushMode(session, query);
return query.scroll(ScrollMode.FORWARD_ONLY); return query.scroll(ScrollMode.FORWARD_ONLY);
} }
@@ -2816,12 +2847,12 @@ public class HibernateNodeDaoServiceImpl
.setLong("childId", childNodeId) .setLong("childId", childNodeId)
.setLong("typeQNameId", assocTypeQNamePair.getFirst()) .setLong("typeQNameId", assocTypeQNamePair.getFirst())
.setParameter("qnameNamespaceId", assocQNameNamespacePair.getFirst()) .setParameter("qnameNamespaceId", assocQNameNamespacePair.getFirst())
.setParameter("qnameLocalName", assocQNameLocalName); .setParameter("qnameLocalName", assocQNameLocalName)
.setLong("qnameCrc", ChildAssocImpl.getCrc(assocQName));
DirtySessionMethodInterceptor.setQueryFlushMode(session, query); DirtySessionMethodInterceptor.setQueryFlushMode(session, query);
return query.list(); return query.list();
} }
}; };
@SuppressWarnings("unchecked")
List<ChildAssoc> childAssocs = (List<ChildAssoc>) getHibernateTemplate().execute(callback); List<ChildAssoc> childAssocs = (List<ChildAssoc>) getHibernateTemplate().execute(callback);
Pair<Long, ChildAssociationRef> ret = null; Pair<Long, ChildAssociationRef> ret = null;
for (ChildAssoc childAssoc : childAssocs) for (ChildAssoc childAssoc : childAssocs)
@@ -2912,16 +2943,19 @@ public class HibernateNodeDaoServiceImpl
/** /**
* Columns returned are: * Columns returned are:
* <pre> * <pre>
0 assoc.id, 0 assoc.id,
1 assoc.typeQName, 1 assoc.typeQName,
2 assoc.qnameNamespace, 2 assoc.qnameNamespace,
3 assoc.qnameLocalName, 3 assoc.qnameLocalName,
4 assoc.isPrimary, 4 assoc.qnameCrc,
5 assoc.index, 5 assoc.childNodeName
6 child.id, 6 assoc.childNodeNameCrc
7 child.store.key.protocol, 7 assoc.isPrimary,
8 child.store.key.identifier, 8 assoc.index,
9 child.uuid 9 child.id,
10 child.store.key.protocol,
11 child.store.key.identifier,
12 child.uuid
* </pre> * </pre>
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -2942,14 +2976,14 @@ public class HibernateNodeDaoServiceImpl
String assocQNameNamespace = qnameDAO.getNamespace((Long) row[2]).getSecond(); String assocQNameNamespace = qnameDAO.getNamespace((Long) row[2]).getSecond();
String assocQNameLocalName = (String) row[3]; String assocQNameLocalName = (String) row[3];
QName assocQName = QName.createQName(assocQNameNamespace, assocQNameLocalName); QName assocQName = QName.createQName(assocQNameNamespace, assocQNameLocalName);
String assocChildNodeName = (String) row[4]; String assocChildNodeName = (String) row[5];
Long assocChildNodeNameCrc = (Long) row[5]; Long assocChildNodeNameCrc = (Long) row[6];
Boolean assocIsPrimary = (Boolean) row[6]; Boolean assocIsPrimary = (Boolean) row[7];
Integer assocIndex = (Integer) row[7]; Integer assocIndex = (Integer) row[8];
Long childNodeId = (Long) row[8]; Long childNodeId = (Long) row[9];
String childProtocol = (String) row[9]; String childProtocol = (String) row[10];
String childIdentifier = (String) row[10]; String childIdentifier = (String) row[11];
String childUuid = (String) row[11]; String childUuid = (String) row[12];
NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid);
ChildAssociationRef assocRef = new ChildAssociationRef( ChildAssociationRef assocRef = new ChildAssociationRef(
assocTypeQName, assocTypeQName,
@@ -2982,7 +3016,10 @@ public class HibernateNodeDaoServiceImpl
} }
// Cache the nodes // Cache the nodes
cacheNodes(childNodeRefs); if (resultsCallback.preLoadNodes() && !childNodeRefs.isEmpty())
{
cacheNodes(childNodeRefs);
}
// Pass results to callback // Pass results to callback
for (Object[] callbackResult : callbackResults) for (Object[] callbackResult : callbackResults)
@@ -3094,7 +3131,7 @@ public class HibernateNodeDaoServiceImpl
Long nodeId = node.getId(); Long nodeId = node.getId();
storeAndNodeIdCache.put(node.getNodeRef(), nodeId); storeAndNodeIdCache.put(node.getNodeRef(), nodeId);
nodeIds.add(nodeId); nodeIds.add(nodeId);
} }
if (nodeIds.size() == 0) if (nodeIds.size() == 0)
{ {
@@ -3108,20 +3145,17 @@ public class HibernateNodeDaoServiceImpl
criteria.setCacheMode(CacheMode.PUT); criteria.setCacheMode(CacheMode.PUT);
criteria.setFlushMode(FlushMode.MANUAL); criteria.setFlushMode(FlushMode.MANUAL);
List<ChildAssoc> parentAssocs = criteria.list(); List<ChildAssoc> parentAssocs = criteria.list();
Map<Long, List<ChildAssoc>> parentAssocMap = new HashMap<Long, List<ChildAssoc>>(nodeIds.size() * 2);
for (ChildAssoc parentAssoc : parentAssocs) for (ChildAssoc parentAssoc : parentAssocs)
{ {
Long nodeId = parentAssoc.getChild().getId(); Long nodeId = parentAssoc.getChild().getId();
Set<Long> parentAssocsOfNode = parentAssocsCache.get(nodeId); List<ChildAssoc> parentAssocsOfNode = parentAssocMap.get(nodeId);
if (parentAssocsOfNode == null) if (parentAssocsOfNode == null)
{ {
parentAssocsOfNode = new HashSet<Long>(3); parentAssocsOfNode = new ArrayList<ChildAssoc>(3);
parentAssocMap.put(nodeId, parentAssocsOfNode);
} }
else parentAssocsOfNode.add(parentAssoc);
{
parentAssocsOfNode = new HashSet<Long>(parentAssocsOfNode);
}
parentAssocsOfNode.add(parentAssoc.getId());
parentAssocsCache.put(nodeId, parentAssocsOfNode);
if (isDebugParentAssocCacheEnabled) if (isDebugParentAssocCacheEnabled)
{ {
loggerParentAssocsCache.debug("\n" + loggerParentAssocsCache.debug("\n" +
@@ -3130,6 +3164,17 @@ public class HibernateNodeDaoServiceImpl
" Assocs: " + parentAssocsOfNode); " Assocs: " + parentAssocsOfNode);
} }
} }
// Cache NodeInfo for each node
for (Node node : nodeList)
{
Long nodeId = node.getId();
List<ChildAssoc> parentAsssocsOfNode = parentAssocMap.get(nodeId);
if (parentAsssocsOfNode == null)
{
parentAsssocsOfNode = Collections.emptyList();
}
parentAssocsCache.put(nodeId, new NodeInfo(node, qnameDAO, parentAsssocsOfNode));
}
} }
private Collection<Pair<Long, AssociationRef>> convertToAssocRefs(List<NodeAssoc> queryResults) private Collection<Pair<Long, AssociationRef>> convertToAssocRefs(List<NodeAssoc> queryResults)
@@ -3364,19 +3409,18 @@ public class HibernateNodeDaoServiceImpl
Long childNodeId = childNode.getId(); Long childNodeId = childNode.getId();
// Add remove the child association from the cache // Add remove the child association from the cache
Set<Long> oldParentAssocIds = parentAssocsCache.get(childNodeId); NodeInfo oldNodeInfo = parentAssocsCache.get(childNodeId);
if (oldParentAssocIds != null) if (oldNodeInfo != null)
{ {
Set<Long> newParentAssocIds = new HashSet<Long>(oldParentAssocIds); NodeInfo newNodeInfo = oldNodeInfo.removeAssoc(childAssocId);
newParentAssocIds.remove(childAssocId); parentAssocsCache.put(childNodeId, newNodeInfo);
parentAssocsCache.put(childNodeId, newParentAssocIds);
if (this.isDebugParentAssocCacheEnabled) if (this.isDebugParentAssocCacheEnabled)
{ {
loggerParentAssocsCache.debug("\n" + loggerParentAssocsCache.debug("\n" +
"Parent associations cache - Updating entry: \n" + "Parent associations cache - Updating entry: \n" +
" Node: " + childNodeId + "\n" + " Node: " + childNodeId + "\n" +
" Before: " + oldParentAssocIds + "\n" + " Before: " + oldNodeInfo.getParentAssocs().keySet() + "\n" +
" After: " + newParentAssocIds); " After: " + newNodeInfo.getParentAssocs().keySet());
} }
} }
@@ -3404,45 +3448,47 @@ public class HibernateNodeDaoServiceImpl
* @return Returns the parent associations without any interpretation * @return Returns the parent associations without any interpretation
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Collection<ChildAssoc> getParentAssocsInternal(final Long childNodeId) private NodeInfo getParentAssocsInternal(final Long childNodeId)
{ {
List<ChildAssoc> parentAssocs = null;
// First check the cache // First check the cache
Set<Long> parentAssocIds = parentAssocsCache.get(childNodeId); NodeInfo nodeInfo = parentAssocsCache.get(childNodeId);
if (parentAssocIds != null) if (nodeInfo != null)
{ {
if (isDebugParentAssocCacheEnabled) // Let's ensure this ref hasn't become stale due to a concurrent cascade delete
try
{ {
loggerParentAssocsCache.debug("\n" + for (Long assocId : nodeInfo.getParentAssocs().keySet())
"Parent associations cache - Hit: \n" +
" Node: " + childNodeId + "\n" +
" Assocs: " + parentAssocIds);
}
parentAssocs = new ArrayList<ChildAssoc>(parentAssocIds.size());
for (Long parentAssocId : parentAssocIds)
{
ChildAssoc parentAssoc = (ChildAssoc) getSession().get(ChildAssocImpl.class, parentAssocId);
if (parentAssoc == null)
{ {
// The cache is out of date, so just repopulate it getChildAssocNotNull(assocId);
parentAssocs = null;
break;
} }
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 // 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) if (isDebugParentAssocCacheEnabled)
{ {
loggerParentAssocsCache.debug("\n" + loggerParentAssocsCache.debug("\n" +
"Parent associations cache - Miss: \n" + "Parent associations cache - Miss: \n" +
" Node: " + childNodeId + "\n" + " Node: " + childNodeId + "\n" +
" Assocs: " + parentAssocIds); " Assocs: null");
} }
HibernateCallback callback = new HibernateCallback() HibernateCallback callback = new HibernateCallback()
{ {
@@ -3456,29 +3502,163 @@ public class HibernateNodeDaoServiceImpl
} }
}; };
List<Object[]> rows = (List<Object[]>) getHibernateTemplate().execute(callback); List<Object[]> rows = (List<Object[]>) getHibernateTemplate().execute(callback);
parentAssocs = new ArrayList<ChildAssoc>(rows.size());
parentAssocIds = new HashSet<Long>(parentAssocs.size()); nodeInfo = new NodeInfo(node, qnameDAO, rows);
for (Object[] row : rows)
{
ChildAssoc parentAssoc = (ChildAssoc) row[0];
// Populate the results
parentAssocs.add(parentAssoc);
parentAssocIds.add(parentAssoc.getId());
}
// Populate the cache // Populate the cache
parentAssocsCache.put(childNodeId, parentAssocIds); parentAssocsCache.put(childNodeId, nodeInfo);
if (isDebugParentAssocCacheEnabled) if (isDebugParentAssocCacheEnabled)
{ {
loggerParentAssocsCache.debug("\n" + loggerParentAssocsCache.debug("\n" +
"Parent associations cache - Adding entry: \n" + "Parent associations cache - Adding entry: \n" +
" Node: " + childNodeId + "\n" + " Node: " + childNodeId + "\n" +
" Assocs: " + parentAssocIds); " Assocs: " + nodeInfo.getParentAssocs().keySet());
} }
} }
// Done // Done
return parentAssocs; return nodeInfo;
} }
/**
* Recursive method used to build up paths from a given node to the root.
* <p>
* Whilst walking up the hierarchy to the root, some nodes may have a <b>root</b> aspect.
* Everytime one of these is encountered, a new path is farmed off, but the method
* continues to walk up the hierarchy.
*
* @param currentNode the node to start from, i.e. the child node to work upwards from
* @param currentPath the path from the current node to the descendent that we started from
* @param completedPaths paths that have reached the root are added to this collection
* @param assocStack the parent-child relationships traversed whilst building the path.
* Used to detected cyclic relationships.
* @param primaryOnly true if only the primary parent association must be traversed.
* If this is true, then the only root is the top level node having no parents.
* @throws CyclicChildRelationshipException
*/
public void prependPaths(
Pair<Long, NodeRef> currentNodePair,
Pair<StoreRef, NodeRef> currentRootNodePair,
Path currentPath,
Collection<Path> completedPaths,
Stack<Long> 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<Long, NodeRef> rootNodePair = getRootNode(currentStoreRef);
currentRootNodePair = new Pair<StoreRef, NodeRef>(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<Long, ParentAssocInfo> 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<Long, NodeRef> parentNodePair = new Pair<Long, NodeRef>(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} * {@inheritDoc}
* *
@@ -3486,14 +3666,14 @@ public class HibernateNodeDaoServiceImpl
*/ */
public Collection<Pair<Long, ChildAssociationRef>> getParentAssocs(final Long childNodeId) public Collection<Pair<Long, ChildAssociationRef>> getParentAssocs(final Long childNodeId)
{ {
Collection<ChildAssoc> parentAssocs = getParentAssocsInternal(childNodeId); NodeInfo nodeInfo = getParentAssocsInternal(childNodeId);
Map <Long, ParentAssocInfo> parentAssocs = nodeInfo.getParentAssocs();
Collection<Pair<Long, ChildAssociationRef>> ret = new ArrayList<Pair<Long, ChildAssociationRef>>(parentAssocs.size()); Collection<Pair<Long, ChildAssociationRef>> ret = new ArrayList<Pair<Long, ChildAssociationRef>>(parentAssocs.size());
for (ChildAssoc childAssoc : parentAssocs) for (Map.Entry<Long, ParentAssocInfo> entry : parentAssocs.entrySet())
{ {
Long childAssocId = childAssoc.getId(); Pair<Long, ChildAssociationRef> childAssocPair = new Pair<Long, ChildAssociationRef>(entry.getKey(), entry
ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(qnameDAO); .getValue().getChildAssociationRef());
Pair<Long, ChildAssociationRef> childAssocPair = new Pair<Long, ChildAssociationRef>(childAssocId, childAssocRef);
ret.add(childAssocPair); ret.add(childAssocPair);
} }
// Done // Done
@@ -3512,12 +3692,13 @@ public class HibernateNodeDaoServiceImpl
public Pair<Long, ChildAssociationRef> getPrimaryParentAssoc(Long childNodeId) public Pair<Long, ChildAssociationRef> getPrimaryParentAssoc(Long childNodeId)
{ {
// get the assocs pointing to the node // get the assocs pointing to the node
Collection<ChildAssoc> parentAssocs = getParentAssocsInternal(childNodeId); NodeInfo nodeInfo = getParentAssocsInternal(childNodeId);
ChildAssoc primaryAssoc = null; Pair<Long, ChildAssociationRef> primaryAssoc = null;
for (ChildAssoc assoc : parentAssocs) for (Map.Entry<Long, ParentAssocInfo> entry : nodeInfo.getParentAssocs().entrySet())
{ {
ChildAssociationRef assoc = entry.getValue().getChildAssociationRef();
// ignore non-primary assocs // ignore non-primary assocs
if (!assoc.getIsPrimary()) if (!assoc.isPrimary())
{ {
continue; continue;
} }
@@ -3537,7 +3718,7 @@ public class HibernateNodeDaoServiceImpl
} }
} }
} }
primaryAssoc = assoc; primaryAssoc = new Pair<Long, ChildAssociationRef>(entry.getKey(), assoc);
// we keep looping to hunt out data integrity issues // we keep looping to hunt out data integrity issues
} }
// done // done
@@ -3547,7 +3728,7 @@ public class HibernateNodeDaoServiceImpl
} }
else else
{ {
return new Pair<Long, ChildAssociationRef>(primaryAssoc.getId(), primaryAssoc.getChildAssocRef(qnameDAO)); return primaryAssoc;
} }
} }
@@ -4092,7 +4273,6 @@ public class HibernateNodeDaoServiceImpl
} }
} }
@SuppressWarnings("unchecked")
public void getNodesDeletedInOldTxns( public void getNodesDeletedInOldTxns(
final Long minNodeId, final Long minNodeId,
long maxCommitTime, long maxCommitTime,
@@ -4243,7 +4423,6 @@ public class HibernateNodeDaoServiceImpl
return txns; return txns;
} }
@SuppressWarnings("unchecked")
public int getTxnUpdateCount(final long txnId) public int getTxnUpdateCount(final long txnId)
{ {
HibernateCallback callback = new HibernateCallback() HibernateCallback callback = new HibernateCallback()
@@ -4262,7 +4441,6 @@ public class HibernateNodeDaoServiceImpl
return count.intValue(); return count.intValue();
} }
@SuppressWarnings("unchecked")
public int getTxnDeleteCount(final long txnId) public int getTxnDeleteCount(final long txnId)
{ {
HibernateCallback callback = new HibernateCallback() HibernateCallback callback = new HibernateCallback()
@@ -4281,7 +4459,6 @@ public class HibernateNodeDaoServiceImpl
return count.intValue(); return count.intValue();
} }
@SuppressWarnings("unchecked")
public int getTransactionCount() public int getTransactionCount()
{ {
HibernateCallback callback = new HibernateCallback() HibernateCallback callback = new HibernateCallback()
@@ -4669,7 +4846,7 @@ public class HibernateNodeDaoServiceImpl
if (propertyTypeQName.equals(DataTypeDefinition.ANY)) if (propertyTypeQName.equals(DataTypeDefinition.ANY))
{ {
// It is multi-valued if required (we are not in a collection and the property is a new collection) // 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 else
{ {
@@ -4679,7 +4856,7 @@ public class HibernateNodeDaoServiceImpl
// Handle different scenarios. // Handle different scenarios.
// - Do we need to explode a collection? // - Do we need to explode a collection?
// - Does the property allow collections? // - 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 // We are not (yet) processing a collection but the property should be part of a collection
HibernateNodeDaoServiceImpl.addValueToPersistedProperties( HibernateNodeDaoServiceImpl.addValueToPersistedProperties(
@@ -4692,7 +4869,7 @@ public class HibernateNodeDaoServiceImpl
localeDAO, localeDAO,
contentDataDAO); 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 // 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 // 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 // We are either processing collection elements OR the property is not a collection
// Collections of collections are only supported by type d:any // 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( throw new DictionaryException(
"Collections of collections (Serializable) are only supported by type 'd:any': \n" + "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 the property is multi-valued then the output property must be a collection
if (currentPropertyDef != null && currentPropertyDef.isMultiValued()) if (currentPropertyDef != null && currentPropertyDef.isMultiValued())
{ {
if (collapsedValue != null && !(collapsedValue instanceof Collection)) if (collapsedValue != null && !(collapsedValue instanceof Collection<?>))
{ {
// Can't use Collections.singletonList: ETHREEOH-1172 // Can't use Collections.singletonList: ETHREEOH-1172
ArrayList<Serializable> collection = new ArrayList<Serializable>(1); ArrayList<Serializable> collection = new ArrayList<Serializable>(1);
@@ -5059,7 +5236,7 @@ public class HibernateNodeDaoServiceImpl
} }
} }
// Make sure that multi-valued properties are returned as a collection // 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 // Can't use Collections.singletonList: ETHREEOH-1172
ArrayList<Serializable> collection = new ArrayList<Serializable>(1); ArrayList<Serializable> collection = new ArrayList<Serializable>(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<Long, ParentAssocInfo> parentAssocInfo;
public NodeInfo(Node node, QNameDAO qnameDAO, List<? extends Object> parents)
{
this.isRoot = hasNodeAspect(qnameDAO, node, ContentModel.ASPECT_ROOT);
this.isStoreRoot = node.getTypeQName(qnameDAO).equals(ContentModel.TYPE_STOREROOT);
this.parentAssocInfo = new HashMap<Long, ParentAssocInfo>(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<Long, ParentAssocInfo>(copy.parentAssocInfo);
}
public boolean isRoot()
{
return isRoot;
}
public boolean isStoreRoot()
{
return isStoreRoot;
}
public Map<Long, ParentAssocInfo> 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;
}
}
} }

View File

@@ -179,7 +179,7 @@ public abstract class AbstractLuceneIndexerAndSearcherFactory implements LuceneI
private int mergerTargetOverlayCount = 5; private int mergerTargetOverlayCount = 5;
private int mergerTargetOverlaysBlockingFactor = 2; private int mergerTargetOverlaysBlockingFactor = 1;
private int termIndexInterval =IndexWriter.DEFAULT_TERM_INDEX_INTERVAL; private int termIndexInterval =IndexWriter.DEFAULT_TERM_INDEX_INTERVAL;

View File

@@ -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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -677,7 +677,7 @@ public abstract class AbstractLuceneIndexerImpl<T> extends AbstractLuceneBase
{ {
if (commandList.size() > 0) if (commandList.size() > 0)
{ {
Command last = commandList.get(commandList.size() - 1); Command<T> last = commandList.get(commandList.size() - 1);
if ((last.action == command.action) && (last.ref.equals(command.ref))) if ((last.action == command.action) && (last.ref.equals(command.ref)))
{ {
return; return;
@@ -692,7 +692,7 @@ public abstract class AbstractLuceneIndexerImpl<T> extends AbstractLuceneBase
} }
} }
private void purgeCommandList(Command command) private void purgeCommandList(Command<T> command)
{ {
if (command.action == Action.DELETE) if (command.action == Action.DELETE)
{ {
@@ -712,17 +712,27 @@ public abstract class AbstractLuceneIndexerImpl<T> extends AbstractLuceneBase
} }
} }
private void removeFromCommandList(Command command, boolean matchExact) private void removeFromCommandList(Command<T> command, boolean matchExact)
{ {
for (ListIterator<Command<T>> it = commandList.listIterator(commandList.size()); it.hasPrevious(); /**/) for (ListIterator<Command<T>> it = commandList.listIterator(commandList.size()); it.hasPrevious(); /**/)
{ {
Command<T> current = it.previous(); Command<T> current = it.previous();
if (matchExact) if (matchExact)
{ {
if ((current.action == command.action) && (current.ref.equals(command.ref))) if (current.ref.equals(command.ref))
{ {
it.remove(); if ((current.action == command.action))
return; {
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 else
@@ -746,7 +756,7 @@ public abstract class AbstractLuceneIndexerImpl<T> extends AbstractLuceneBase
mainReader = getReader(); mainReader = getReader();
Set<String> forIndex = new LinkedHashSet<String>(); Set<String> forIndex = new LinkedHashSet<String>();
for (Command command : commandList) for (Command<T> command : commandList)
{ {
if (command.action == Action.INDEX) if (command.action == Action.INDEX)
{ {

View File

@@ -323,7 +323,7 @@ public class IndexInfo implements IndexMonitor
private int mergerTargetOverlays = 5; private int mergerTargetOverlays = 5;
private int mergerTargetOverlaysBlockingFactor = 2; private int mergerTargetOverlaysBlockingFactor = 1;
// Common properties for indexers // Common properties for indexers

View File

@@ -251,7 +251,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
if (container != null) if (container != null)
{ {
for (ChildAssociationRef childRef : nodeService.getChildAssocs(container, 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); addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, pattern);
} }
@@ -267,7 +267,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
if (container != null) if (container != null)
{ {
for (ChildAssociationRef childRef : nodeService.getChildAssocs(container, 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, addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type,
pattern); pattern);
@@ -427,7 +427,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
else else
{ {
List<ChildAssociationRef> results = nodeService.getChildAssocs(getAuthorityContainer(), List<ChildAssociationRef> 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(); return results.isEmpty() ? null : results.get(0).getChildRef();
} }
} }
@@ -468,13 +468,13 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
if (systemContainerRef == null) if (systemContainerRef == null)
{ {
NodeRef rootNodeRef = nodeService.getRootNode(this.storeRef); NodeRef rootNodeRef = nodeService.getRootNode(this.storeRef);
List<ChildAssociationRef> results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem); List<ChildAssociationRef> results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem, false);
if (results.size() == 0) if (results.size() == 0)
{ {
throw new AlfrescoRuntimeException("Required system path not found: " + qnameAssocSystem); throw new AlfrescoRuntimeException("Required system path not found: " + qnameAssocSystem);
} }
NodeRef sysNodeRef = results.get(0).getChildRef(); 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) if (results.size() == 0)
{ {
throw new AlfrescoRuntimeException("Required path not found: " + assocQName); throw new AlfrescoRuntimeException("Required path not found: " + assocQName);
@@ -543,7 +543,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
{ {
NodeRef zoneContainerRef = getZoneContainer(); NodeRef zoneContainerRef = getZoneContainer();
QName zoneQName = QName.createQName("cm", zoneName, namespacePrefixResolver); QName zoneQName = QName.createQName("cm", zoneName, namespacePrefixResolver);
List<ChildAssociationRef> results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName); List<ChildAssociationRef> results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName, false);
if (results.isEmpty()) if (results.isEmpty())
{ {
if (create) if (create)
@@ -605,7 +605,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
NodeRef zoneRef = getZone(zoneName); NodeRef zoneRef = getZone(zoneName);
if (zoneRef != null) 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); addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, null);
} }

View File

@@ -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) public void onCreateNode(ChildAssociationRef childAssocRef)
{
if (enableHomeFolderCreationAsPeopleAreCreated)
{
makeHomeFolder(childAssocRef);
}
}
/**
* Find the provider and call.
*/
public void makeHomeFolder(ChildAssociationRef childAssocRef)
{ {
HomeFolderProvider provider = defaultProvider; HomeFolderProvider provider = defaultProvider;
String providerName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(childAssocRef String providerName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(childAssocRef

View File

@@ -350,7 +350,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{ {
List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(getPeopleContainer(), List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(getPeopleContainer(),
ContentModel.ASSOC_CHILDREN, QName.createQName("cm", searchUserName.toLowerCase(), ContentModel.ASSOC_CHILDREN, QName.createQName("cm", searchUserName.toLowerCase(),
namespacePrefixResolver)); namespacePrefixResolver), false);
allRefs = new LinkedHashMap<String, NodeRef>(childRefs.size() * 2); allRefs = new LinkedHashMap<String, NodeRef>(childRefs.size() * 2);
// add to cache // add to cache
personCache.put(cacheKey, allRefs); personCache.put(cacheKey, allRefs);
@@ -569,6 +569,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
} }
public void setPersonProperties(String userName, Map<QName, Serializable> properties) public void setPersonProperties(String userName, Map<QName, Serializable> properties)
{
setPersonProperties(userName, properties, true);
}
public void setPersonProperties(String userName, Map<QName, Serializable> properties, boolean autoCreate)
{ {
NodeRef personNode = getPersonOrNull(userName); NodeRef personNode = getPersonOrNull(userName);
if (personNode == null) if (personNode == null)
@@ -584,7 +589,10 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
} }
else else
{ {
makeHomeFolderIfRequired(personNode); if (autoCreate)
{
makeHomeFolderIfRequired(personNode);
}
String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, ContentModel.PROP_USERNAME)); String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, ContentModel.PROP_USERNAME));
properties.put(ContentModel.PROP_USERNAME, realUserName); properties.put(ContentModel.PROP_USERNAME, realUserName);
} }
@@ -603,7 +611,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{ {
HashMap<QName, Serializable> properties = getDefaultProperties(userName); HashMap<QName, Serializable> properties = getDefaultProperties(userName);
NodeRef person = createPerson(properties); NodeRef person = createPerson(properties);
makeHomeFolderIfRequired(person);
return person; return person;
} }
@@ -619,7 +626,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{ {
public Object execute() throws Throwable public Object execute() throws Throwable
{ {
homeFolderManager.onCreateNode(ref); homeFolderManager.makeHomeFolder(ref);
return null; return null;
} }
}, transactionService.isReadOnly(), false); }, transactionService.isReadOnly(), false);
@@ -688,7 +695,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{ {
NodeRef rootNodeRef = nodeService.getRootNode(tenantService.getName(storeRef)); NodeRef rootNodeRef = nodeService.getRootNode(tenantService.getName(storeRef));
List<ChildAssociationRef> children = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, List<ChildAssociationRef> 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) if (children.size() == 0)
{ {
@@ -699,7 +706,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
NodeRef systemNodeRef = children.get(0).getChildRef(); NodeRef systemNodeRef = children.get(0).getChildRef();
children = nodeService.getChildAssocs(systemNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName( children = nodeService.getChildAssocs(systemNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName(
PEOPLE_FOLDER_SHORT_QNAME, namespacePrefixResolver)); PEOPLE_FOLDER_SHORT_QNAME, namespacePrefixResolver), false);
if (children.size() == 0) if (children.size() == 0)
{ {
@@ -763,7 +770,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
public Set<NodeRef> getAllPeople() public Set<NodeRef> getAllPeople()
{ {
List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(getPeopleContainer(), List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(getPeopleContainer(),
ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL); ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL, false);
Set<NodeRef> refs = new HashSet<NodeRef>(childRefs.size()*2); Set<NodeRef> refs = new HashSet<NodeRef>(childRefs.size()*2);
for (ChildAssociationRef childRef : childRefs) for (ChildAssociationRef childRef : childRefs)
{ {

View File

@@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@@ -65,6 +66,9 @@ public class BatchProcessor<T> implements BatchMonitor
/** The retrying transaction helper. */ /** The retrying transaction helper. */
private RetryingTransactionHelper retryingTransactionHelper; private RetryingTransactionHelper retryingTransactionHelper;
/** The rule service. */
private RuleService ruleService;
/** The collection. */ /** The collection. */
private Collection<T> collection; private Collection<T> collection;
@@ -106,6 +110,8 @@ public class BatchProcessor<T> implements BatchMonitor
* *
* @param retryingTransactionHelper * @param retryingTransactionHelper
* the retrying transaction helper * the retrying transaction helper
* @param ruleService
* the rule service
* @param collection * @param collection
* the collection * the collection
* @param processName * @param processName
@@ -119,11 +125,12 @@ public class BatchProcessor<T> implements BatchMonitor
* @param batchSize * @param batchSize
* the number of entries we process at a time in a transaction * 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<T> collection, String processName, ApplicationEventPublisher applicationEventPublisher, Collection<T> collection, String processName,
int loggingInterval, int workerThreads, int batchSize) int loggingInterval, int workerThreads, int batchSize)
{ {
this.retryingTransactionHelper = retryingTransactionHelper; this.retryingTransactionHelper = retryingTransactionHelper;
this.ruleService = ruleService;
this.collection = collection; this.collection = collection;
this.processName = processName; this.processName = processName;
this.loggingInterval = loggingInterval; this.loggingInterval = loggingInterval;
@@ -272,7 +279,7 @@ public class BatchProcessor<T> implements BatchMonitor
// Create a thread pool executor with the specified number of threads and a finite blocking queue of jobs // 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( ExecutorService executorService = splitTxns && this.workerThreads > 1 ? new ThreadPoolExecutor(
this.workerThreads, this.workerThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>( this.workerThreads, this.workerThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(
this.workerThreads * 10) this.workerThreads * batchSize * 10)
{ {
// Add blocking behaviour to work queue // Add blocking behaviour to work queue
@Override @Override
@@ -378,10 +385,10 @@ public class BatchProcessor<T> implements BatchMonitor
NumberFormat.getPercentInstance().format( NumberFormat.getPercentInstance().format(
totalResults == 0 ? 1.0F : (float) processed / totalResults)).append(" complete"); 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) 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."); message.append(". " + this.totalErrors + " failures detected.");
BatchProcessor.logger.info(message); BatchProcessor.logger.info(message);
@@ -454,13 +461,13 @@ public class BatchProcessor<T> implements BatchMonitor
/** The current entry being processed in the transaction */ /** The current entry being processed in the transaction */
private String txnEntryId; private String txnEntryId;
/** The last error. */ /** The last error. */
private Throwable txnLastError; private Throwable txnLastError;
/** The last error entry id. */ /** The last error entry id. */
private String txnLastErrorEntryId; private String txnLastErrorEntryId;
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute () * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute ()
@@ -473,7 +480,7 @@ public class BatchProcessor<T> implements BatchMonitor
this.txnEntryId = this.worker.getIdentifier(entry); this.txnEntryId = this.worker.getIdentifier(entry);
synchronized (BatchProcessor.this) synchronized (BatchProcessor.this)
{ {
BatchProcessor.this.currentEntryId = txnEntryId; BatchProcessor.this.currentEntryId = this.txnEntryId;
} }
try try
{ {
@@ -486,11 +493,11 @@ public class BatchProcessor<T> implements BatchMonitor
{ {
if (BatchProcessor.logger.isWarnEnabled()) if (BatchProcessor.logger.isWarnEnabled())
{ {
BatchProcessor.logger.warn(getProcessName() + ": Failed to process entry \"" + txnEntryId BatchProcessor.logger.warn(getProcessName() + ": Failed to process entry \""
+ "\".", t); + this.txnEntryId + "\".", t);
} }
this.txnLastError = t; this.txnLastError = t;
this.txnLastErrorEntryId = txnEntryId; this.txnLastErrorEntryId = this.txnEntryId;
this.txnErrors++; this.txnErrors++;
} }
else else
@@ -508,6 +515,8 @@ public class BatchProcessor<T> implements BatchMonitor
*/ */
public void run() public void run()
{ {
// Disable rules for this thread
BatchProcessor.this.ruleService.disableRules();
try try
{ {
BatchProcessor.this.retryingTransactionHelper.doInTransaction(this, false, this.splitTxns); BatchProcessor.this.retryingTransactionHelper.doInTransaction(this, false, this.splitTxns);
@@ -540,6 +549,12 @@ public class BatchProcessor<T> implements BatchMonitor
throw new AlfrescoRuntimeException("Transactional error during " + getProcessName(), t); throw new AlfrescoRuntimeException("Transactional error during " + getProcessName(), t);
} }
} }
finally
{
// Re-enable rules
BatchProcessor.this.ruleService.enableRules();
}
commitProgress(); commitProgress();
} }
@@ -606,6 +621,7 @@ public class BatchProcessor<T> implements BatchMonitor
BatchProcessor.this.lastError = this.txnLastError; BatchProcessor.this.lastError = this.txnLastError;
BatchProcessor.this.lastErrorEntryId = this.txnLastErrorEntryId; BatchProcessor.this.lastErrorEntryId = this.txnLastErrorEntryId;
} }
reset(); reset();
} }
} }

View File

@@ -53,6 +53,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.attributes.AttributeService; 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.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
@@ -134,6 +135,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
/** The retrying transaction helper. */ /** The retrying transaction helper. */
private RetryingTransactionHelper retryingTransactionHelper; private RetryingTransactionHelper retryingTransactionHelper;
/** The rule service. */
private RuleService ruleService;
/** The job lock service. */ /** The job lock service. */
private JobLockService jobLockService; private JobLockService jobLockService;
@@ -221,6 +225,17 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
this.retryingTransactionHelper = retryingTransactionHelper; 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. * Sets the job lock service.
* *
@@ -304,6 +319,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
*/ */
public void synchronize(boolean force, boolean splitTxns) public void synchronize(boolean force, boolean splitTxns)
{ {
String lockToken = null;
// Let's ensure all exceptions get logged // Let's ensure all exceptions get logged
try 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. // 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. // Assume the sync will be completed on another node.
this.jobLockService.getTransactionalLock(ChainingUserRegistrySynchronizer.LOCK_QNAME, lockToken = this.retryingTransactionHelper.doInTransaction(
ChainingUserRegistrySynchronizer.LOCK_TTL, 0, 1); new RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
return ChainingUserRegistrySynchronizer.this.jobLockService.getLock(
ChainingUserRegistrySynchronizer.LOCK_QNAME,
ChainingUserRegistrySynchronizer.LOCK_TTL, 0, 1);
}
}, false, splitTxns);
} }
else else
{ {
@@ -382,6 +407,23 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
ChainingUserRegistrySynchronizer.logger.error("Synchronization aborted due to error", e); ChainingUserRegistrySynchronizer.logger.error("Synchronization aborted due to error", e);
throw e; throw e;
} }
// Release the lock if necessary
finally
{
if (lockToken != null)
{
final String token = lockToken;
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
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<String> zoneSet = getZones(zoneId); final Set<String> zoneSet = getZones(zoneId);
long lastModifiedMillis = getMostRecentUpdateTime( long lastModifiedMillis = getMostRecentUpdateTime(
ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId); ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns);
Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
@@ -474,33 +516,22 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
} }
} }
// Get current set of known authorities
Set<String> allZoneAuthorities = this.retryingTransactionHelper.doInTransaction(
new RetryingTransactionCallback<Set<String>>()
{
public Set<String> 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 // 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. // and delete. Also deal with 'overlaps' with other zones in the authentication chain.
final BatchProcessor<NodeDescription> groupProcessor = new BatchProcessor<NodeDescription>( final BatchProcessor<NodeDescription> groupProcessor = new BatchProcessor<NodeDescription>(
this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getGroups(lastModified), this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher, userRegistry
zone + " Group Analysis", this.loggingInterval, this.workerThreads, 20); .getGroups(lastModified), zone + " Group Analysis", this.loggingInterval, this.workerThreads,
20);
class Analyzer implements Worker<NodeDescription> class Analyzer implements Worker<NodeDescription>
{ {
private final Set<String> allZoneAuthorities; private final Set<String> allZoneAuthorities = new TreeSet<String>();
private final Set<String> groupsToCreate = new TreeSet<String>(); private final Set<String> groupsToCreate = new TreeSet<String>();
private final Map<String, Set<String>> groupAssocsToCreate = new TreeMap<String, Set<String>>(); private final Map<String, Set<String>> groupAssocsToCreate = new TreeMap<String, Set<String>>();
private final Map<String, Set<String>> groupAssocsToDelete = new TreeMap<String, Set<String>>(); private final Map<String, Set<String>> groupAssocsToDelete = new TreeMap<String, Set<String>>();
private long latestTime; private long latestTime;
public Analyzer(final Set<String> allZoneAuthorities, final long latestTime) public Analyzer(final long latestTime)
{ {
this.allZoneAuthorities = allZoneAuthorities;
this.latestTime = latestTime; this.latestTime = latestTime;
} }
@@ -509,6 +540,11 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
return this.latestTime; return this.latestTime;
} }
public Set<String> getAllZoneAuthorities()
{
return this.allZoneAuthorities;
}
public Set<String> getGroupsToCreate() public Set<String> getGroupsToCreate()
{ {
return this.groupsToCreate; 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); int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns);
final Map<String, Set<String>> groupAssocsToCreate = groupAnalyzer.getGroupAssocsToCreate(); final Map<String, Set<String>> groupAssocsToCreate = groupAnalyzer.getGroupAssocsToCreate();
final Map<String, Set<String>> groupAssocsToDelete = groupAnalyzer.getGroupAssocsToDelete(); final Map<String, Set<String>> groupAssocsToDelete = groupAnalyzer.getGroupAssocsToDelete();
// Prune our set of authorities according to deletions
Set<String> deletionCandidates = null; Set<String> 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<String>(allZoneAuthorities); // Get current set of known authorities
userRegistry.processDeletions(deletionCandidates); Set<String> allZoneAuthorities = this.retryingTransactionHelper.doInTransaction(
allZoneAuthorities.removeAll(deletionCandidates); new RetryingTransactionCallback<Set<String>>()
groupAssocsToCreate.keySet().removeAll(deletionCandidates);
groupAssocsToDelete.keySet().removeAll(deletionCandidates);
}
// Sort the group associations in depth-first order (root groups first)
Map<String, Set<String>> sortedGroupAssociations = new LinkedHashMap<String, Set<String>>(groupAssocsToCreate
.size() * 2);
List<String> authorityPath = new ArrayList<String>(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<String> groupsToCreate = groupAnalyzer.getGroupsToCreate();
BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>(
this.retryingTransactionHelper, this.applicationEventPublisher, sortedGroupAssociations.entrySet(),
zone + " Group Creation and Association", this.loggingInterval, 1, 20);
groupCreator.process(new Worker<Map.Entry<String, Set<String>>>()
{
public String getIdentifier(Map.Entry<String, Set<String>> entry)
{
return entry.getKey() + " " + entry.getValue();
}
public void process(Map.Entry<String, Set<String>> entry) throws Throwable
{
Set<String> parents = entry.getValue();
String child = entry.getKey();
if (groupsToCreate.contains(child))
{
String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child);
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
{ {
ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'"); public Set<String> execute() throws Throwable
}
// 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 '" return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(
+ ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) zoneId, null);
+ "' to group '"
+ ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName)
+ "'");
} }
} }, true, splitTxns);
ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, child); // Add in those that will be created or moved
} allZoneAuthorities.addAll(groupAnalyzer.getAllZoneAuthorities());
Set<String> parentsToDelete = groupAssocsToDelete.get(child);
if (parentsToDelete != null && !parentsToDelete.isEmpty()) // Prune our set of authorities according to deletions
if (force)
{
deletionCandidates = new TreeSet<String>(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<String, Set<String>> sortedGroupAssociations = new LinkedHashMap<String, Set<String>>(
groupAssocsToCreate.size() * 2);
List<String> authorityPath = new ArrayList<String>(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<String> groupsToCreate = groupAnalyzer.getGroupsToCreate();
BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>(
this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher,
sortedGroupAssociations.entrySet(), zone + " Group Creation and Association",
this.loggingInterval, this.workerThreads, 20);
groupCreator.process(new Worker<Map.Entry<String, Set<String>>>()
{
public String getIdentifier(Map.Entry<String, Set<String>> entry)
{
return entry.getKey() + " " + entry.getValue();
}
public void process(Map.Entry<String, Set<String>> entry) throws Throwable
{
Set<String> parents = entry.getValue();
String child = entry.getKey();
if (groupsToCreate.contains(child))
{ {
ChainingUserRegistrySynchronizer.logger String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService
.debug("Removing '" .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<String> parentsToDelete = groupAssocsToDelete.get(child);
if (parentsToDelete != null && !parentsToDelete.isEmpty())
{
for (String parent : parentsToDelete)
{
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
{
ChainingUserRegistrySynchronizer.logger.debug("Removing '"
+ ChainingUserRegistrySynchronizer.this.authorityService + ChainingUserRegistrySynchronizer.this.authorityService
.getShortName(child) .getShortName(child)
+ "' from group '" + "' from group '"
+ ChainingUserRegistrySynchronizer.this.authorityService + ChainingUserRegistrySynchronizer.this.authorityService
.getShortName(parent) + "'"); .getShortName(parent) + "'");
}
ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child);
}
} }
ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child);
} }
} }, splitTxns);
} }
}, splitTxns); }
// Process persons and their parent associations // Process persons and their parent associations
lastModifiedMillis = getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, lastModifiedMillis = getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE,
zoneId); zoneId, splitTxns);
lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{ {
@@ -778,8 +838,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
} }
} }
final BatchProcessor<NodeDescription> personProcessor = new BatchProcessor<NodeDescription>( final BatchProcessor<NodeDescription> personProcessor = new BatchProcessor<NodeDescription>(
this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getPersons(lastModified), this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher, userRegistry
zone + " User Creation and Association", this.loggingInterval, this.workerThreads, 10); .getPersons(lastModified), zone + " User Creation and Association", this.loggingInterval,
this.workerThreads, 10);
class PersonWorker implements Worker<NodeDescription> class PersonWorker implements Worker<NodeDescription>
{ {
private long latestTime; private long latestTime;
@@ -822,7 +883,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
ChainingUserRegistrySynchronizer.logger.debug("Updating user '" + personName + "'"); ChainingUserRegistrySynchronizer.logger.debug("Updating user '" + personName + "'");
} }
ChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, ChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName,
personProperties); personProperties, false);
} }
else else
{ {
@@ -936,8 +997,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
if (force) if (force)
{ {
BatchProcessor<String> authorityDeletionProcessor = new BatchProcessor<String>( BatchProcessor<String> authorityDeletionProcessor = new BatchProcessor<String>(
this.retryingTransactionHelper, this.applicationEventPublisher, deletionCandidates, zone this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher,
+ " Authority Deletion", this.loggingInterval, this.workerThreads, 10); deletionCandidates, zone + " Authority Deletion", this.loggingInterval, this.workerThreads, 10);
class AuthorityDeleter implements Worker<String> class AuthorityDeleter implements Worker<String>
{ {
private int personProcessedCount; private int personProcessedCount;
@@ -1062,11 +1123,18 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
* the zone id * the zone id
* @return the most recent update time in milliseconds * @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 return this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Long>()
+ '/' + label + '/' + zoneId); {
return attribute == null ? -1 : attribute.getLongValue();
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 public Object doWork() throws Exception
{ {
return ChainingUserRegistrySynchronizer.this.retryingTransactionHelper try
.doInTransaction(new RetryingTransactionCallback<Object>() {
{ synchronize(false, true);
}
public Object execute() throws Throwable catch (Exception e)
{ {
try ChainingUserRegistrySynchronizer.logger.warn("Failed initial synchronize with user registries",
{ e);
synchronize(false, true); }
} return null;
catch (Exception e)
{
ChainingUserRegistrySynchronizer.logger.warn(
"Failed initial synchronize with user registries", e);
}
return null;
}
});
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }

View File

@@ -171,15 +171,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
{ {
newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5") newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5")
})); }));
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.synchronizer.synchronize(true, true);
{
public Object execute() throws Throwable
{
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
return null;
}
});
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{ {
@@ -200,7 +192,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
assertExists("Z2", "G7", "U5"); assertExists("Z2", "G7", "U5");
return null; return null;
} }
}); }, false, true);
} }
/** /**
@@ -209,22 +201,14 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
* @throws Exception * @throws Exception
* the exception * the exception
*/ */
private void tearDownTestUsersAndGroups() throws Exception public void tearDownTestUsersAndGroups() throws Exception
{ {
// Wipe out everything that was in Z1 and Z2 // Wipe out everything that was in Z1 and Z2
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[] {}, this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[] {},
new NodeDescription[] {}), new MockUserRegistry("Z1", new NodeDescription[] {}, new NodeDescription[] {}), new MockUserRegistry("Z1", new NodeDescription[] {},
new NodeDescription[] {}), new MockUserRegistry("Z2", new NodeDescription[] {}, new NodeDescription[] {}), new MockUserRegistry("Z2", new NodeDescription[] {},
new NodeDescription[] {})); new NodeDescription[] {}));
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.synchronizer.synchronize(true, true);
{
public Object execute() throws Throwable
{
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
return null;
}
});
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{ {
@@ -245,7 +229,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
assertNotExists("G7"); assertNotExists("G7");
return null; 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") newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U4", "U5")
})); }));
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.synchronizer.synchronize(true, true);
{
public Object execute() throws Throwable
{
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
return null;
}
});
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{ {
@@ -378,7 +354,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
assertExists("Z2", "G7"); assertExists("Z2", "G7");
return null; return null;
} }
}); }, false, true);
tearDownTestUsersAndGroups(); tearDownTestUsersAndGroups();
} }
@@ -393,15 +369,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
List<NodeDescription> persons = new ArrayList<NodeDescription>(new RandomPersonCollection(100)); List<NodeDescription> persons = new ArrayList<NodeDescription>(new RandomPersonCollection(100));
List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(100, persons)); List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(100, persons));
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", persons, groups)); this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", persons, groups));
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() this.synchronizer.synchronize(true, true);
{
public Object execute() throws Throwable
{
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
return null;
}
});
tearDownTestUsersAndGroups(); tearDownTestUsersAndGroups();
} }
@@ -413,20 +381,20 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
*/ */
public void dontTestAssocs() throws Exception public void dontTestAssocs() throws Exception
{ {
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>() List<NodeDescription> groups = this.retryingTransactionHelper.doInTransaction(
{ new RetryingTransactionCallback<List<NodeDescription>>()
public Object execute() throws Throwable {
{
List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(1000, public List<NodeDescription> execute() throws Throwable
ChainingUserRegistrySynchronizerTest.this.authorityService.getAllAuthoritiesInZone( {
AuthorityService.ZONE_AUTH_EXT_PREFIX + "Z0", null))); return new ArrayList<NodeDescription>(new RandomGroupCollection(1000,
ChainingUserRegistrySynchronizerTest.this.applicationContextManager ChainingUserRegistrySynchronizerTest.this.authorityService.getAllAuthoritiesInZone(
.setUserRegistries(new MockUserRegistry("Z0", Collections.<NodeDescription> emptyList(), groups)); AuthorityService.ZONE_AUTH_EXT_PREFIX + "Z0", null)));
; }
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); }, true, true);
return null; ChainingUserRegistrySynchronizerTest.this.applicationContextManager.setUserRegistries(new MockUserRegistry(
} "Z0", Collections.<NodeDescription> emptyList(), groups));
}); ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
tearDownTestUsersAndGroups(); tearDownTestUsersAndGroups();
} }
@@ -631,10 +599,9 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
{ {
return this.zoneId; return this.zoneId;
} }
/* (non-Javadoc) /*
* (non-Javadoc)
* @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set) * @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set)
*/ */
public void processDeletions(Set<String> candidateAuthoritiesForDeletion) public void processDeletions(Set<String> 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) * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date)
*/ */
public Collection<NodeDescription> getGroups(Date modifiedSince) public Collection<NodeDescription> getGroups(Date modifiedSince)

View File

@@ -489,6 +489,13 @@ public class NodeServiceImpl implements NodeService, VersionModel
return getChildAssocs(VersionUtil.convertNodeRef(nodeRef), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL); return getChildAssocs(VersionUtil.convertNodeRef(nodeRef), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL);
} }
public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern,
QNamePattern qnamePattern, boolean preload) throws InvalidNodeRefException
{
return getChildAssocs(nodeRef, typeQNamePattern, qnamePattern);
}
/** /**
* Performs conversion from version store properties to <i>real</i> associations * Performs conversion from version store properties to <i>real</i> associations
*/ */

View File

@@ -519,6 +519,32 @@ public interface NodeService
QNamePattern qnamePattern) QNamePattern qnamePattern)
throws InvalidNodeRefException; 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 <b>./{url}localname</b> in the context of the
* parent node.
*
* @param nodeRef the parent node - usually a <b>container</b>
* @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 <code>ChildAssociationRef</code> instances. If the
* node is not a <b>container</b> 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<ChildAssociationRef> 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 * Retrieve immediate children of a given node where the child nodes are in the given inclusive list
* and not in the given exclusive list. * and not in the given exclusive list.

View File

@@ -137,6 +137,20 @@ public interface PersonService
@Auditable(parameters = {"userName", "properties"}) @Auditable(parameters = {"userName", "properties"})
public void setPersonProperties(String userName, Map<QName, Serializable> properties); public void setPersonProperties(String userName, Map<QName, Serializable> 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<QName, Serializable> properties, boolean autoCreate);
/** /**
* Can this service create, delete and update person information? * Can this service create, delete and update person information?
* *

View File

@@ -18,6 +18,9 @@
<property name="retryingTransactionHelper"> <property name="retryingTransactionHelper">
<ref bean="retryingTransactionHelper" /> <ref bean="retryingTransactionHelper" />
</property> </property>
<property name="ruleService">
<ref bean="ruleService" />
</property>
<property name="jobLockService"> <property name="jobLockService">
<ref bean="jobLockService" /> <ref bean="jobLockService" />
</property> </property>
@@ -25,7 +28,7 @@
<value>userRegistry</value> <value>userRegistry</value>
</property> </property>
<property name="loggingInterval"> <property name="loggingInterval">
<value>10</value> <value>100</value>
</property> </property>
</bean> </bean>