From cb9064aa145eca7c31d9f2ee63683707396166cd Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Sat, 31 Jan 2015 15:32:16 +0000 Subject: [PATCH] Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud) 94142: Merged 5.0.N (5.0.1) to HEAD-BUG-FIX (5.1/Cloud) 94079: Merged DEV to 5.0.N (5.0.1) 93735 : MNT-13078: Office2013 : Incorrect version history for .xlxs files created via CIFS - Added shuffle scenario for MS Excel 2013 94001 : MNT-13078: Office2013 : Incorrect version history for .xlxs files created via CIFS - Added a test for "MultipleRenameShuffle" pattern git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@95043 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../default/network-protocol-context.xml | 6 + .../rules/ScenarioMultipleRenameShuffle.java | 122 +++++++++ ...ScenarioMultipleRenameShuffleInstance.java | 229 ++++++++++++++++ .../filesys/repo/ContentDiskDriverTest.java | 244 ++++++++++++++++++ 4 files changed, 601 insertions(+) create mode 100755 source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffle.java create mode 100755 source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffleInstance.java diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index 0e6e443dec..c6e12825f6 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -223,6 +223,12 @@ 60000 HIGH + + + .*\.xlsx~RF.*\.TMP + 30000 + HIGH + .*D_[0-9]*.TMP$ diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffle.java new file mode 100755 index 0000000000..1191fa101a --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffle.java @@ -0,0 +1,122 @@ +/* + * 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 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; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This is an instance of a "multiple rename shuffle" triggered by rename of a file to a special pattern + * file matching a specified pattern. + * + * a) Original file renamed to the temporary + * b) Any operations with temporary (optional): + * b1) Temporary file renamed to other temporary + * b2) Temporary file deleted + * c) Temporary file (maybe not the same, as it was at step 1) renamed to the original file + *

+ * If this filter is active then this is what happens. + * a) Temporary file created. Content copied from original file to temporary file. + * b) Original file deleted (temporary). + * c) any operations with temporary file + * d) Original file restored. Content copied from temporary file to original file. + * + */ +public class ScenarioMultipleRenameShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioMultipleRenameShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @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 Multiple Rename Shuffle strPattern: " + strPattern + " matches" + r.getTo()); + } + ScenarioMultipleRenameShuffleInstance instance = new ScenarioMultipleRenameShuffleInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffleInstance.java new file mode 100755 index 0000000000..c8ef871653 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioMultipleRenameShuffleInstance.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2005-2015 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 org.alfresco.filesys.repo.ResultCallback; +import org.alfresco.filesys.repo.rules.commands.*; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.ArrayList; +import java.util.Date; + +/** + * This is an instance of a "multiple rename shuffle" triggered by rename of a file to a special pattern + * file matching a specified pattern. + * + * a) Original file renamed to the temporary + * b) Any operations with temporary (optional): + * b1) Temporary file renamed to other temporary + * b2) Temporary file deleted + * c) Temporary file (maybe not the same, as it was at step 1) renamed to the original file + *

+ * If this filter is active then this is what happens. + * a) Temporary file created. Content copied from original file to temporary file. + * b) Original file deleted (temporary). + * c) any operations with temporary file + * d) Original file restored. Content copied from temporary file to original file. + * + */ +public class ScenarioMultipleRenameShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioMultipleRenameShuffleInstance.class); + private NodeRef originalNodeRef; + + enum InternalState + { + NONE, + INITIALISED + } + + InternalState internalState = InternalState.NONE; + private Date startTime = new Date(); + private String originalName; + private Ranking ranking; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + private boolean isComplete; + + /** + * 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: + /** + * Looking for first rename O(original) to T(temporary) + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + if(logger.isDebugEnabled()) + { + logger.debug("Got first rename - tracking rename: " + operation); + } + originalName = r.getFromPath(); + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + + CreateFileCommand c1 = new CreateFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, true); + CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); + postCommitCommands.add(deleteFileCallbackCommand()); + + commands.add(c1); + commands.add(copyContent); + commands.add(d1); + + internalState = InternalState.INITIALISED; + + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a RENAME"); + } + isComplete = true; + } + + case INITIALISED: + + /** + * Looking for last rename T(temporary) to O(original) + */ + if (operation instanceof RenameFileOperation) + { + if (logger.isDebugEnabled()) { + logger.debug("Tracking rename: " + operation); + } + RenameFileOperation r = (RenameFileOperation) operation; + + if(r.getToPath().equalsIgnoreCase(originalName)) + { + ArrayList commands = new ArrayList(); + RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); + CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r1.getName(), r.getRootNodeRef(), r.getFromPath(), r1.getPath()); + DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + if(logger.isDebugEnabled()) + { + logger.debug("Scenario complete"); + } + 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 "ScenarioMultipleRenameShuffleInstance: createName:" + originalName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + + /** + * Called for delete file. + */ + private ResultCallback deleteFileCallbackCommand() + { + 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 AlfrescoTransactionSupport.TxnReadState getTransactionRequired() + { + return AlfrescoTransactionSupport.TxnReadState.TXN_NONE; + } + }; + } +} diff --git a/source/test-java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/test-java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index 2f96940f26..3a41c2f45e 100644 --- a/source/test-java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/test-java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -3226,6 +3226,250 @@ public class ContentDiskDriverTest extends TestCase } + /** + * Excel 2013 With Versionable file (MNT-13078) + * + * CreateFile Cherries.xlsx + * CreateFile ~herries.tmp + * CreateFile Cherries.xlsx~RF172f241.TMP + * DeleteFile Cherries.xlsx~RF172f241.TMP + * RenameFile oldName: Cherries.xls, + * newName: Cherries.xlsx~RF172f241.TMP + * Delete On Close for Cherries.xlsx~RF172f241.TMP + * RenameFile oldName: ~herries.tmp, + * newName: Cherries.xlsx + * + */ + public void testExcel2013SaveShuffle() throws Exception + { + logger.debug("testScenarioExcel2013SaveShuffle"); + final String FILE_NAME = "Cherries.xlsx"; + final String FILE_ORIGINAL_TITLE = "Original"; + final String FILE_UNUSED_TEMP = "Cherries.xlsx~RF172f241.TMP"; + final String FILE_USED_TEMP = "~herries.tmp"; + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testScenarioMSExcel2013SaveShuffle"; + + class TestContext + { + NetworkFile firstFileHandle; + NodeRef testNodeRef; // node ref of test.doc + }; + + final TestContext testContext = new TestContext(); + + 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(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + /** + * Delete a file in the test directory + */ + + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createOriginalFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable { + + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * 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 = "MS Word 2013 shuffle test. This is first file content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + + // now load up the node with lots of other stuff that we will test to see if it gets preserved during the + // shuffle. + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_VERSIONABLE, null); + // need to remove. Otherwise file will be deleted (not placed into archive spaces store) + nodeService.removeAspect(testContext.testNodeRef, ContentModel.ASPECT_NO_CONTENT); + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_TITLE, FILE_ORIGINAL_TITLE); + + return null; + } + }; + tran.doInTransaction(createOriginalFileCB, false, true); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createUsedTempFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_USED_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + NetworkFile fileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(fileHandle); + String testContent = "MS Word 2013 shuffle test. This is used file content"; + byte[] testContentBytes = testContent.getBytes(); + fileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + fileHandle.close(); + + NodeRef usedRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_USED_TEMP); + + nodeService.addAspect(usedRef, ContentModel.ASPECT_VERSIONABLE, null); + nodeService.removeAspect(usedRef, ContentModel.ASPECT_NO_CONTENT); + nodeService.setProperty(usedRef, ContentModel.PROP_TITLE, "Used"); + + return null; + } + }; + tran.doInTransaction(createUsedTempFileCB, false, true); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createUnusedTempFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_UNUSED_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + NetworkFile unusedFile = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(unusedFile); + + NodeRef unusedNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_UNUSED_TEMP); + nodeService.addAspect(unusedNodeRef, ContentModel.ASPECT_VERSIONABLE, null); + nodeService.setProperty(unusedNodeRef, TransferModel.PROP_ENABLED, true); + nodeService.setProperty(unusedNodeRef, ContentModel.PROP_TITLE, "Unused"); + + return null; + } + }; + tran.doInTransaction(createUnusedTempFileCB, false, true); + + /** + * Delete unused temporary file + */ + RetryingTransactionCallback deleteUnusedFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + try + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_UNUSED_TEMP); + } + catch (IOException e) + { + // expect to go here since previous step renamed the file. + } + + return null; + } + }; + tran.doInTransaction(deleteUnusedFileCB, false, true); + + /** + * Rename the original file to unused file + */ + RetryingTransactionCallback renameToUnusedFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_UNUSED_TEMP); + return null; + } + }; + tran.doInTransaction(renameToUnusedFileCB, false, true); + + /** + * Delete unused temporary file + */ + tran.doInTransaction(deleteUnusedFileCB, false, true); + + /** + * Rename the used temporary file to original file + */ + RetryingTransactionCallback renameToUsedFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_USED_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + tran.doInTransaction(renameToUsedFileCB, false, true); + + + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + + // Check trx:enabled has been shuffled. + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + assertTrue("node doesn't contain property 'TITLE'", props.containsKey(ContentModel.PROP_TITLE)); + assertEquals("propety 'TITLE' isn't correct", FILE_ORIGINAL_TITLE, props.get(ContentModel.PROP_TITLE)); + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME)); + + return null; + } + }; + + tran.doInTransaction(validateCB, true, true); + } + /** * Excel 2003 CSV file with Versionable file *