diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java index eab651ee1d..2b280f9352 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java @@ -36,7 +36,9 @@ import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.admin.patch.PatchExecuter; import org.alfresco.repo.batch.BatchProcessor; import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authority.UnknownAuthorityException; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; @@ -180,17 +182,35 @@ public class AuthorityMigrationPatch extends AbstractPatch implements Applicatio parents.add(parentAuthority); assocCount++; } - + // loop over properties Collection members = DefaultTypeConverter.INSTANCE.getCollection(String.class, this.nodeService .getProperty(current, AuthorityMigrationPatch.PROP_MEMBERS)); if (members != null) { + String tenantDomain = null; + if (tenantAdminService.isEnabled()) + { + tenantDomain = tenantAdminService.getCurrentUserDomain(); + } + for (String user : members) { // Believe it or not, some old authorities have null members in them! if (user != null) { + if ((tenantDomain != null) && (! (tenantDomain.equals(TenantService.DEFAULT_DOMAIN)))) + { + if (tenantAdminService.getUserDomain(user).equals(TenantService.DEFAULT_DOMAIN)) + { + if (user.equals(tenantAdminService.getBaseNameUser(AuthenticationUtil.getAdminUserName()))) + { + // MT: workaround for CHK-11393 (eg. EMAIL_CONTRIBUTORS with member "admin" instead of "admin@tenant") + user = tenantAdminService.getDomainUser(user, tenantDomain); + } + } + } + Set propParents = parentAssocs.get(user); if (propParents == null) { @@ -271,7 +291,7 @@ public class AuthorityMigrationPatch extends AbstractPatch implements Applicatio }; // Migrate using 2 threads, 20 authorities per transaction. Log every 100 entries. new BatchProcessor>>(AuthorityMigrationPatch.progress_logger, - this.transactionService.getRetryingTransactionHelper(), this.ruleService, + this.transactionService.getRetryingTransactionHelper(), this.ruleService, this.tenantAdminService, this.applicationEventPublisher, parentAssocs.entrySet(), I18NUtil .getMessage(AuthorityMigrationPatch.MSG_PROCESS_NAME), 100, 2, 20).process(worker, true); } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java index 039f2c4707..61712741ab 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java @@ -181,7 +181,7 @@ public class FixNameCrcValuesPatch extends AbstractPatch implements ApplicationE { // get the association types to check BatchProcessor batchProcessor = new BatchProcessor(logger, transactionService - .getRetryingTransactionHelper(), ruleService, applicationEventPublisher, findMismatchedCrcs(), + .getRetryingTransactionHelper(), ruleService, tenantAdminService, applicationEventPublisher, findMismatchedCrcs(), "FixNameCrcValuesPatch", 1000, 2, 20); // Precautionary flush and clear so that we have an empty session diff --git a/source/java/org/alfresco/repo/batch/BatchProcessor.java b/source/java/org/alfresco/repo/batch/BatchProcessor.java index 9cca3fc266..461512dde5 100644 --- a/source/java/org/alfresco/repo/batch/BatchProcessor.java +++ b/source/java/org/alfresco/repo/batch/BatchProcessor.java @@ -34,6 +34,10 @@ import java.util.concurrent.TimeUnit; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUserService; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -63,6 +67,11 @@ public class BatchProcessor implements BatchMonitor /** The rule service. */ private final RuleService ruleService; + + /** The tenant user service. */ + private final TenantUserService tenantUserService; + + private final String tenantDomain; /** The collection. */ private final Collection collection; @@ -125,18 +134,61 @@ public class BatchProcessor implements BatchMonitor * @param batchSize * the number of entries we process at a time in a transaction */ - public BatchProcessor(Log logger, RetryingTransactionHelper retryingTransactionHelper, RuleService ruleService, + public BatchProcessor(Log logger, RetryingTransactionHelper retryingTransactionHelper, RuleService ruleService, ApplicationEventPublisher applicationEventPublisher, Collection collection, String processName, int loggingInterval, int workerThreads, int batchSize) + { + this(logger, retryingTransactionHelper, ruleService, null, applicationEventPublisher, collection, processName, + loggingInterval, workerThreads, batchSize); + } + + /** + * Instantiates a new batch processor. + * + * @param logger + * the logger to use + * @param retryingTransactionHelper + * the retrying transaction helper + * @param ruleService + * the rule service + * @param tenantUserService + * the tenant user service + * @param collection + * the collection + * @param processName + * the process name + * @param loggingInterval + * the number of entries to process before reporting progress + * @param applicationEventPublisher + * the application event publisher + * @param workerThreads + * the number of worker threads + * @param batchSize + * the number of entries we process at a time in a transaction + */ + public BatchProcessor(Log logger, RetryingTransactionHelper retryingTransactionHelper, RuleService ruleService, + TenantUserService tenantUserService, ApplicationEventPublisher applicationEventPublisher, Collection collection, String processName, + int loggingInterval, int workerThreads, int batchSize) { this.logger = logger; this.retryingTransactionHelper = retryingTransactionHelper; this.ruleService = ruleService; + this.tenantUserService = tenantUserService; this.collection = collection; this.processName = processName; this.loggingInterval = loggingInterval; this.workerThreads = workerThreads; this.batchSize = batchSize; + + if (tenantUserService != null) + { + this.tenantDomain = tenantUserService.getUserDomain(AuthenticationUtil.getRunAsUser()); + } + else + { + this.tenantDomain = TenantService.DEFAULT_DOMAIN; + } + // Let the (enterprise) monitoring side know of our presence applicationEventPublisher.publishEvent(new BatchMonitorEvent(this)); } @@ -313,6 +365,7 @@ public class BatchProcessor implements BatchMonitor { batch = new ArrayList(this.batchSize); } + if (executorService == null) { callback.run(); @@ -551,9 +604,24 @@ public class BatchProcessor implements BatchMonitor { // Disable rules for this thread BatchProcessor.this.ruleService.disableRules(); + + final BatchProcessor.TxnCallback callback = this; try { - BatchProcessor.this.retryingTransactionHelper.doInTransaction(this, false, this.splitTxns); + String systemUser = AuthenticationUtil.getSystemUserName(); + if (tenantUserService != null) + { + systemUser = tenantUserService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); + } + + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + BatchProcessor.this.retryingTransactionHelper.doInTransaction(callback, false, splitTxns); + return null; + } + }, systemUser); } catch (Throwable t) { diff --git a/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java b/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java index 7a55895f60..54d1d636e7 100644 --- a/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java @@ -26,6 +26,8 @@ import java.util.Set; import org.alfresco.repo.domain.contentdata.AbstractContentDataDAOImpl; import org.alfresco.repo.domain.contentdata.ContentDataEntity; import org.alfresco.repo.domain.contentdata.ContentUrlEntity; +import org.alfresco.service.cmr.repository.ContentData; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.ibatis.SqlMapClientTemplate; import com.ibatis.sqlmap.client.event.RowHandler; @@ -202,18 +204,20 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl protected int deleteContentDataEntity(Long id) { // Get the content urls - ContentDataEntity contentDataEntity = getContentDataEntity(id); - // This might be null as there is no constraint ensuring that the node points to a valid ContentData entity - if (contentDataEntity != null) + try { - // Register the content URL for a later orphan-check - String contentUrl = contentDataEntity.getContentUrl(); + ContentData contentData = getContentData(id).getSecond(); + String contentUrl = contentData.getContentUrl(); if (contentUrl != null) { // It has been dereferenced and may be orphaned - we'll check later registerDereferencedContentUrl(contentUrl); } } + catch (DataIntegrityViolationException e) + { + // Doesn't exist. The node doesn't enforce a FK constraint, so we protect against this. + } // Issue the delete statement Map params = new HashMap(11); params.put("id", id); diff --git a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java index 98a2ad729a..84d7704689 100644 --- a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java +++ b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java @@ -32,6 +32,8 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.dictionary.AspectDefinition; @@ -359,26 +361,42 @@ public class IncompleteNodeTagger @Override public void beforeCommit(boolean readOnly) { - Map> nodes = getNodes(); + final Map> nodes = getNodes(); + if (readOnly || nodes == null) + { + // Nothing was touched + return; + } // clear the set out of the transaction // there may be processes that react to the addition/removal of the aspect, // and these will, in turn, lead to further events AlfrescoTransactionSupport.unbindResource(KEY_NODES); - // process each node - for (Map.Entry> entry : nodes.entrySet()) + + // Tag/untag the nodes as 'system' to prevent cm:lockable-related issues (ETHREEOH-3983) + RunAsWork processNodesWork = new RunAsWork() { - if (nodeService.exists(entry.getKey())) + public Void doWork() throws Exception { - processNode(entry.getKey(), entry.getValue()); + // process each node + for (Map.Entry> entry : nodes.entrySet()) + { + if (nodeService.exists(entry.getKey())) + { + processNode(entry.getKey(), entry.getValue()); + } + } + return null; } - } + }; + AuthenticationUtil.runAs(processNodesWork, AuthenticationUtil.getSystemUserName()); } private void processNode(NodeRef nodeRef, Set assocTypes) { - // ignore the node if the marker aspect is already present - boolean isTagged = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE); - + // get the node aspects + Set aspectTypeQNames = nodeService.getAspects(nodeRef); + boolean isTagged = aspectTypeQNames.contains(ContentModel.ASPECT_INCOMPLETE); + // get the node properties Map nodeProperties = nodeService.getProperties(nodeRef); // get the node type @@ -401,8 +419,6 @@ public class IncompleteNodeTagger return; } - // get the node aspects - Set aspectTypeQNames = nodeService.getAspects(nodeRef); for (QName aspectTypeQName : aspectTypeQNames) { // get property definitions for the aspect diff --git a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java index b876eff707..c652b2a0c8 100644 --- a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java +++ b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java @@ -25,14 +25,22 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.model.ContentModel; +import org.alfresco.repo.avm.util.HrefBearingRequestPathNameMatcher; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.PropertyMap; @@ -56,6 +64,8 @@ public class IncompleteNodeTaggerTest extends TestCase private PropertyMap properties; private UserTransaction txn; private AuthenticationComponent authenticationComponent; + private MutableAuthenticationService authenticationService; + private PermissionService permissionService; public void setUp() throws Exception { @@ -71,10 +81,18 @@ public class IncompleteNodeTaggerTest extends TestCase serviceRegistry = (ServiceRegistry) IntegrityTest.ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); nodeService = serviceRegistry.getNodeService(); + authenticationService = serviceRegistry.getAuthenticationService(); + permissionService = serviceRegistry.getPermissionService(); this.authenticationComponent = (AuthenticationComponent)IntegrityTest.ctx.getBean("authenticationComponent"); this.authenticationComponent.setSystemUserAsCurrentUser(); + String user = getName(); + if (!authenticationService.authenticationExists(user)) + { + authenticationService.createAuthentication(user, user.toCharArray()); + } + // begin a transaction TransactionService transactionService = serviceRegistry.getTransactionService(); txn = transactionService.getUserTransaction(); @@ -83,11 +101,20 @@ public class IncompleteNodeTaggerTest extends TestCase if (!nodeService.exists(storeRef)) { nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); + rootNodeRef = nodeService.getRootNode(storeRef); + // Make sure our user can do everything + permissionService.setPermission(rootNodeRef, user, PermissionService.ALL_PERMISSIONS, true); + } + else + { + rootNodeRef = nodeService.getRootNode(storeRef); } - rootNodeRef = nodeService.getRootNode(storeRef); properties = new PropertyMap(); properties.put(IntegrityTest.TEST_PROP_TEXT_C, "abc"); + + // Authenticate as a test-specific user + authenticationComponent.setCurrentUser(user); } public void tearDown() throws Exception @@ -115,10 +142,18 @@ public class IncompleteNodeTaggerTest extends TestCase assertNotNull("IncompleteNodeTagger not created", tagger); } - private void checkTagging(NodeRef nodeRef, boolean mustBeTagged) + private void checkTagging(final NodeRef nodeRef, final boolean mustBeTagged) { tagger.beforeCommit(false); - assertEquals(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE), mustBeTagged); + RunAsWork checkWork = new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE), mustBeTagged); + return null; + } + }; + AuthenticationUtil.runAs(checkWork, AuthenticationUtil.getSystemUserName()); } public void testCreateWithoutProperties() throws Exception @@ -150,4 +185,37 @@ public class IncompleteNodeTaggerTest extends TestCase ); checkTagging(nodeRef, false); } + + /** + * ETHREEOH-3983 + */ + public void testIncompleteLockedNode() throws Exception + { + LockService lockService = serviceRegistry.getLockService(); + + NodeRef nodeRef = createNode("abc", IntegrityTest.TEST_TYPE_WITH_PROPERTIES, null); + checkTagging(nodeRef, true); + // Now remove the aspect, lock the node and check again + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE); + lockService.lock(nodeRef, LockType.READ_ONLY_LOCK); + + // Authenticate as someone else - someone not able to do anything + final String user = "someuser"; + RunAsWork createUserWork = new RunAsWork() + { + public Void doWork() throws Exception + { + if (!authenticationService.authenticationExists(user)) + { + authenticationService.createAuthentication(user, user.toCharArray()); + } + return null; + } + }; + AuthenticationUtil.runAs(createUserWork, AuthenticationUtil.getSystemUserName()); + authenticationComponent.setCurrentUser(user); + + // Tag + checkTagging(nodeRef, true); + } }