diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index d93609a6f3..3ee1a8e72e 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -55,6 +55,8 @@ + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 5bc36f7555..6af89d5c70 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -192,6 +192,7 @@ system.acl.maxPermissionChecks=1000 # The maximum number of filefolder list results system.filefolderservice.defaultListMaxResults=5000 +system.preserve.modificationData=false # Properties to control read permission evaluation for acegi system.readpermissions.optimise=true diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index f87916ef67..a5727e5cb7 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -41,6 +41,7 @@ import org.alfresco.query.PagingResults; import org.alfresco.repo.copy.AbstractBaseCopyService; import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.PermissionCheckedCollection.PermissionCheckedCollectionMixin; @@ -132,8 +133,11 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi private SearchService searchService; private ContentService contentService; private MimetypeService mimetypeService; + private BehaviourFilter behaviourFilter; private NamedObjectRegistry> cannedQueryRegistry; - + + private boolean preserveModificationData = true; + // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) private List systemPaths; @@ -206,8 +210,23 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi { this.defaultListMaxResults = defaultListMaxResults; } - - + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setPreserveModificationData(boolean preserveModificationData) + { + this.preserveModificationData = preserveModificationData; + } + + public boolean isPreserveModificationData() + { + return preserveModificationData; + } + + public void init() { } @@ -1044,7 +1063,23 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi if (isPrimaryParent) { // move the node so that the association moves as well - newAssocRef = nodeService.moveNode(sourceNodeRef, targetParentRef, assocTypeQname, qname); + boolean auditableBehaviorWasDisabled = preserveModificationData && behaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE); + if (auditableBehaviorWasDisabled) + { + behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + + try + { + newAssocRef = nodeService.moveNode(sourceNodeRef, targetParentRef, assocTypeQname, qname); + } + finally + { + if (auditableBehaviorWasDisabled) + { + behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + } } else { diff --git a/source/test-java/org/alfresco/repo/model/ModelTestSuite.java b/source/test-java/org/alfresco/repo/model/ModelTestSuite.java index 55ffe99880..dce37427c6 100644 --- a/source/test-java/org/alfresco/repo/model/ModelTestSuite.java +++ b/source/test-java/org/alfresco/repo/model/ModelTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,6 +20,7 @@ package org.alfresco.repo.model; import org.alfresco.repo.model.filefolder.FileFolderDuplicateChildTest; import org.alfresco.repo.model.filefolder.FileFolderServiceImplTest; +import org.alfresco.repo.model.filefolder.FileFolderServicePropagationTest; import org.alfresco.repo.model.filefolder.HiddenAspectTest; import org.alfresco.repo.model.ml.tools.ContentFilterLanguagesMapTest; import org.alfresco.repo.model.ml.tools.EditionServiceImplTest; @@ -52,6 +53,7 @@ import org.junit.runners.Suite.SuiteClasses; // interceptors which would otherwise confuse things FileFolderServiceImplTest.class, FileFolderDuplicateChildTest.class, + FileFolderServicePropagationTest.class }) public class ModelTestSuite { diff --git a/source/test-java/org/alfresco/repo/model/filefolder/FileFolderServicePropagationTest.java b/source/test-java/org/alfresco/repo/model/filefolder/FileFolderServicePropagationTest.java new file mode 100644 index 0000000000..3d4832e824 --- /dev/null +++ b/source/test-java/org/alfresco/repo/model/filefolder/FileFolderServicePropagationTest.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.model.filefolder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +/** + * @see FileFolderService + * @author Dmitry Velichkevich + */ +public class FileFolderServicePropagationTest extends TestCase +{ + private static final String TEST_USER_NAME = "userx"; + + private static final String TEST_USER_PASSWORD = TEST_USER_NAME; + + private static final String ADMIN_USER_NAME = "admin"; + + + private ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + + private Boolean defaultPreservationValue; + + + private MutableAuthenticationService authenticationService; + + private TransactionService transactionService; + + private FileFolderServiceImpl fileFolderService; + + private PermissionService permissionService; + + private SearchService searchService; + + private NodeService nodeService; + + + private FileInfo testFile; + + private FileInfo testFolder; + + private FileInfo testRootFolder; + + private FileInfo testEmptyFolder; + + + @Before + public void setUp() throws Exception + { + fileFolderService = (FileFolderServiceImpl) applicationContext.getBean("fileFolderService"); + + if (null == defaultPreservationValue) + { + defaultPreservationValue = fileFolderService.isPreserveModificationData(); + } + + ServiceRegistry serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + + authenticationService = serviceRegistry.getAuthenticationService(); + transactionService = serviceRegistry.getTransactionService(); + permissionService = serviceRegistry.getPermissionService(); + searchService = serviceRegistry.getSearchService(); + nodeService = serviceRegistry.getNodeService(); + + testFile = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public FileInfo execute() throws Throwable + { + FileInfo result = AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public FileInfo doWork() throws Exception + { + ResultSet resultSet = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, "PATH:\"/app:company_home\""); + NodeRef companyHome = resultSet.getNodeRef(0); + resultSet.close(); + + StringBuilder name = new StringBuilder("TestRootFolder-").append(System.currentTimeMillis()); + testRootFolder = fileFolderService.create(companyHome, name.toString(), ContentModel.TYPE_FOLDER); + + name = new StringBuilder("TestDocument-").append(System.currentTimeMillis()).append(".txt"); + FileInfo result = fileFolderService.create(testRootFolder.getNodeRef(), name.toString(), ContentModel.TYPE_CONTENT); + ContentWriter writer = fileFolderService.getWriter(result.getNodeRef()); + writer.setEncoding("UTF-8"); + writer.setMimetype("text/plain"); + writer.putContent("Test content named " + result.getName()); + + name = new StringBuilder("TestEmptyFolder-").append(System.currentTimeMillis()); + testEmptyFolder = fileFolderService.create(testRootFolder.getNodeRef(), name.toString(), ContentModel.TYPE_FOLDER); + + name = new StringBuilder("TestFolder-").append(System.currentTimeMillis()); + testFolder = fileFolderService.create(testRootFolder.getNodeRef(), name.toString(), ContentModel.TYPE_FOLDER); + + return result; + } + }, ADMIN_USER_NAME); + + AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + authenticationService.createAuthentication(TEST_USER_NAME, TEST_USER_PASSWORD.toCharArray()); + permissionService.setPermission(testRootFolder.getNodeRef(), TEST_USER_NAME, PermissionService.FULL_CONTROL, true); + return null; + } + }); + + return result; + } + }); + } + + @After + public void tearDown() throws Exception + { + // Resetting to default value... + fileFolderService.setPreserveModificationData(defaultPreservationValue); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + return AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + authenticationService.deleteAuthentication(TEST_USER_NAME); + fileFolderService.delete(testRootFolder.getNodeRef()); + return null; + } + }); + } + }); + } + + + @Test + public void testPreservingPropertiesOfDocumentMnt8109() throws Exception + { + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + // Just stop to wait for the end of... + } + + // Enabling preservation of modification properties data... + fileFolderService.setPreserveModificationData(true); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_NAME); + moveObjectAndAssert(testFile); + return null; + } + }); + } + + @Test + public void testPreservingPropertiesOfFolderMnt8109() throws Exception + { + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + // Just stop to wait for the end of... + } + + // Enabling preservation of modification properties data... + fileFolderService.setPreserveModificationData(true); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_NAME); + moveObjectAndAssert(testFolder); + return null; + } + }); + } + + @Test + public void testPreservingPropertiesOfParentFolderMnt8109() throws Exception + { + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + // Just stop to wait for the end of... + } + + // Enabling preservation of modification properties data... + fileFolderService.setPreserveModificationData(true); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_NAME); + moveObjectAndAssert(testFile); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + FileInfo actualParent = fileFolderService.getFileInfo(getAndAssertSingleParent(testFile)); + assertEquals(testEmptyFolder.getModifiedDate(), actualParent.getModifiedDate()); + assertEquals(testEmptyFolder.getProperties().get(ContentModel.PROP_MODIFIER), actualParent.getProperties().get(ContentModel.PROP_MODIFIER)); + return null; + } + }); + } + + + private void moveObjectAndAssert(FileInfo object) throws FileNotFoundException + { + FileInfo moved = fileFolderService.move(object.getNodeRef(), testEmptyFolder.getNodeRef(), object.getName()); + assertParent(moved, testEmptyFolder); + assertEquals(object.getModifiedDate(), moved.getModifiedDate()); + assertEquals(object.getProperties().get(ContentModel.PROP_MODIFIER), moved.getProperties().get(ContentModel.PROP_MODIFIER)); + } + + + @Test + public void testNotPreservingPropertiesOfDocumentMnt8109() throws Exception + { + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + // Just stop to wait for the end of... + } + + // Disabling preservation of modification properties data... + fileFolderService.setPreserveModificationData(false); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_NAME); + moveObjectAndAssertAbsenceOfPropertiesPreserving(testFile); + return null; + + } + }); + } + + @Test + public void testNotPreservingPropertiesOfFolderMnt8109() throws Exception + { + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + // Just stop to wait for the end of... + } + + // Disabling preservation of modification properties data... + fileFolderService.setPreserveModificationData(false); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_NAME); + moveObjectAndAssertAbsenceOfPropertiesPreserving(testFolder); + return null; + + } + }); + } + + @Test + public void testNotPreservingPropertiesOfParentFolderMnt8109() throws Exception + { + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + // Just stop to wait for the end of... + } + + // Disabling preservation of modification properties data... + fileFolderService.setPreserveModificationData(false); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_NAME); + moveObjectAndAssertAbsenceOfPropertiesPreserving(testFile); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + FileInfo actualParent = fileFolderService.getFileInfo(getAndAssertSingleParent(testFile)); + assertTrue("Modification time difference MUST BE greater or equal than 1 000 milliseconds!", (actualParent.getModifiedDate().getTime() - testEmptyFolder + .getModifiedDate().getTime()) >= 1000); + assertEquals(TEST_USER_NAME, actualParent.getProperties().get(ContentModel.PROP_MODIFIER)); + return null; + } + }); + } + + + private void moveObjectAndAssertAbsenceOfPropertiesPreserving(FileInfo object) throws FileNotFoundException + { + FileInfo moved = fileFolderService.move(object.getNodeRef(), testEmptyFolder.getNodeRef(), object.getName()); + assertParent(moved, testEmptyFolder); + assertTrue("Modification time difference MUST BE greater or equal than 1 000 milliseconds!", (moved.getModifiedDate().getTime() - object.getModifiedDate().getTime()) >= 1000); + assertEquals(TEST_USER_NAME, moved.getProperties().get(ContentModel.PROP_MODIFIER)); + } + + private void assertParent(FileInfo child, FileInfo expectedParent) + { + assertEquals(expectedParent.getNodeRef(), getAndAssertSingleParent(child)); + } + + private NodeRef getAndAssertSingleParent(FileInfo child) + { + List parentAssocs = nodeService.getParentAssocs(child.getNodeRef()); + assertNotNull(("No one parent has been found for " + child.toString()), parentAssocs); + assertEquals(1, parentAssocs.size()); + return parentAssocs.iterator().next().getParentRef(); + } +}