diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 94a8fcf7f0..b21121a42e 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -730,7 +730,7 @@ parent="baseComplexContentTransformer" > - + @@ -871,6 +871,14 @@ + + + + text/html + application/pdf + + + @@ -938,7 +946,7 @@ parent="baseComplexContentTransformer" > - + @@ -989,6 +997,61 @@ + + + + + + + + + + + + application/pdf + + + + + + + + + + + + + + + application/vnd.oasis.opendocument.text + + + + + + text/html + application/pdf + + + + + + + text/html + application/pdf + + + + + diff --git a/config/alfresco/model/imapModel.xml b/config/alfresco/model/imapModel.xml index 3bc8243383..1d79886e02 100644 --- a/config/alfresco/model/imapModel.xml +++ b/config/alfresco/model/imapModel.xml @@ -40,31 +40,71 @@ d:text + + true + false + both + d:text + + true + false + both + d:text + + true + false + both + d:text + + true + false + both + d:text + + true + false + both + d:text + + true + false + both + Date Received d:datetime false + + true + false + both + Date Sent d:datetime false + + true + false + both + diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml index 02bfa6faa4..deac3bf049 100644 --- a/config/alfresco/rule-services-context.xml +++ b/config/alfresco/rule-services-context.xml @@ -143,13 +143,24 @@ - + + + + + + + + + + + + onCreateAssociation - + onDeleteAssociation diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index eb32c6a834..b2cb6ea006 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -140,6 +140,14 @@ 60000 MEDIUM + + + + .* + 20000 + MEDIUM + + .* @@ -207,7 +215,16 @@ 20000 MEDIUM true - + + + + + ^Word Work File L_.*\.tmp? + 20000 + MEDIUM + false + + diff --git a/config/alfresco/swf-transform-context.xml b/config/alfresco/swf-transform-context.xml index 05d43fea8a..47d52cd681 100644 --- a/config/alfresco/swf-transform-context.xml +++ b/config/alfresco/swf-transform-context.xml @@ -31,7 +31,7 @@ parent="baseComplexContentTransformer" > - + diff --git a/source/java/org/alfresco/filesys/alfresco/ExtendedDiskInterface.java b/source/java/org/alfresco/filesys/alfresco/ExtendedDiskInterface.java index 1e349499da..b2560ca98e 100644 --- a/source/java/org/alfresco/filesys/alfresco/ExtendedDiskInterface.java +++ b/source/java/org/alfresco/filesys/alfresco/ExtendedDiskInterface.java @@ -18,7 +18,6 @@ */ package org.alfresco.filesys.alfresco; -import org.alfresco.filesys.config.ServerConfigurationBean; import org.alfresco.jlan.server.core.DeviceContext; import org.alfresco.jlan.server.core.DeviceContextException; import org.alfresco.jlan.server.filesys.DiskInterface; diff --git a/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java b/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java index 3e9340c29a..68b112753f 100644 --- a/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java +++ b/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java @@ -92,9 +92,10 @@ public interface RepositoryDiskInterface * @param rootNode * @param fromPath - the source node * @param toPath - the target node - * @throws FileNotFoundException + * @throws FileNotFoundException + * @return node ref of deleted file or null if no file deleted */ - public void closeFile(NodeRef rootNode, String Path, NetworkFile file) throws IOException; + public NodeRef closeFile(NodeRef rootNode, String Path, NetworkFile file) throws IOException; /** diff --git a/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java b/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java index 86e8097c1d..b5c842ad75 100644 --- a/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java +++ b/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java @@ -242,7 +242,7 @@ public class CommandExecutorImpl implements CommandExecutor { logger.debug("close file command"); CloseFileCommand c = (CloseFileCommand)command; - repositoryDiskInterface.closeFile(c.getRootNodeRef(), c.getPath(), c.getNetworkFile()); + return repositoryDiskInterface.closeFile(c.getRootNodeRef(), c.getPath(), c.getNetworkFile()); } else if(command instanceof ReduceQuotaCommand) { diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java index 9e5698151d..3074c1e62e 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -1505,7 +1505,14 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD // Inhibit versioning for this transaction getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); - // Check if the file is being marked for deletion, if so then check if the file is locked + // Check if the file is being marked for deletion, if so then check if the file is locked + + /* + * Which DeleteOnClose flag has priority? + * SetDeleteOnClose is not set or used in this method. + * The NTProtocolHandler sets the deleteOnClose in both + * info and the NetworkFile - it's the one in NetworkFile that results in the file being deleted. + */ if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) { if(logger.isDebugEnabled()) @@ -1542,6 +1549,8 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD throw new DirectoryNotEmptyException( name); } } + + } if(info.hasSetFlag(FileInfo.SetAttributes)) @@ -1618,10 +1627,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD // Set the creation and modified date/time Map auditableProps = new HashMap(5); - // Which DeleteOnClose flag has priority? - // SetDeleteOnClose is not set or used in this method. - // The NTProtocolHandler sets the deleteOnClose in both - // info and the NetworkFile - it's the one in NetworkFile that works. + if ( info.hasSetFlag(FileInfo.SetCreationDate) && info.hasCreationDateTime()) { @@ -2725,8 +2731,9 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD * Close the file. * * @exception java.io.IOException If an error occurs. + * @return node ref of deleted file */ - public void closeFile(NodeRef rootNode, String path, NetworkFile file) throws IOException + public NodeRef closeFile(NodeRef rootNode, String path, NetworkFile file) throws IOException { if ( logger.isDebugEnabled()) { @@ -2736,7 +2743,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD if( file instanceof PseudoNetworkFile) { file.close(); - return; + return null; } /** @@ -2744,13 +2751,15 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD */ if(file.hasDeleteOnClose()) { + NodeRef target = null; + if(logger.isDebugEnabled()) { logger.debug("closeFile has delete on close set path:" + path); } try { - NodeRef target = getCifsHelper().getNodeRef(rootNode, path); + target = getCifsHelper().getNodeRef(rootNode, path); if(target!=null) { nodeService.deleteNode(target); @@ -2774,7 +2783,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose()); } - return; + return target; } // Check for a temp file - which will be a new file or a read/write file @@ -2890,6 +2899,8 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL)); } } + + return null; } catch (IOException e) { diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index 3c5bb0895e..722220f359 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -5432,6 +5432,354 @@ public class ContentDiskDriverTest extends TestCase } // testMacDragAndDrop + + /** + * Mountain Lion 2011 Word + * a) Create new file (Word Work File D2.tmp) + * (Actually in real life its renamed from a temp directory. + * c) Existing file rename out of the way. (Word Work File L_5.tmp) + * d) New file rename into place. (MacWord1.docx) + * e) Old file deleted + */ + public void testScenarioMountainLionWord2011() throws Exception + { + logger.debug("testScenarioMountainLionWord2011"); + + final String FILE_NAME = "MacWord1.docx"; + final String FILE_OLD_TEMP = "Word Work File L_5.tmp"; + final String FILE_NEW_TEMP = "Word Work File D_2.tmp"; + + class TestContext + { + NetworkFile firstFileHandle; + String mimetype; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testScenarioMountainLionWord2011"; + + 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; + } + }; + + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + logger.debug("a) create new file"); + 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_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 test + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest3.doc"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest3.doc", fileResource); + writeResourceToNetworkFile(fileResource, testContext.firstFileHandle); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + NodeRef file1NodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.addAspect(file1NodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * b) Save the new file + * Write ContentDiskDriverTest3.doc, + */ + logger.debug("b) move new file into place"); + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest3.doc"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest3.doc", fileResource); + writeResourceToNetworkFile(fileResource, testContext.firstFileHandle); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + + NodeRef file1NodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + Map props = nodeService.getProperties(file1NodeRef); + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); +// assertNotNull("data is null", data); +// assertEquals("size is wrong", 166912, data.getSize()); + testContext.mimetype = data.getMimetype(); + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * c) rename the old file + */ + logger.debug("c) rename old file"); + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + /** + * d) Move the new file into place, stuff should get shuffled + */ + logger.debug("d) move new file into place"); + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + /** + * d) Delete the old file + */ + logger.debug("d) delete the old file"); + RetryingTransactionCallback deleteOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + + tran.doInTransaction(deleteOldFileCB, false, true); + + logger.debug("e) validate results"); + + /** + * Now validate everything is correct + */ + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + //assertNotNull("data is null", data); + //assertEquals("size is wrong", 123904, data.getSize()); + + NodeRef file1NodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + assertTrue("file has lost versionable aspect", nodeService.hasAspect(file1NodeRef, ContentModel.ASPECT_VERSIONABLE)); + + assertEquals("mimeType is wrong", testContext.mimetype, data.getMimetype()); + + + return null; + } + }; + + tran.doInTransaction(validateCB, true, true); + } // Test Word 2011 Mountain Lion + + /** + * This test tries to simulate the cifs shuffling that is done + * from Save from Mac Mountain Lion by Preview + * + * a) Temp file created in temporary folder (Crysanthemum.jpg) + * b) Target file deleted by open / delete on close flag / close + * c) Temp file moved to target file. + */ + public void testScenarioMacMountainLionPreview() throws Exception + { + logger.debug("testScenarioMountainLionPreview"); + final String FILE_NAME = "Crysanthemeum.jpg"; + final String TEMP_FILE_NAME = "Crysanthemeum.jpg"; + + final String UPDATED_TEXT = "Mac Lion Preview Updated Content"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile tempFileHandle; + NodeRef testNodeRef; // node ref Crysanthemenum.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 Mountain Lion Text"; + 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 + "\\" + TEMP_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); + + /** + * b) Delete the target file by opening it and set the delete on close bit + */ + RetryingTransactionCallback deleteTargetFileCB = 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); + FileInfo info = new FileInfo(); + info.setFileInformationFlags(FileInfo.SetDeleteOnClose); + info.setDeleteOnClose(true); + testContext.tempFileHandle.setDeleteOnClose(true); + + driver.setFileInformation(testSession, testConnection, TEST_DIR + "\\" + 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(deleteTargetFileCB, false, true); + + /** + * c) Move the temp file into target directory + */ + RetryingTransactionCallback moveTempFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_TEMP_DIR + "\\" + TEMP_FILE_NAME, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + tran.doInTransaction(moveTempFileCB, false, true); + + /** + * Validate results. + */ + 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); + return null; + } + }; + + tran.doInTransaction(validateCB, false, true); + + } // testScenarioMountainLionPreview + + /** * Test server */ diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteOnCloseRename.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteOnCloseRename.java new file mode 100644 index 0000000000..37b297fd8e --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteOnCloseRename.java @@ -0,0 +1,115 @@ +/* + * 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.CloseFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The DeleteOnClose rename shuffle is a delete on close of a file resulting in a file being deleted followed by a rename of a file from + * somewhere else. + * + * First case of this is Mac Mountain Lion Preview application. + * and then a new copy of the file put into place. + * + * a) DeleteOnClose fileA + * b) Close fileA + * c) Rename whatever fileA + * + */ +public class ScenarioDeleteOnCloseRename implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioDeleteOnCloseRename.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 CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches() && c.getNetworkFile().hasDeleteOnClose()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario ScenarioDeleteOnCloseRename strPattern:" + pattern); + } + ScenarioDeleteOnCloseRenameInstance instance = new ScenarioDeleteOnCloseRenameInstance(); + 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/ScenarioDeleteOnCloseRenameInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteOnCloseRenameInstance.java new file mode 100644 index 0000000000..35703369fc --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDeleteOnCloseRenameInstance.java @@ -0,0 +1,251 @@ +/* + * 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.RestoreFileCommand; +import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +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; + +/** + * First case of this is Mac Mountain Lion Preview application. + * and then a new copy of the file put into place. + * + * a) DeleteOnClose fileA + * b) Close fileA + * c) Rename whatever fileA + * + * This rule will kick in and ... + * + */ +class ScenarioDeleteOnCloseRenameInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioDeleteOnCloseRenameInstance.class); + + private Date startTime = new Date(); + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + private NodeRef originalNodeRef = null; + + enum InternalState + { + NONE, + LOOK_FOR_RENAME + } ; + + InternalState state = InternalState.NONE; + + String name; + + /** + * 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 (state) + { + case NONE: + if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + this.name = c.getName(); + logger.debug("New scenario initialised for file " + name); + state = InternalState.LOOK_FOR_RENAME; + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.add(new CloseFileCommand(c.getName(), c.getNetworkFile(), c.getRootNodeRef(), c.getPath())); + postCommitCommands.add(newDeleteFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } + break; + + case LOOK_FOR_RENAME: + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + if(name.equals(r.getTo())) + { + logger.debug("Delete on close Rename shuffle - fire!"); + + if(originalNodeRef != null) + { + /** + * Shuffle is as follows + * a) Copy content from File to File~ + * b) Delete File + * c) Rename File~ to File + */ + ArrayList commands = new ArrayList(); + RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); + CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + logger.debug("Scenario complete"); + isComplete = true; + return new CompoundCommand(commands); + } + else + { + logger.debug("Scenario complete"); + isComplete = true; + return null; + } + } + } + + + if(operation instanceof MoveFileOperation) + { + MoveFileOperation r = (MoveFileOperation)operation; + if(name.equals(r.getTo())) + { + logger.debug("Delete on close Rename shuffle - fire!"); + + if(originalNodeRef != null) + { + /** + * Shuffle is as follows + * a) Copy content from File to File~ + * b) Delete File + * c) Rename File~ to File + */ + ArrayList commands = new ArrayList(); + RestoreFileCommand r1 = new RestoreFileCommand(r.getTo(), r.getRootNodeRef(), r.getToPath(), 0, originalNodeRef); + CopyContentCommand copyContent = new CopyContentCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + DeleteFileCommand d1 = new DeleteFileCommand(r.getFrom(), r.getRootNodeRef(), r.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + logger.debug("Scenario complete"); + isComplete = true; + return new CompoundCommand(commands); + } + else + { + logger.debug("Scenario complete"); + isComplete = true; + return null; + } + } + } + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioDeleteOnCloseRenameShuffleInstance name:" + name ; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + /** + * 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/filesys/repo/rules/ScenarioOpenFileInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java index 04fc80965e..68caa96acd 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java @@ -562,6 +562,23 @@ class ScenarioOpenFileInstance implements ScenarioInstance, DependentInstance logger.debug("returning merged high priority executor"); return new CompoundCommand(commands, postCommitCommands, postErrorCommands); } + + + if(looser.scenario instanceof ScenarioDeleteOnCloseRenameInstance) + { + CompoundCommand l = (CompoundCommand)looser.command; + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + commands.addAll(c.getCommands()); + postCommitCommands.addAll(c.getPostCommitCommands()); + // Merge in the loosing post commit + postCommitCommands.addAll(l.getPostCommitCommands()); + postErrorCommands.addAll(c.getPostErrorCommands()); + + logger.debug("returning merged high priority executor"); + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + } } } // No change diff --git a/source/java/org/alfresco/repo/content/ContentServiceImpl.java b/source/java/org/alfresco/repo/content/ContentServiceImpl.java index 1c914f7da0..562bdb0ab7 100644 --- a/source/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/source/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -800,9 +800,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa private void debugActiveTransformers(String sourceMimetype, String targetMimetype, long sourceSize, TransformationOptions transformOptions) { - // check the file name, but do faster tests first - if (sourceSize == 18 && - MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && + // check the file name + if (MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype) && "debugTransformers.txt".equals(transformerDebug.getFileName(transformOptions, true, 0))) { diff --git a/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracter.java index 52dc38fe4a..ccc6670998 100644 --- a/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracter.java @@ -65,6 +65,7 @@ public class DWGMetadataExtracter extends TikaPoweredMetadataExtracter super(SUPPORTED_MIMETYPES); } + @SuppressWarnings("deprecation") @Override protected Map extractSpecific(Metadata metadata, Map properties, Map headers) diff --git a/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracterTest.java index 3260fc8fec..ab43839c2f 100644 --- a/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/DWGMetadataExtracterTest.java @@ -45,6 +45,7 @@ public class DWGMetadataExtracterTest extends AbstractMetadataExtracterTest private static final QName TIKA_LAST_AUTHOR_TEST_PROPERTY = QName.createQName("TikaLastAuthorTestProp"); + @SuppressWarnings("deprecation") @Override public void setUp() throws Exception { diff --git a/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java index 226db82850..478a0bb400 100644 --- a/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/MP3MetadataExtracter.java @@ -77,6 +77,7 @@ public class MP3MetadataExtracter extends TikaAudioMetadataExtracter return new Mp3Parser(); } + @SuppressWarnings("deprecation") @Override protected Map extractSpecific(Metadata metadata, Map properties, Map headers) diff --git a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java index 45a40224e9..193a260529 100644 --- a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java @@ -59,7 +59,7 @@ public class MailMetadataExtracter extends TikaPoweredMetadataExtracter public static ArrayList SUPPORTED_MIMETYPES = buildSupportedMimetypes( new String[] {MimetypeMap.MIMETYPE_OUTLOOK_MSG}, - null + (Parser[])null ); public MailMetadataExtracter() @@ -74,6 +74,7 @@ public class MailMetadataExtracter extends TikaPoweredMetadataExtracter return new OfficeParser(); } + @SuppressWarnings("deprecation") @Override protected Map extractSpecific(Metadata metadata, Map properties, Map headers) diff --git a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracterTest.java index abffac8d20..d0d035e673 100644 --- a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracterTest.java @@ -20,7 +20,6 @@ package org.alfresco.repo.content.metadata; import java.io.File; import java.io.Serializable; -import java.util.Collection; import java.util.Map; import org.alfresco.model.ContentModel; diff --git a/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java b/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java index 1aabf85e94..2411611503 100644 --- a/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java +++ b/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java @@ -173,6 +173,7 @@ public class MetadataExtracterRegistry return liveExtractor; } + @SuppressWarnings("deprecation") private String getName(MetadataExtracter extractor) { return extractor == null diff --git a/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java index e2629318a9..d08d9fe3d2 100644 --- a/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java @@ -92,6 +92,7 @@ public class OfficeMetadataExtracter extends TikaPoweredMetadataExtracter return new OfficeParser(); } + @SuppressWarnings("deprecation") @Override protected Map extractSpecific(Metadata metadata, Map properties, Map headers) diff --git a/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java index f6ca5d86ee..976e5ce803 100644 --- a/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/OpenDocumentMetadataExtracter.java @@ -70,8 +70,8 @@ public class OpenDocumentMetadataExtracter extends TikaPoweredMetadataExtracter private static final String KEY_INITIAL_CREATOR = "initialCreator"; private static final String KEY_KEYWORD = "keyword"; private static final String KEY_LANGUAGE = "language"; - private static final String KEY_PRINT_DATE = "printDate"; - private static final String KEY_PRINTED_BY = "printedBy"; +// private static final String KEY_PRINT_DATE = "printDate"; +// private static final String KEY_PRINTED_BY = "printedBy"; private static final String CUSTOM_PREFIX = "custom:"; @@ -110,6 +110,7 @@ public class OpenDocumentMetadataExtracter extends TikaPoweredMetadataExtracter return new OpenDocumentParser(); } + @SuppressWarnings("deprecation") @Override protected Map extractSpecific(Metadata metadata, Map properties, Map headers) diff --git a/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java index d9a482cdb8..4ff8530375 100644 --- a/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java @@ -162,6 +162,7 @@ public class RFC822MetadataExtracter extends AbstractMappingMetadataExtracter * Extract values from all header fields, including extension fields "X-" */ Set keys = getMapping().keySet(); + @SuppressWarnings("unchecked") Enumeration
headers = mimeMessage.getAllHeaders(); while (headers.hasMoreElements()) { diff --git a/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracterTest.java index 86a731b772..7dee659984 100644 --- a/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracterTest.java @@ -91,6 +91,7 @@ public class RFC822MetadataExtracterTest extends AbstractMetadataExtracterTest * Check that this was sprung-in - if not, then * other tests will fail! */ + @SuppressWarnings("unchecked") public void testHasDateFormats() throws Exception { Set supportedDateFormats; diff --git a/source/java/org/alfresco/repo/content/metadata/TikaAudioMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/TikaAudioMetadataExtracter.java index 9f4030286e..31b74a1221 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaAudioMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaAudioMetadataExtracter.java @@ -155,6 +155,7 @@ public class TikaAudioMetadataExtracter extends TikaPoweredMetadataExtracter * @param props the properties extracted from the file * @return the description */ + @SuppressWarnings("deprecation") private String generateDescription(Metadata metadata) { StringBuilder result = new StringBuilder(); diff --git a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java index 4617a92eae..8a4c01b044 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java @@ -225,7 +225,8 @@ public class TikaAutoMetadataExtracterTest extends AbstractMetadataExtracterTest * Instead, these will be handled by the Auto Tika Parser, and * this test ensures that they are */ - public void testImageVideo() throws Throwable { + @SuppressWarnings("deprecation") +public void testImageVideo() throws Throwable { Map p; // Image diff --git a/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java index 3217a49c0d..b527fc2ddf 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java @@ -269,6 +269,7 @@ public abstract class TikaPoweredMetadataExtracter } } + @SuppressWarnings("deprecation") @Override protected Map extractRaw(ContentReader reader) throws Throwable { diff --git a/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java index e7aee2bcd9..e195b65660 100644 --- a/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/xml/XPathMetadataExtracter.java @@ -149,6 +149,7 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp } /** {@inheritDoc} */ + @SuppressWarnings("rawtypes") public Iterator getPrefixes(String namespaceURI) { ParameterCheck.mandatoryString("namespaceURI", namespaceURI); @@ -341,6 +342,7 @@ public class XPathMetadataExtracter extends AbstractMappingMetadataExtracter imp * * @see #setMappingProperties(Properties) */ + @SuppressWarnings("rawtypes") protected void readXPathMappingProperties(Properties xpathMappingProperties) { // Get the namespaces diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java index 08a2a4ba38..c7278c124b 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java @@ -38,6 +38,7 @@ public class ContentTransformerHelper private MimetypeService mimetypeService; private List explicitTransformations; private List supportedTransformations; + private List unsupportedTransformations; /** * @@ -46,6 +47,7 @@ public class ContentTransformerHelper { setExplicitTransformations(Collections. emptyList()); setSupportedTransformations(null); + setUnsupportedTransformations(null); } /** @@ -78,7 +80,7 @@ public class ContentTransformerHelper /** * Restricts the transformations that may be performed even though the transformer - * may perform other transformations. An null value applies no additional restrictions. + * may perform other transformations. An null value applies no additional restrictions. * Even if a list is specified, the * {@link ContentTransformer#isTransformableMimetype(String, String, TransformationOptions)} * method will still be called. @@ -88,6 +90,18 @@ public class ContentTransformerHelper this.supportedTransformations = supportedTransformations; } + /** + * Restricts the transformations that may be performed even though the transformer + * may claim to perform the transformations. An null value applies no additional restrictions. + * Even if a list is specified, the + * {@link ContentTransformer#isTransformableMimetype(String, String, TransformationOptions)} + * method will still be called. + */ + public void setUnsupportedTransformations(List unsupportedTransformations) + { + this.unsupportedTransformations = unsupportedTransformations; + } + /** * Convenience to fetch and check the mimetype for the given content * @@ -131,10 +145,10 @@ public class ContentTransformerHelper public boolean isSupportedTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options) { - boolean result = true; + boolean supported = true; if (supportedTransformations != null) { - result = false; + supported = false; for (SupportedTransformation suportedTransformation : supportedTransformations) { String supportedSourceMimetype = suportedTransformation.getSourceMimetype(); @@ -142,11 +156,25 @@ public class ContentTransformerHelper if ((supportedSourceMimetype == null || sourceMimetype.equals(supportedSourceMimetype)) && (supportedTargetMimetype == null || targetMimetype.equals(supportedTargetMimetype))) { - result = true; + supported = true; break; } } } - return result; + if (supported && unsupportedTransformations != null) + { + for (SupportedTransformation unsuportedTransformation : unsupportedTransformations) + { + String unsupportedSourceMimetype = unsuportedTransformation.getSourceMimetype(); + String unsupportedTargetMimetype = unsuportedTransformation.getTargetMimetype(); + if ((unsupportedSourceMimetype == null || sourceMimetype.equals(unsupportedSourceMimetype)) && + (unsupportedTargetMimetype == null || targetMimetype.equals(unsupportedTargetMimetype))) + { + supported = false; + break; + } + } + } + return supported; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/lock/LockServiceImpl.java b/source/java/org/alfresco/repo/lock/LockServiceImpl.java index ffcb227b79..7ca0561934 100644 --- a/source/java/org/alfresco/repo/lock/LockServiceImpl.java +++ b/source/java/org/alfresco/repo/lock/LockServiceImpl.java @@ -22,7 +22,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -33,7 +32,6 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; -import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.BehaviourFilter; @@ -72,7 +70,6 @@ public class LockServiceImpl implements LockService, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnMoveNodePolicy, CopyServicePolicies.OnCopyNodePolicy, - VersionServicePolicies.BeforeCreateVersionPolicy, VersionServicePolicies.OnCreateVersionPolicy { /** Key to the nodes ref's to ignore when checking for locks */ @@ -148,10 +145,10 @@ public class LockServiceImpl implements LockService, new JavaBehaviour(this, "getCopyCallback")); // Register the onCreateVersion behavior for the version aspect - this.policyComponent.bindClassBehaviour( - VersionServicePolicies.BeforeCreateVersionPolicy.QNAME, - ContentModel.ASPECT_LOCKABLE, - new JavaBehaviour(this, "beforeCreateVersion")); + + // BeforeCreateVersion behavior was removed + // we should be able to version a node regardless of its lock state, see ALF-16540 + this.policyComponent.bindClassBehaviour( VersionServicePolicies.OnCreateVersionPolicy.QNAME, ContentModel.ASPECT_LOCKABLE, @@ -544,16 +541,6 @@ public class LockServiceImpl implements LockService, { return DoNothingCopyBehaviourCallback.getInstance(); } - - /** - * Ensures that node is not locked. - * - * @see #checkForLock(NodeRef) - */ - public void beforeCreateVersion(NodeRef versionableNode) - { - checkForLock(versionableNode); - } /** * OnCreateVersion behaviour for the lock aspect diff --git a/source/java/org/alfresco/repo/rule/MiscellaneousRulesTest.java b/source/java/org/alfresco/repo/rule/MiscellaneousRulesTest.java new file mode 100644 index 0000000000..c451a76537 --- /dev/null +++ b/source/java/org/alfresco/repo/rule/MiscellaneousRulesTest.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2005-2012 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.rule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; +import org.alfresco.repo.action.executer.CopyActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.CopyService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.test.junitrules.AlfrescoPerson; +import org.alfresco.util.test.junitrules.ApplicationContextInit; +import org.alfresco.util.test.junitrules.RunAsFullyAuthenticatedRule; +import org.alfresco.util.test.junitrules.TemporaryNodes; +import org.alfresco.util.test.junitrules.TemporarySites; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.springframework.context.ApplicationContext; + + +/** + * Integration tests for Alfresco Rules. This class does not test the internals of the {@link RuleService} + * but rather sets up and runs common or previously problematic rules use cases and ensures they are correct. + * + * @author Neil Mc Erlean + * @since 4.1.3 + */ +public class MiscellaneousRulesTest +{ + // Static JUnit Rules + public static ApplicationContextInit APP_CTXT_INIT = new ApplicationContextInit(); + public static AlfrescoPerson TEST_USER = new AlfrescoPerson(APP_CTXT_INIT); + + // Rule chain to ensure they run in the right order + @ClassRule public static RuleChain STATIC_RULE_CHAIN = RuleChain.outerRule(APP_CTXT_INIT) + .around(TEST_USER); + + // Non-static JUnit Rules + public RunAsFullyAuthenticatedRule runAsRule = new RunAsFullyAuthenticatedRule(TEST_USER); + public TemporarySites testSites = new TemporarySites(APP_CTXT_INIT); + public TemporaryNodes testNodes = new TemporaryNodes(APP_CTXT_INIT); + + // Rule chain to ensure they run in the right order + @Rule public RuleChain ruleChain = RuleChain.outerRule(runAsRule) + .around(testSites) + .around(testNodes); + + private static ActionService ACTION_SERVICE; + private static CopyService COPY_SERVICE; + private static NodeService NODE_SERVICE; + private static RetryingTransactionHelper TRANSACTION_HELPER; + private static RuleService RULE_SERVICE; + private static SiteService SITE_SERVICE; + + private SiteInfo testSite; + + @BeforeClass public static void initSpringBeans() throws Exception + { + final ApplicationContext appCtxt = APP_CTXT_INIT.getApplicationContext(); + + ACTION_SERVICE = appCtxt.getBean("ActionService", ActionService.class); + COPY_SERVICE = appCtxt.getBean("CopyService", CopyService.class); + NODE_SERVICE = appCtxt.getBean("NodeService", NodeService.class); + TRANSACTION_HELPER = appCtxt.getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + RULE_SERVICE = appCtxt.getBean("RuleService", RuleService.class); + SITE_SERVICE = appCtxt.getBean("SiteService", SiteService.class); + } + + @Before public void createTestData() throws Exception + { + testSite = testSites.createSite("sitePreset", "testSiteName", "testSiteTitle", "test site description", + SiteVisibility.PUBLIC, TEST_USER.getUsername()); + + } + + @Test public void alf14568() throws Exception + { + final NodeRef testSiteDocLib = TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + return SITE_SERVICE.getContainer(testSite.getShortName(), SiteService.DOCUMENT_LIBRARY); + } + }); + assertNotNull("Null doclib", testSiteDocLib); + + // Create four folders - the first (zero'th) will not be used. + final NodeRef[] folders = new NodeRef[4]; + for (int i : new int[] {0, 1, 2, 3}) + { + folders[i] = testNodes.createFolder(testSiteDocLib, "folder" + i, TEST_USER.getUsername()); + } + + // Create an inbound rule for Folder1 - copy to Folder2. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Clashes with the JUnit annotation @Rule + org.alfresco.service.cmr.rule.Rule rule = new org.alfresco.service.cmr.rule.Rule(); + rule.setRuleType(RuleType.INBOUND); + rule.applyToChildren(false); + rule.setRuleDisabled(false); + rule.setTitle("Copy to folder2"); + rule.setExecuteAsynchronously(false); + + Map params = new HashMap(); + params.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, folders[2]); + Action copyAction = ACTION_SERVICE.createAction("copy", params); + rule.setAction(copyAction); + + RULE_SERVICE.saveRule(folders[1], rule); + + return null; + } + }); + + // Create an update rule for Folder2 - copy to Folder3. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + org.alfresco.service.cmr.rule.Rule rule = new org.alfresco.service.cmr.rule.Rule(); + rule.setRuleType(RuleType.UPDATE); + rule.applyToChildren(false); + rule.setRuleDisabled(false); + rule.setTitle("Copy to folder3"); + rule.setExecuteAsynchronously(false); + + Map params = new HashMap(); + params.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, folders[3]); + Action copyAction = ACTION_SERVICE.createAction("copy", params); + rule.setAction(copyAction); + + RULE_SERVICE.saveRule(folders[2], rule); + + return null; + } + }); + + // Now put a file in Folder1. - don't need transaction as one is included within this call + testNodes.createQuickFile(MimetypeMap.MIMETYPE_TEXT_PLAIN, folders[1], "quick.txt", TEST_USER.getUsername()); + + // Which folders is the file in? + final Set foldersContainingFile = new HashSet(); + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for (NodeRef folder : folders) + { + NodeRef child = NODE_SERVICE.getChildByName(folder, ContentModel.ASSOC_CONTAINS, "quick.txt"); + if (child != null) + { + foldersContainingFile.add(folder); + } + } + + return null; + } + }); + + // Now disable all the rules - I don't think this should be necessary, but if we don't do this, the teardown + // parts of the JUnit Rules cause problems in the repo. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for (NodeRef folder : folders) + { + RULE_SERVICE.removeAllRules(folder); + } + + return null; + } + }); + + final Set expectedFolders = new HashSet(); + expectedFolders.add(folders[1]); + expectedFolders.add(folders[2]); + + assertEquals(expectedFolders, foldersContainingFile); + } + + /** + * ALF-14568 doesn't explicitly say so, but there is a related problem on top of the + * 'creating cm:original assoc triggers rule' bug. It is the related 'deleting cm:original assoc triggers rule' bug. + * This test case validates the fix for that issue. + */ + @Test public void alf14568_supplementary() throws Exception + { + final NodeRef testSiteDocLib = TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + return SITE_SERVICE.getContainer(testSite.getShortName(), SiteService.DOCUMENT_LIBRARY); + } + }); + assertNotNull("Null doclib", testSiteDocLib); + + // Create a folder to put our Alfresco Rules on - but don't put any rules on it yet. + final NodeRef ruleFolder = testNodes.createFolder(testSiteDocLib, "ruleFolder", TEST_USER.getUsername()); + + // Create a piece of content outside our Rules folder. + final NodeRef originalContent = testNodes.createQuickFile(MimetypeMap.MIMETYPE_TEXT_PLAIN, + testSiteDocLib, + "original.txt", + TEST_USER.getUsername()); + + // Now copy that node into the Ruled folder, which will create a cm:original assoc. + final NodeRef copyNode = TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + return COPY_SERVICE.copy(originalContent, ruleFolder, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS); + } + }); + + final QName exifAspectQName = QName.createQName("{http://www.alfresco.org/model/exif/1.0}exif"); + + // Only now do we create the update rule on our folder - put a marker aspect on the node. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Clashes with the JUnit annotation @Rule + org.alfresco.service.cmr.rule.Rule rule = new org.alfresco.service.cmr.rule.Rule(); + rule.setRuleType(RuleType.UPDATE); + rule.applyToChildren(false); + rule.setRuleDisabled(false); + rule.setTitle("Put EXIF aspect on changed nodes"); + rule.setExecuteAsynchronously(false); + + Map params = new HashMap(); + params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, exifAspectQName); + Action addAspectAction = ACTION_SERVICE.createAction("add-features", params); + rule.setAction(addAspectAction); + + RULE_SERVICE.saveRule(ruleFolder, rule); + + return null; + } + }); + + // Now delete the original node. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + NODE_SERVICE.deleteNode(originalContent); + + return null; + } + }); + + // The removal of the cm:original association should NOT have triggered the rule. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + assertFalse("Rule executed when it shouldn't have.", NODE_SERVICE.hasAspect(copyNode, exifAspectQName)); + + return null; + } + }); + } +} diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java index 63219a3463..48265a676a 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java @@ -199,7 +199,30 @@ public class RuleTriggerTest extends BaseSpringTest this.nodeService.createAssociation(nodeRef, nodeRef2, ContentModel.ASSOC_CHILDREN); // Check to see if the rule type has been triggered - assertTrue(ruleType.rulesTriggered); + assertTrue(ruleType.rulesTriggered); + } + + public void testOnCreateOriginalAssociationTrigger() + { + NodeRef nodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER).getChildRef(); + NodeRef nodeRef2 = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER).getChildRef(); + + TestRuleType ruleType = createTestRuleType(ON_CREATE_ASSOCIATION_TRIGGER); + assertFalse(ruleType.rulesTriggered); + + // Try and trigger the type + this.nodeService.createAssociation(nodeRef, nodeRef2, ContentModel.ASSOC_ORIGINAL); + + // Check to see if the rule type has been triggered + assertFalse(ruleType.rulesTriggered); } public void testOnDeleteAssociationTrigger() diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/SingleAssocRefPolicyRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/SingleAssocRefPolicyRuleTrigger.java index 3516e0f52b..ecaf9f5d02 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/SingleAssocRefPolicyRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/SingleAssocRefPolicyRuleTrigger.java @@ -18,7 +18,9 @@ */ package org.alfresco.repo.rule.ruletrigger; +import java.util.Collections; import java.util.List; +import java.util.Set; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.service.cmr.repository.AssociationRef; @@ -36,6 +38,7 @@ public class SingleAssocRefPolicyRuleTrigger extends RuleTriggerAbstractBase private String policyNamespace = NamespaceService.ALFRESCO_URI; private String policyName; + private Set excludedAssocTypes = Collections.emptySet(); public void setPolicyNamespace(String policyNamespace) { @@ -47,6 +50,10 @@ public class SingleAssocRefPolicyRuleTrigger extends RuleTriggerAbstractBase this.policyName = policyName; } + public void setExcludedAssociationTypes(Set assocTypes) { + this.excludedAssocTypes = assocTypes; + } + /** * @see org.alfresco.repo.rule.ruletrigger.RuleTrigger#registerRuleTrigger() */ @@ -63,19 +70,23 @@ public class SingleAssocRefPolicyRuleTrigger extends RuleTriggerAbstractBase public void policyBehaviour(AssociationRef assocRef) { - NodeRef nodeRef = assocRef.getSourceRef(); - - if (nodeService.exists(nodeRef)) + final QName assocTypeQName = assocRef.getTypeQName(); + if ( !excludedAssocTypes.contains(assocTypeQName)) { - List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef parentAssocRef : parentsAssocRefs) + NodeRef nodeRef = assocRef.getSourceRef(); + + if (nodeService.exists(nodeRef)) { - triggerRules(parentAssocRef.getParentRef(), nodeRef); - if (logger.isDebugEnabled() == true) + List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef parentAssocRef : parentsAssocRefs) { - logger.debug( - "OnUpdateAssoc rule triggered (parent); " + - "nodeRef=" + parentAssocRef.getParentRef()); + triggerRules(parentAssocRef.getParentRef(), nodeRef); + if (logger.isDebugEnabled() == true) + { + logger.debug( + "OnUpdateAssoc rule triggered (parent); " + + "nodeRef=" + parentAssocRef.getParentRef()); + } } } } diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java index b8c271cdf3..abf35f7750 100644 --- a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java @@ -48,7 +48,10 @@ import org.alfresco.util.ScriptPagingDetails; * @author Mark Rogers */ public class ScriptAuthorityService extends BaseScopableProcessorExtension -{ +{ + /** RegEx to split a String on the first space. */ + public static final String ON_FIRST_SPACE = " +"; + /** The group/authority service */ private AuthorityService authorityService; /** The person service */ @@ -468,6 +471,15 @@ public class ScriptAuthorityService extends BaseScopableProcessorExtension filter.add(new Pair(ContentModel.PROP_LASTNAME, nameFilter)); filter.add(new Pair(ContentModel.PROP_USERNAME, nameFilter)); + // In order to support queries for "Alan Smithee", we'll parse these tokens + // and add them in to the query. + final Pair tokenisedName = tokeniseName(nameFilter); + if (tokenisedName != null) + { + filter.add(new Pair(ContentModel.PROP_FIRSTNAME, tokenisedName.getFirst())); + filter.add(new Pair(ContentModel.PROP_LASTNAME, tokenisedName.getSecond())); + } + // Build the sorting. The user controls the primary sort, we supply // additional ones automatically List> sort = new ArrayList>(); @@ -506,4 +518,39 @@ public class ScriptAuthorityService extends BaseScopableProcessorExtension return users; } + + /** + * This method will tokenise a name string in order to extract first name, last name - if possible. + * The split is simple - it's made on the first whitespace within the trimmed nameFilter String. So + *

+ * "Luke Skywalker" becomes ["Luke", "Skywalker"]. + *

+ * "Jar Jar Binks" becomes ["Jar", "Jar Binks"]. + *

+ * "C-3PO" becomes null. + * + * @param nameFilter + * @return A Pair if the String is valid, else null. + */ + private Pair tokeniseName(String nameFilter) + { + Pair result = null; + + if (nameFilter != null) + { + final String trimmedNameFilter = nameFilter.trim(); + + // We can only have a first name and a last name if we have at least 3 characters e.g. "A B". + if (trimmedNameFilter.length() > 3) + { + final String[] tokens = trimmedNameFilter.split(ON_FIRST_SPACE, 2); + if (tokens.length == 2) + { + result = new Pair(tokens[0], tokens[1]); + } + } + } + + return result; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService_RegExTest.java b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService_RegExTest.java new file mode 100644 index 0000000000..944f5ad9a6 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService_RegExTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005-2012 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.security.authority.script; + +import static org.alfresco.repo.security.authority.script.ScriptAuthorityService.ON_FIRST_SPACE; +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +/** + * Simple sanity tests for regular expression {@link ScriptAuthorityService#ON_FIRST_SPACE}. + * + * @author Neil Mc Erlean + * @since 4.1.3 + */ +public class ScriptAuthorityService_RegExTest +{ + @Test public void validateFirstNameLastNameRegEx() throws Exception + { + assertArrayEquals(new String[] {"Luke", "Skywalker"}, "Luke Skywalker".split(ON_FIRST_SPACE, 2)); + + // Surnames with spaces in - yes, this is wrong (I think), but it's what we expect our naive algorithm to do. + assertArrayEquals(new String[] {"Jar", "Jar Binks"}, "Jar Jar Binks".split(ON_FIRST_SPACE, 2)); + + // Too short names (no surname) + assertArrayEquals(new String[] {"C-3PO"}, "C-3PO".split(ON_FIRST_SPACE, 2)); + } +} diff --git a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java index 2e96ee5a88..0171b9fb9c 100644 --- a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java +++ b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java @@ -35,6 +35,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; @@ -66,6 +67,7 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest protected NodeArchiveService nodeArchiveService; protected NodeService nodeService; protected PermissionService permissionService; + protected CheckOutCheckInService checkOutCheckInService; /* * Data used by tests @@ -155,6 +157,7 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest this.nodeArchiveService = (NodeArchiveService) applicationContext.getBean("nodeArchiveService"); this.nodeService = (NodeService)applicationContext.getBean("nodeService"); this.permissionService = (PermissionService)this.applicationContext.getBean("permissionService"); + this.checkOutCheckInService = (CheckOutCheckInService) applicationContext.getBean("checkOutCheckInService"); setVersionService((VersionService)applicationContext.getBean("versionService")); diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index 48ae441c5f..d694503cdb 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -353,11 +353,20 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe // Create the version data object Version version = getVersion(newVersionRef); - // Set the new version label on the 'live' (versioned) node - this.nodeService.setProperty( - nodeRef, - ContentModel.PROP_VERSION_LABEL, - version.getVersionLabel()); + // Disabling behavior to be able to create properties for a locked node, see ALF-16540 + policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_LOCKABLE); + try + { + // Set the new version label on the 'live' (versioned) node + this.nodeService.setProperty( + nodeRef, + ContentModel.PROP_VERSION_LABEL, + version.getVersionLabel()); + } + finally + { + policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_LOCKABLE); + } // Re-enable the auditable aspect (if we turned it off earlier) policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 0a979c290c..ad1e3d6319 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -2052,6 +2052,36 @@ public class VersionServiceImplTest extends BaseVersionStoreTest assertEquals(USER_NAME_A, nodeService.getProperty(versioned, ContentModel.PROP_MODIFIER)); } + /* + * It should be possible to create a version for a locked node, see ALF-16540 + */ + public void testVersionLockedNode() + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // create versionable node and ensure it has the necessary aspect + NodeRef versionableNode = createNewVersionableNode(); + assertEquals(true, nodeService.hasAspect(versionableNode, ContentModel.ASPECT_VERSIONABLE)); + + // add lockable aspect and write lock the node + dbNodeService.addAspect(versionableNode, ContentModel.ASPECT_LOCKABLE, new HashMap()); + assertEquals(true, nodeService.hasAspect(versionableNode, ContentModel.ASPECT_LOCKABLE)); + + checkOutCheckInService.checkout(versionableNode); + + // try to create a version + createVersion(versionableNode); + VersionHistory vh = versionService.getVersionHistory(versionableNode); + assertEquals(1, vh.getAllVersions().size()); + return null; + } + }); + } + public static void main(String ... args) { try