diff --git a/config/alfresco/cmis-api-context.xml b/config/alfresco/cmis-api-context.xml index a432110c34..6758257f5d 100644 --- a/config/alfresco/cmis-api-context.xml +++ b/config/alfresco/cmis-api-context.xml @@ -30,8 +30,6 @@ - - diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index 8a91242584..dff0615249 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -238,7 +238,13 @@ MEDIUM false - + + + \.smbdelete.* + 20000 + HIGH + + diff --git a/source/java/org/alfresco/cmis/mapping/BaseCMISTest.java b/source/java/org/alfresco/cmis/mapping/BaseCMISTest.java index 31f3eaf4ba..33fee752ef 100644 --- a/source/java/org/alfresco/cmis/mapping/BaseCMISTest.java +++ b/source/java/org/alfresco/cmis/mapping/BaseCMISTest.java @@ -32,17 +32,20 @@ import org.alfresco.cmis.CMISRenditionService; import org.alfresco.cmis.CMISServices; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.NamespaceDAOImpl; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.security.permissions.impl.ModelDAO; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.ContentService; 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.rule.RuleService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PermissionService; @@ -58,61 +61,39 @@ import org.springframework.context.ApplicationContext; * Basic TX control and authentication * * @author andyh - * */ public abstract class BaseCMISTest extends TestCase { private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); protected CMISMapping cmisMapping; - protected CMISServices cmisService; - protected CMISDictionaryService cmisDictionaryService; - protected CMISRenditionService cmisRenditionService; - protected CMISAccessControlService cmisAccessControlService; - protected DictionaryService dictionaryService; - protected TransactionService transactionService; - protected AuthenticationComponent authenticationComponent; - protected UserTransaction testTX; - protected NodeService nodeService; - protected NodeRef rootNodeRef; - protected FileFolderService fileFolderService; - protected ServiceRegistry serviceRegistry; - protected NamespaceService namespaceService; - protected CMISQueryService cmisQueryService; - private MutableAuthenticationService authenticationService; - private MutableAuthenticationDao authenticationDAO; - protected SearchService searchService; - protected ContentService contentService; - protected PermissionService permissionService; - protected ThumbnailService thumbnailService; - protected ModelDAO permissionModelDao; - protected DictionaryDAO dictionaryDAO; - protected NamespaceDAOImpl namespaceDao; - protected VersionService versionService; + protected ActionService actionService; + protected RuleService ruleService; + protected Repository repositoryHelper; public void setUp() throws Exception { @@ -140,6 +121,11 @@ public abstract class BaseCMISTest extends TestCase versionService = (VersionService) ctx.getBean("versionService"); + actionService = (ActionService)ctx.getBean("actionService"); + ruleService = (RuleService)ctx.getBean("ruleService"); + + repositoryHelper = (Repository)ctx.getBean("repositoryHelper"); + authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao"); diff --git a/source/java/org/alfresco/cmis/mapping/CMISPropertyServiceTest.java b/source/java/org/alfresco/cmis/mapping/CMISPropertyServiceTest.java index afbbfab2cf..1e4854cab0 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISPropertyServiceTest.java +++ b/source/java/org/alfresco/cmis/mapping/CMISPropertyServiceTest.java @@ -19,19 +19,29 @@ package org.alfresco.cmis.mapping; import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; +import org.alfresco.cmis.CMISConstraintException; import org.alfresco.cmis.CMISDictionaryModel; import org.alfresco.cmis.CMISInvalidArgumentException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.rule.Rule; +import org.alfresco.service.cmr.rule.RuleType; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionType; import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; @@ -774,4 +784,136 @@ public class CMISPropertyServiceTest extends BaseCMISTest String mimetype = (String) cmisService.getProperty(content, CMISDictionaryModel.PROP_CONTENT_STREAM_MIME_TYPE); assertTrue("Mimetype is not defined correctly.", mimetype.equals(MimetypeMap.MIMETYPE_HTML)); } + + /** + * Test for ALF-18151. + */ + public void testDeleteFolder() throws Exception + { + Map testFolderMap = new HashMap(4); + + try + { + // create folder with file + String folderName = "cmistestfolder" + GUID.generate(); + String docName = "cmistestdoc.txt" + GUID.generate(); + FileInfo folder = createContent(folderName, docName, false); + testFolderMap.put(folder, Boolean.FALSE); + + // create empty folder + String folderNameEmpty = "cmistestfolder_empty1" + GUID.generate(); + FileInfo folderEmpty = createContent(folderNameEmpty, null, false); + testFolderMap.put(folderEmpty, Boolean.TRUE); + + // create folder with file + String folderNameRule = "cmistestfolde_rule" + GUID.generate(); + String docNameRule = "cmistestdoc_rule.txt" + GUID.generate(); + FileInfo folderWithRule = createContent(folderNameRule, docNameRule, true); + testFolderMap.put(folderWithRule, Boolean.FALSE); + + // create empty folder + String folderNameEmptyRule = "cmistestfolde_empty_rule1" + GUID.generate(); + FileInfo folderEmptyWithRule = createContent(folderNameEmptyRule, null, true); + testFolderMap.put(folderEmptyWithRule, Boolean.TRUE); + + for (Map.Entry entry : testFolderMap.entrySet()) + { + try + { + // delete folder + cmisService.deleteObject((String) cmisService.getProperty(entry.getKey().getNodeRef(), CMISDictionaryModel.PROP_OBJECT_ID), Boolean.TRUE); + } + catch (CMISConstraintException ex) + { + assertTrue(!entry.getValue()); + continue; + } + + assertTrue(entry.getValue()); + } + + } + finally + { + for (Map.Entry entry : testFolderMap.entrySet()) + { + if (fileFolderService.exists(entry.getKey().getNodeRef())) + { + fileFolderService.delete(entry.getKey().getNodeRef()); + } + } + } + } + + private FileInfo createContent(final String folderName, final String docName, final boolean isRule) + { + final FileInfo folderInfo = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public FileInfo execute() throws Throwable + { + NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome(); + + FileInfo folderInfo = fileFolderService.create(companyHomeNodeRef, folderName, ContentModel.TYPE_FOLDER); + nodeService.setProperty(folderInfo.getNodeRef(), ContentModel.PROP_NAME, folderName); + assertNotNull(folderInfo); + + FileInfo fileInfo; + if (docName != null) + { + fileInfo = fileFolderService.create(folderInfo.getNodeRef(), docName, ContentModel.TYPE_CONTENT); + nodeService.setProperty(fileInfo.getNodeRef(), ContentModel.PROP_NAME, docName); + assertNotNull(fileInfo); + } + + if (isRule) + { + Rule rule = addRule(true, folderName); + + assertNotNull(rule); + + // Attach the rule to the node + ruleService.saveRule(folderInfo.getNodeRef(), rule); + + assertTrue(ruleService.getRules(folderInfo.getNodeRef()).size() > 0); + } + + return folderInfo; + } + }); + + return folderInfo; + } + + private Rule addRule(boolean isAppliedToChildren, String title) + { + + // Rule properties + Map conditionProps = new HashMap(); + conditionProps.put(ComparePropertyValueEvaluator.PARAM_VALUE, ".txt"); + + Map actionProps = new HashMap(); + actionProps.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + List ruleTypes = new ArrayList(1); + ruleTypes.add(RuleType.INBOUND); + + // Create the action + org.alfresco.service.cmr.action.Action action = actionService.createAction(title); + action.setParameterValues(conditionProps); + + ActionCondition actionCondition = actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + actionCondition.setParameterValues(conditionProps); + action.addActionCondition(actionCondition); + + // Create the rule + Rule rule = new Rule(); + rule.setRuleTypes(ruleTypes); + rule.setTitle(title); + rule.setDescription("description"); + rule.applyToChildren(isAppliedToChildren); + rule.setAction(action); + + return rule; + } } diff --git a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java index d1d101a9df..0ece7644e7 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java +++ b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java @@ -67,7 +67,6 @@ import org.alfresco.query.EmptyPagingResults; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.model.Repository; -import org.alfresco.repo.model.filefolder.HiddenAspect; import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -92,7 +91,6 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -160,7 +158,6 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, private CMISRenditionService cmisRenditionService; private CheckOutCheckInService checkOutCheckInService; private VersionService versionService; - private MimetypeService mimetypeService; private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); // CMIS supported version @@ -336,33 +333,13 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, this.versionService = versionService; } - /** - * Sets the mimetype service. - * - * @param mimetypeService - * the mimetype service - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - /* (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { lifecycle.setApplicationContext(applicationContext); } - public void setHiddenAspect(HiddenAspect hiddenAspect) - { - this.hiddenAspect = hiddenAspect; - } - - /* (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ + @Override public void onApplicationEvent(ApplicationContextEvent event) { lifecycle.onApplicationEvent(event); @@ -374,43 +351,31 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, private class ProcessorLifecycle extends AbstractLifecycleBean { - /* (non-Javadoc) - * @see org.alfresco.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent) - */ @Override protected void onBootstrap(ApplicationEvent event) { init(); } - /* (non-Javadoc) - * @see org.alfresco.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent) - */ @Override protected void onShutdown(ApplicationEvent event) { } } - /* (non-Javadoc) - * @see org.alfresco.repo.tenant.TenantDeployer#onEnableTenant() - */ + @Override public void onEnableTenant() { init(); } - /* (non-Javadoc) - * @see org.alfresco.repo.tenant.TenantDeployer#onDisableTenant() - */ + @Override public void onDisableTenant() { destroy(); } - /* (non-Javadoc) - * @see org.alfresco.repo.tenant.TenantDeployer#init() - */ + @Override public void init() { // initialise data types @@ -595,18 +560,12 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, } } - private HiddenAspect hiddenAspect; - - /* - * (non-Javadoc) - * @see org.alfresco.cmis.CMISServices#getChildren(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.cmis.CMISTypesFilterEnum, java.lang.String) - */ + @Override public NodeRef[] getChildren(NodeRef folderNodeRef, CMISTypesFilterEnum typesFilter, String orderBy) throws CMISInvalidArgumentException { PagingResults pageOfNodeInfos = getChildren(folderNodeRef, typesFilter, BigInteger.valueOf(Integer.MAX_VALUE), BigInteger.valueOf(0), orderBy); -// List filteredChildren = hiddenAspect.removeHiddenFiles(Client.cmis, pageOfNodeInfos.getPage()); List filteredChildren = pageOfNodeInfos.getPage(); int pageCnt = filteredChildren.size(); NodeRef[] result = new NodeRef[pageCnt]; @@ -1663,7 +1622,10 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, CMISTypeDefinition typeDef = getTypeDefinition(nodeRef); if (typeDef.getTypeId().getBaseTypeId() == CMISDictionaryModel.FOLDER_TYPE_ID) { - if (nodeService.getChildAssocs(nodeRef).size() > 0) + if (nodeService.getChildAssocs( + nodeRef, + ContentModel.ASSOC_CONTAINS, + RegexQNamePattern.MATCH_ALL).size() > 0) { throw new CMISConstraintException("Could not delete folder with at least one Child"); } diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index 1514fcfb75..e5086c288f 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -6355,6 +6355,187 @@ public class ContentDiskDriverTest extends TestCase tran.doInTransaction(validateCB, false, true); } // testScenarioMacMountainLionPreview_MNT_263 + + /** + * This test tries to simulate the cifs shuffling that is done + * from Save from Mac Mountain Lion by Preview when document is opened/saved few time a row + * + * a) Temp file created in temporary folder (temp\image.jpg) + * b) Original file is renamed for deletion(test\image.jpg -> test\.smbdeleteAAA1b994.4) + * c) Renamed file has got deleteOnClose flag + * d) Renamed file is closed. + * e) Temp file is moved into original file location(temp\image.jpg -> test\image.jgp) + */ + public void testScenarioMacMountainLionPreview_MNT_317() throws Exception + { + logger.debug("testScenarioMacMountainLionPreview_MNT_317"); + final String FILE_NAME = "image.jpg"; + final String TEMP_FILE_NAME = ".smbdeleteAAA1b994.4"; + + final String UPDATED_TEXT = "Mac Lion Preview Updated Content"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile tempFileHandle; + NodeRef testNodeRef; // node ref image.jpg + } + ; + + final TestContext testContext = new TestContext(); + + final String TEST_ROOT_DIR = "\\ContentDiskDriverTest"; + final String TEST_DIR = "\\ContentDiskDriverTest\\testScenarioMountainLionPreview"; + final String TEST_TEMP_DIR = "\\ContentDiskDriverTest\\testScenarioMountainLionPreview\\.Temporary Items"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() + { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createTempDirParams = new FileOpenParams(TEST_TEMP_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + driver.createDirectory(testSession, testConnection, createTempDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + String testContent = "Mac Lion Preview Content"; + byte[] testContentBytes = testContent.getBytes(); + + driver.writeFile(testSession, testConnection, testContext.firstFileHandle, testContentBytes, 0, testContentBytes.length, 0); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + /** + * Create the temp file we are going to use + */ + FileOpenParams createTempFileParams = new FileOpenParams(TEST_TEMP_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.tempFileHandle = driver.createFile(testSession, testConnection, createTempFileParams); + assertNotNull(testContext.tempFileHandle); + + testContent = UPDATED_TEXT; + testContentBytes = testContent.getBytes(); + driver.writeFile(testSession, testConnection, testContext.tempFileHandle, testContentBytes, 0, testContentBytes.length, 0); + driver.closeFile(testSession, testConnection, testContext.tempFileHandle); + + /** + * Also add versionable to target file + */ + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + RetryingTransactionCallback renameFileCB = new RetryingTransactionCallback() + { + + @Override + public Void execute() throws Throwable + { + FileOpenParams openFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.tempFileHandle = driver.openFile(testSession, testConnection, openFileParams); + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + TEMP_FILE_NAME); + + return null; + } + }; + tran.doInTransaction(renameFileCB, false, true); + + /** + * Delete file via deleteOnClose flag. + */ + RetryingTransactionCallback deleteOnCloseCB = new RetryingTransactionCallback() + { + + @Override + public Void execute() throws Throwable + { + FileInfo info = new FileInfo(); + info.setFileInformationFlags(FileInfo.SetDeleteOnClose); + info.setDeleteOnClose(true); + testContext.tempFileHandle.setDeleteOnClose(true); + + driver.setFileInformation(testSession, testConnection, TEST_DIR + "\\" + TEMP_FILE_NAME, info); + + assertNotNull(testContext.tempFileHandle); + logger.debug("this close should result in a file being deleted"); + driver.closeFile(testSession, testConnection, testContext.tempFileHandle); + return null; + } + }; + tran.doInTransaction(deleteOnCloseCB, false, true); + +// /** +// * Delete file directly. +// */ +// RetryingTransactionCallback deleteTargetFileCB = new RetryingTransactionCallback() +// { +// +// @Override +// public Void execute() throws Throwable +// { +// +// driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + TEMP_FILE_NAME); +// return null; +// } +// }; +// tran.doInTransaction(deleteTargetFileCB, false, true); + + RetryingTransactionCallback moveRenamedTempFileCB = new RetryingTransactionCallback() + { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_TEMP_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + tran.doInTransaction(moveRenamedTempFileCB, false, true); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() + { + + @Override + public Void execute() throws Throwable + { + + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + assertTrue("node is not versionable", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_VERSIONABLE)); + assertEquals("shuffledNode ref is different", shuffledNodeRef, testContext.testNodeRef); + assertEquals("Unexpected content size", contentService.getReader(shuffledNodeRef, ContentModel.PROP_CONTENT).getSize(), UPDATED_TEXT.length()); + + return null; + } + }; + + tran.doInTransaction(validateCB, false, true); + + } // testScenarioMacMountainLionPreview_MNT_317 /** * Gedit has the nasty behaviour of renaming an open file. diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameDeleteMove.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameDeleteMove.java new file mode 100644 index 0000000000..a32cda7956 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameDeleteMove.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2010 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.filesys.repo.rules; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A rename, delete, move scenario + * + * a) Original file is renamed. + * b) Renamed file is deleted via delete command or via deleteOnClose flag and close operation. + * c) Temp file is moved into original file location. + */ +public class ScenarioRenameDeleteMove implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioRenameDeleteMove.class); + + /** + * The regex pattern of a close that will trigger a new instance of the scenario. + */ + private Pattern pattern; + private String strPattern; + + private long timeout = 30000; + + @Override + public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation) + { + /** + * This scenario is triggered by a rename of a file matching the pattern + */ + if (operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation) operation; + + Matcher m = pattern.matcher(r.getTo()); + if (m.matches()) + { + if (logger.isDebugEnabled()) + { + logger.debug("New Scenario ScenarioRenameDeleteMoveInstance strPattern:" + pattern); + } + ScenarioRenameDeleteMoveInstance instance = new ScenarioRenameDeleteMoveInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return strPattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameDeleteMoveInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameDeleteMoveInstance.java new file mode 100644 index 0000000000..e7d0dfbcfe --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameDeleteMoveInstance.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2005-2010 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.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; + +import org.alfresco.filesys.repo.ResultCallback; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.commands.RestoreFileCommand; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a rename, delete, move scenario triggered by a rename of a + * file matching a specified pattern. + *

+ * a) Original file is renamed. Typically with an obscure name. + * b) Renamed file is deleted via delete command or via deleteOnClose flag and close operation. + * c) Temp file is moved into original file location. + * + *

+ * If this filter is active then this is what happens. + * a) Original file is renamed: + * - File is renamed. + * b) Renamed file is deleted via delete command or via deleteOnClose flag and close operation: + * - File is deleted. + * c) Temp file is moved into original file location - Scenario fires + * - Deleted file is restored. + * - Restored file is renamed to it's original name. + * - Content from file that must be moved is copied to restored file. + * - File that must be moved is deleted. + */ +public class ScenarioRenameDeleteMoveInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioRenameDeleteMoveInstance.class); + + enum InternalState + { + NONE, DELETE, MOVE + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String fileMiddle; + private String fileFrom; + private String fileEnd; + + private Ranking ranking; + private boolean deleteBackup; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete; + private String folderMiddle; + private String folderEnd; + private NodeRef originalNodeRef; + + /** + * Evaluate the next operation + * + * @param operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if (now.getTime() > startTime.getTime() + getTimeout()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + + } + isComplete = true; + return null; + } + + switch (internalState) + { + + case NONE: + + if (operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation) operation; + fileMiddle = r.getFrom(); + fileEnd = r.getTo(); + + String[] paths = FileName.splitPath(r.getFromPath()); + folderMiddle = paths[0]; + + String[] paths2 = FileName.splitPath(r.getToPath()); + folderEnd = paths2[0]; + + internalState = InternalState.DELETE; + } + else + { + // anything else bomb out + if (logger.isDebugEnabled()) + { + logger.debug("State error, expected a RENAME"); + } + isComplete = true; + } + + case DELETE: + + if (operation instanceof DeleteFileOperation) + { + internalState = InternalState.MOVE; + DeleteFileOperation d = (DeleteFileOperation) operation; + if (d.getName().equalsIgnoreCase(fileEnd)) + { + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + // Rename node to remove "hidden". In this case node will be moved to the archive store and can be restored later. + // This can be replaced with command that removes hidden aspect in future(when ContentDiskDriver2.setFileInformation() method will support hidden attribute) + RenameFileCommand r1 = new RenameFileCommand(fileEnd, "tmp" + fileEnd, d.getRootNodeRef(), folderEnd + "\\" + fileEnd, folderEnd + "\\" + "tmp" + fileEnd); + fileEnd = "tmp" + fileEnd; + commands.add(r1); + commands.add(new DeleteFileCommand(fileEnd, d.getRootNodeRef(), folderEnd + "\\" + fileEnd)); + postCommitCommands.add(newDeleteFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + } + if (operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation) operation; + if (c.getNetworkFile().hasDeleteOnClose() && c.getName().equalsIgnoreCase(fileEnd)) + { + internalState = InternalState.MOVE; + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + // Rename node to remove "hidden". In this case node will be moved to the archive store and can be restored later. + RenameFileCommand r1 = new RenameFileCommand(fileEnd, "tmp" + fileEnd, c.getRootNodeRef(), folderEnd + "\\" + fileEnd, folderEnd + "\\" + "tmp" + fileEnd); + fileEnd = "tmp" + fileEnd; + commands.add(r1); + commands.add(new CloseFileCommand(fileEnd, c.getNetworkFile(), c.getRootNodeRef(), folderEnd + "\\" + fileEnd)); + postCommitCommands.add(newDeleteFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + + } + + break; + + case MOVE: + + if (operation instanceof MoveFileOperation && originalNodeRef != null) + { + if (logger.isDebugEnabled()) + { + logger.info("Tracking rename: " + operation); + } + MoveFileOperation m = (MoveFileOperation) operation; + + if (fileMiddle.equalsIgnoreCase(m.getTo())) + { + if (logger.isDebugEnabled()) + { + logger.debug("Got second rename"); + } + + fileFrom = m.getFrom(); + + String[] paths = FileName.splitPath(m.getFromPath()); + String oldFolder = paths[0]; + + ArrayList commands = new ArrayList(); + + RestoreFileCommand rest1 = new RestoreFileCommand(fileEnd, m.getRootNodeRef(), folderEnd, 0, originalNodeRef); + RenameFileCommand r1 = new RenameFileCommand(fileEnd, fileMiddle, m.getRootNodeRef(), folderEnd + "\\" + fileEnd, folderMiddle + "\\" + fileMiddle); + commands.add(rest1); + commands.add(r1); + CopyContentCommand copyContent = new CopyContentCommand(fileFrom, fileMiddle, m.getRootNodeRef(), oldFolder + "\\" + fileFrom, folderMiddle + "\\" + fileMiddle); + commands.add(copyContent); + DeleteFileCommand d1 = new DeleteFileCommand(oldFolder, m.getRootNodeRef(), oldFolder + "\\" + fileFrom); + commands.add(d1); + + isComplete = true; + return new CompoundCommand(commands); + } + } + + break; + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioRenameDeleteMove:" + fileMiddle; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setDeleteBackup(boolean deleteBackup) + { + this.deleteBackup = deleteBackup; + } + + public boolean isDeleteBackup() + { + return deleteBackup; + } + + /** + * Called for delete file. + */ + private ResultCallback newDeleteFileCallbackCommand() + { + return new ResultCallback() + { + @Override + public void execute(Object result) + { + if (result instanceof NodeRef) + { + logger.debug("got node ref of deleted node"); + originalNodeRef = (NodeRef) result; + } + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + }; + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index 7821e6c3bb..33c986ff74 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -1546,7 +1546,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr // handle folders if (info.isFolder()) { - if (connector.getNodeService().getChildAssocs(nodeRef).size() > 0) + if (connector.getNodeService().getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL).size() > 0) { throw new CmisConstraintException( "Could not delete folder with at least one child!"); diff --git a/source/java/org/alfresco/opencmis/CMISTest.java b/source/java/org/alfresco/opencmis/CMISTest.java index 0237738454..ea2d02a35c 100644 --- a/source/java/org/alfresco/opencmis/CMISTest.java +++ b/source/java/org/alfresco/opencmis/CMISTest.java @@ -7,23 +7,29 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.io.Serializable; import java.math.BigInteger; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.model.WCMModel; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; +import org.alfresco.service.cmr.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.apache.chemistry.opencmis.commons.PropertyIds; @@ -66,6 +72,9 @@ public class CMISTest private AlfrescoCmisServiceFactory factory; private SimpleCallContext context; + private ActionService actionService; + private RuleService ruleService; + /** * Test class to provide the service factory * @@ -167,6 +176,8 @@ public class CMISTest @Before public void before() { + this.actionService = (ActionService)ctx.getBean("actionService"); + this.ruleService = (RuleService)ctx.getBean("ruleService"); this.fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); this.transactionService = (TransactionService)ctx.getBean("transactionService"); this.nodeService = (NodeService)ctx.getBean("NodeService"); @@ -360,4 +371,163 @@ public class CMISTest AuthenticationUtil.popAuthentication(); } } + + /** + * Test for ALF-18151. + */ + @Test + public void testDeleteFolder() + { + String repositoryId = null; + ObjectData objectData = null; + Holder objectId = null; + + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + Map testFolderMap = new HashMap(4); + + try + { + // create folder with file + String folderName = "testfolder" + GUID.generate(); + String docName = "testdoc.txt" + GUID.generate(); + FileInfo folder = createContent(folderName, docName, false); + testFolderMap.put(folder, Boolean.FALSE); + + // create empty folder + String folderNameEmpty = "testfolder_empty1" + GUID.generate(); + FileInfo folderEmpty = createContent(folderNameEmpty, null, false); + testFolderMap.put(folderEmpty, Boolean.TRUE); + + // create folder with file + String folderNameRule = "testfolde_rule" + GUID.generate(); + String docNameRule = "testdoc_rule.txt" + GUID.generate(); + FileInfo folderWithRule = createContent(folderNameRule, docNameRule, true); + testFolderMap.put(folderWithRule, Boolean.FALSE); + + // create empty folder + String folderNameEmptyRule = "testfolde_empty_rule1" + GUID.generate(); + FileInfo folderEmptyWithRule = createContent(folderNameEmptyRule, null, true); + testFolderMap.put(folderEmptyWithRule, Boolean.TRUE); + + CmisService service = factory.getService(context); + + try + { + List repositories = service.getRepositoryInfos(null); + RepositoryInfo repo = repositories.get(0); + repositoryId = repo.getId(); + + for (Map.Entry entry : testFolderMap.entrySet()) + { + objectData = service.getObjectByPath(repositoryId, "/" + entry.getKey().getName(), null, true, IncludeRelationships.NONE, null, false, true, null); + + objectId = new Holder(objectData.getId()); + + try + { + // delete folder + service.deleteObjectOrCancelCheckOut(repositoryId, objectId.getValue(), Boolean.TRUE, null); + } + catch (CmisConstraintException ex) + { + assertTrue(!entry.getValue()); + continue; + } + + assertTrue(entry.getValue()); + } + } + finally + { + service.close(); + } + + } + finally + { + for (Map.Entry entry : testFolderMap.entrySet()) + { + if (fileFolderService.exists(entry.getKey().getNodeRef())) + { + fileFolderService.delete(entry.getKey().getNodeRef()); + } + } + + AuthenticationUtil.popAuthentication(); + } + } + + private FileInfo createContent(final String folderName, final String docName, final boolean isRule) + { + final FileInfo folderInfo = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public FileInfo execute() throws Throwable + { + NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome(); + + FileInfo folderInfo = fileFolderService.create(companyHomeNodeRef, folderName, ContentModel.TYPE_FOLDER); + nodeService.setProperty(folderInfo.getNodeRef(), ContentModel.PROP_NAME, folderName); + assertNotNull(folderInfo); + + FileInfo fileInfo; + if (docName != null) + { + fileInfo = fileFolderService.create(folderInfo.getNodeRef(), docName, ContentModel.TYPE_CONTENT); + nodeService.setProperty(fileInfo.getNodeRef(), ContentModel.PROP_NAME, docName); + assertNotNull(fileInfo); + } + + if (isRule) + { + Rule rule = addRule(true, folderName); + + assertNotNull(rule); + + // Attach the rule to the node + ruleService.saveRule(folderInfo.getNodeRef(), rule); + + assertTrue(ruleService.getRules(folderInfo.getNodeRef()).size() > 0); + } + + return folderInfo; + } + }); + + return folderInfo; + } + + private Rule addRule(boolean isAppliedToChildren, String title) + { + + // Rule properties + Map conditionProps = new HashMap(); + conditionProps.put(ComparePropertyValueEvaluator.PARAM_VALUE, ".txt"); + + Map actionProps = new HashMap(); + actionProps.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + List ruleTypes = new ArrayList(1); + ruleTypes.add(RuleType.INBOUND); + + // Create the action + org.alfresco.service.cmr.action.Action action = actionService.createAction(title); + action.setParameterValues(conditionProps); + + ActionCondition actionCondition = actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + actionCondition.setParameterValues(conditionProps); + action.addActionCondition(actionCondition); + + // Create the rule + Rule rule = new Rule(); + rule.setRuleTypes(ruleTypes); + rule.setTitle(title); + rule.setDescription("description"); + rule.applyToChildren(isAppliedToChildren); + rule.setAction(action); + + return rule; + } } diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java index 50a03b173e..f088443b2e 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java @@ -1404,7 +1404,8 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine if(lazyInitialization) { - resultingTasks.add(new LazyActivitiWorkflowTask(task, typeConverter, tenantService)); + resultingTasks.add(new LazyActivitiWorkflowTask(task, typeConverter, tenantService, + typeConverter.getWorkflowDefinitionName(task.getProcessDefinitionId()))); } else { @@ -1484,7 +1485,17 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine // have a group with the same name if(lazyInitialization) { - currentTask = new LazyActivitiWorkflowTask(task, typeConverter, tenantService); + String workflowDefinitionName = typeConverter.getWorkflowDefinitionName(task.getProcessDefinitionId()); + try + { + workflowDefinitionName = tenantService.getBaseName(workflowDefinitionName); + currentTask = new LazyActivitiWorkflowTask(task, typeConverter, tenantService, workflowDefinitionName); + } + catch(RuntimeException re) + { + // Domain mismatch, don't use this task + currentTask = null; + } } else { diff --git a/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java b/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java index 4232aaf5d1..f3dd88331c 100644 --- a/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java +++ b/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java @@ -55,7 +55,7 @@ public class LazyActivitiWorkflowTask extends WorkflowTask private LazyPropertiesMap lazyPropertiesMap; @SuppressWarnings("deprecation") - public LazyActivitiWorkflowTask(Task task, ActivitiTypeConverter typeConverter, TenantService tenantService) + public LazyActivitiWorkflowTask(Task task, ActivitiTypeConverter typeConverter, TenantService tenantService, String workflowDefinitionName) { super(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, task.getId()), null, null, null, null, null, null, null); this.task = task; @@ -65,9 +65,6 @@ public class LazyActivitiWorkflowTask extends WorkflowTask // Fetch task-definition and a partially-initialized WorkflowTask (not including properties and path) WorkflowTaskDefinition taskDefinition = activitiTypeConverter.getTaskDefinition(task); - String workflowDefinitionName = activitiTypeConverter.getWorkflowDefinitionName(task.getProcessDefinitionId()); - workflowDefinitionName = tenantService.getBaseName(workflowDefinitionName); - WorkflowTask partiallyInitialized = typeConverter.getWorkflowObjectFactory().createTask(task.getId(), taskDefinition, taskDefinition.getId(), task.getName(), task.getDescription(), WorkflowTaskState.IN_PROGRESS, null, workflowDefinitionName , lazyPropertiesMap);