diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 0776ff557b..9382e98557 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -633,6 +633,9 @@ ${lucene.indexer.mergerMergeFactor} + + ${lucene.indexer.mergerMergeBlockingFactor} + ${lucene.indexer.mergerMinMergeDocs} @@ -653,6 +656,9 @@ ${lucene.indexer.mergerTargetOverlayCount} + + ${lucene.indexer.mergerTargetOverlaysBlockingFactor} + ${lucene.indexer.maxDocsForInMemoryMerge} diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 910d8fa4cf..027f41e83d 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -173,6 +173,7 @@ lucene.indexer.maxTypeCacheSize=10000 # lucene.indexer.mergerMaxMergeDocs=1000000 lucene.indexer.mergerMergeFactor=5 +lucene.indexer.mergerMergeBlockingFactor=1 lucene.indexer.mergerMinMergeDocs=1000 # # Properties for delta indexes (not this does not affect the final index segment which will be optimised) @@ -186,6 +187,7 @@ lucene.indexer.writerMinMergeDocs=1000 # lucene.indexer.mergerTargetIndexCount=5 lucene.indexer.mergerTargetOverlayCount=5 +lucene.indexer.mergerTargetOverlaysBlockingFactor=2 lucene.indexer.maxDocsForInMemoryMerge=10000 # # Other lucene properties diff --git a/source/java/org/alfresco/repo/admin/ConfigurationChecker.java b/source/java/org/alfresco/repo/admin/ConfigurationChecker.java index cd2bb0d459..0b945663b4 100644 --- a/source/java/org/alfresco/repo/admin/ConfigurationChecker.java +++ b/source/java/org/alfresco/repo/admin/ConfigurationChecker.java @@ -231,12 +231,23 @@ public class ConfigurationChecker extends AbstractLifecycleBean { if (storeRef.getProtocol().equals(StoreRef.PROTOCOL_AVM)) { - IndexMode storeIndexMode = avmSnapShotTriggeredIndexingMethodInterceptor.getIndexMode(storeRef.getIdentifier()); - if (storeIndexMode.equals(IndexMode.UNINDEXED)) + if (avmSnapShotTriggeredIndexingMethodInterceptor.isIndexingEnabled()) + { + IndexMode storeIndexMode = avmSnapShotTriggeredIndexingMethodInterceptor.getIndexMode(storeRef.getIdentifier()); + if (storeIndexMode.equals(IndexMode.UNINDEXED)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Skipping index check for store: " + storeRef + " (unindexed AVM store)"); + } + continue; + } + } + else { if (logger.isDebugEnabled()) { - logger.debug("Skipping index for store: " + storeRef + " (unindexed AVM store)"); + logger.debug("Skipping index check for store: " + storeRef + " (AVM indexing is disabled)"); } continue; } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 723eacf110..c0efad2cf7 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -64,6 +64,7 @@ import org.alfresco.service.cmr.search.QueryParameterDefinition; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; import org.alfresco.util.SearchLanguageConversion; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -571,23 +572,18 @@ public class FileFolderServiceImpl implements FileFolderService targetParentRef = assocRef.getParentRef(); } + boolean changedParent = !targetParentRef.equals(assocRef.getParentRef()); // there is nothing to do if both the name and parent folder haven't changed - if (targetParentRef.equals(assocRef.getParentRef())) + if (!nameChanged && !changedParent) { - if (newName.equals(beforeFileInfo.getName())) - { - if (logger.isDebugEnabled()) - { - logger.debug("Doing nothing - neither filename or parent has changed: \n" + - " parent: " + targetParentRef + "\n" + - " before: " + beforeFileInfo + "\n" + - " new name: " + newName); - } - return beforeFileInfo; - } - else if (newName.equalsIgnoreCase(beforeFileInfo.getName())) + if (logger.isDebugEnabled()) { + logger.debug("Doing nothing - neither filename or parent has changed: \n" + + " parent: " + targetParentRef + "\n" + + " before: " + beforeFileInfo + "\n" + + " new name: " + newName); } + return beforeFileInfo; } QName existingQName = assocRef.getQName(); @@ -629,13 +625,29 @@ public class FileFolderServiceImpl implements FileFolderService // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) if (!isSystemPath(sourceNodeRef)) { - // move the node so that the association moves as well - ChildAssociationRef newAssocRef = nodeService.moveNode( - sourceNodeRef, - targetParentRef, - assocTypeQname, - qname); - targetNodeRef = newAssocRef.getChildRef(); + // The cm:name might clash with another node in the target location. + if (nameChanged) + { + // The name will be changing, so we really need to set the node's name to the new + // name. This can't be done at the same time as the move - to avoid incorrect violations + // of the name constraints, the cm:name is set to something random and will be reset + // to the correct name later. + nodeService.setProperty(sourceNodeRef, ContentModel.PROP_NAME, GUID.generate()); + } + try + { + // move the node so that the association moves as well + ChildAssociationRef newAssocRef = nodeService.moveNode( + sourceNodeRef, + targetParentRef, + assocTypeQname, + qname); + targetNodeRef = newAssocRef.getChildRef(); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(targetParentRef, newName); + } } else { @@ -647,7 +659,7 @@ public class FileFolderServiceImpl implements FileFolderService { try { - // copy the node + // Copy the node. The cm:name will be dropped and reset later. targetNodeRef = copyService.copy( sourceNodeRef, targetParentRef, diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index 90869cdb72..d32d4c657d 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -64,13 +64,12 @@ import org.springframework.context.ApplicationContext; /** * @see org.alfresco.repo.model.filefolder.FileFolderServiceImpl - * * @author Derek Hulley */ public class FileFolderServiceImplTest extends TestCase { private static final String IMPORT_VIEW = "filefolder/filefolder-test-import.xml"; - + private static final String NAME_L0_FILE_A = "L0- File A"; private static final String NAME_L0_FILE_B = "L0- File B"; private static final String NAME_L0_FOLDER_A = "L0- Folder A"; @@ -83,7 +82,7 @@ public class FileFolderServiceImplTest extends TestCase private static final String NAME_L1_FILE_C = "L1- File C (%_)"; private static final String NAME_CHECK_FILE = "CHECK_FILE"; private static final String NAME_CHECK_FOLDER = "CHECK_FOLDER"; - + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private TransactionService transactionService; @@ -93,7 +92,7 @@ public class FileFolderServiceImplTest extends TestCase private UserTransaction txn; private NodeRef rootNodeRef; private NodeRef workingRootNodeRef; - + @Override public void setUp() throws Exception { @@ -102,29 +101,29 @@ public class FileFolderServiceImplTest extends TestCase nodeService = serviceRegistry.getNodeService(); fileFolderService = serviceRegistry.getFileFolderService(); dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); - AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx + .getBean("authenticationComponent"); // start the transaction txn = transactionService.getUserTransaction(); txn.begin(); - + // downgrade integrity IntegrityChecker.setWarnInTransaction(); - + // authenticate authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); - + // create a test store - StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); + StoreRef storeRef = nodeService + .createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); - + // create a folder to import into - workingRootNodeRef = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.ALFRESCO_URI, "working root"), - ContentModel.TYPE_FOLDER).getChildRef(); - + workingRootNodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "working root"), ContentModel.TYPE_FOLDER) + .getChildRef(); + // import the test data ImporterService importerService = serviceRegistry.getImporterService(); Location importLocation = new Location(workingRootNodeRef); @@ -136,7 +135,7 @@ public class FileFolderServiceImplTest extends TestCase Reader reader = new InputStreamReader(is); importerService.importView(reader, importLocation, null, null); } - + public void tearDown() throws Exception { try @@ -157,7 +156,11 @@ public class FileFolderServiceImplTest extends TestCase * @param expectedFolderCount the number of uniquely named folders expected * @param expectedNames the names of the files and folders expected */ - private void checkFileList(List files, int expectedFileCount, int expectedFolderCount, String[] expectedNames) + private void checkFileList( + List files, + int expectedFileCount, + int expectedFolderCount, + String[] expectedNames) { int fileCount = 0; int folderCount = 0; @@ -182,70 +185,61 @@ public class FileFolderServiceImplTest extends TestCase assertEquals("Incorrect number of files", expectedFileCount, fileCount); assertEquals("Incorrect number of folders", expectedFolderCount, folderCount); } - + public void testShallowFilesAndFoldersList() throws Exception { List files = fileFolderService.list(workingRootNodeRef); // check - String[] expectedNames = new String[] {NAME_L0_FILE_A, NAME_L0_FILE_B, NAME_L0_FOLDER_A, NAME_L0_FOLDER_B, NAME_L0_FOLDER_C}; + String[] expectedNames = new String[] + { NAME_L0_FILE_A, NAME_L0_FILE_B, NAME_L0_FOLDER_A, NAME_L0_FOLDER_B, NAME_L0_FOLDER_C }; checkFileList(files, 2, 3, expectedNames); } - + public void testShallowFilesOnlyList() throws Exception { List files = fileFolderService.listFiles(workingRootNodeRef); // check - String[] expectedNames = new String[] {NAME_L0_FILE_A, NAME_L0_FILE_B}; + String[] expectedNames = new String[] + { NAME_L0_FILE_A, NAME_L0_FILE_B }; checkFileList(files, 2, 0, expectedNames); } - + public void testShallowFoldersOnlyList() throws Exception { List files = fileFolderService.listFolders(workingRootNodeRef); // check - String[] expectedNames = new String[] {NAME_L0_FOLDER_A, NAME_L0_FOLDER_B, NAME_L0_FOLDER_C}; + String[] expectedNames = new String[] + { NAME_L0_FOLDER_A, NAME_L0_FOLDER_B, NAME_L0_FOLDER_C }; checkFileList(files, 0, 3, expectedNames); } - + public void testShallowFileSearch() throws Exception { - List files = fileFolderService.search( - workingRootNodeRef, - NAME_L0_FILE_B, - true, - false, - false); + List files = fileFolderService.search(workingRootNodeRef, NAME_L0_FILE_B, true, false, false); // check - String[] expectedNames = new String[] {NAME_L0_FILE_B}; + String[] expectedNames = new String[] + { NAME_L0_FILE_B }; checkFileList(files, 1, 0, expectedNames); } - + public void testDeepFilesAndFoldersSearch() throws Exception { - List files = fileFolderService.search( - workingRootNodeRef, - "?1-*", - true, - true, - true); + List files = fileFolderService.search(workingRootNodeRef, "?1-*", true, true, true); // check - String[] expectedNames = new String[] {NAME_L1_FOLDER_A, NAME_L1_FOLDER_B, NAME_L1_FILE_A, NAME_L1_FILE_B, NAME_L1_FILE_C}; + String[] expectedNames = new String[] + { NAME_L1_FOLDER_A, NAME_L1_FOLDER_B, NAME_L1_FILE_A, NAME_L1_FILE_B, NAME_L1_FILE_C }; checkFileList(files, 3, 2, expectedNames); } - + public void testDeepFilesOnlySearch() throws Exception { - List files = fileFolderService.search( - workingRootNodeRef, - "?1-*", - true, - false, - true); + List files = fileFolderService.search(workingRootNodeRef, "?1-*", true, false, true); // check - String[] expectedNames = new String[] {NAME_L1_FILE_A, NAME_L1_FILE_B, NAME_L1_FILE_C}; + String[] expectedNames = new String[] + { NAME_L1_FILE_A, NAME_L1_FILE_B, NAME_L1_FILE_C }; checkFileList(files, 3, 0, expectedNames); } - + /** * Helper to fetch a file or folder by name * @@ -258,9 +252,8 @@ public class FileFolderServiceImplTest extends TestCase List results = fileFolderService.search(workingRootNodeRef, name, !isFolder, isFolder, true); if (results.size() > 1) { - throw new AlfrescoRuntimeException("Name is not unique in hierarchy: \n" + - " name: " + name + "\n" + - " is folder: " + isFolder); + throw new AlfrescoRuntimeException("Name is not unique in hierarchy: \n" + " name: " + name + "\n" + + " is folder: " + isFolder); } else if (results.size() == 0) { @@ -287,7 +280,7 @@ public class FileFolderServiceImplTest extends TestCase assertNotNull(fileInfo); assertFalse(fileInfo.isFolder()); } - + public void testRenameNormal() throws Exception { FileInfo folderInfo = getByName(NAME_L0_FOLDER_A, true); @@ -301,7 +294,7 @@ public class FileFolderServiceImplTest extends TestCase checkInfo = getByName(newName, true); assertNotNull("Folder info for new name is not present", checkInfo); } - + public void testRenameWithoutAssocQNameChange() throws Exception { FileInfo folderInfo = getByName(NAME_L0_FOLDER_A, true); @@ -309,33 +302,25 @@ public class FileFolderServiceImplTest extends TestCase NodeRef folderNodeRef = folderInfo.getNodeRef(); // Create a child file QName assocQName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "abc"); - NodeRef newFileNodeRef = fileFolderService.create( - folderNodeRef, - "AnotherFile.txt", - ContentModel.TYPE_CONTENT, + NodeRef newFileNodeRef = fileFolderService.create(folderNodeRef, "AnotherFile.txt", ContentModel.TYPE_CONTENT, assocQName).getNodeRef(); // Make sure that the correct association QName was used QName checkQName = nodeService.getPrimaryParent(newFileNodeRef).getQName(); - assertEquals( - "The given assoc QName was not used for the path", - assocQName, - checkQName); + assertEquals("The given assoc QName was not used for the path", assocQName, checkQName); // Rename String newName = "AnotherFile-new.txt"; folderInfo = fileFolderService.rename(newFileNodeRef, newName); // Make sure that the association QName did not change checkQName = nodeService.getPrimaryParent(newFileNodeRef).getQName(); - assertEquals( - "The given assoc QName was not used for the path after a rename", - assocQName, - nodeService.getPrimaryParent(newFileNodeRef).getQName()); + assertEquals("The given assoc QName was not used for the path after a rename", assocQName, nodeService + .getPrimaryParent(newFileNodeRef).getQName()); } - + public void testRenameDuplicate() throws Exception { FileInfo folderInfo = getByName(NAME_L0_FOLDER_A, true); assertNotNull(folderInfo); - // rename duplicate. A file with that name already exists + // rename duplicate. A file with that name already exists String newName = NAME_L0_FILE_A; try { @@ -347,9 +332,15 @@ public class FileFolderServiceImplTest extends TestCase // expected } } - + public void testMove() throws Exception { + // we are testing failures as well + txn.commit(); + // start a new one + txn = transactionService.getNonPropagatingUserTransaction(); + txn.begin(); + FileInfo folderToMoveInfo = getByName(NAME_L1_FOLDER_A, true); assertNotNull(folderToMoveInfo); NodeRef folderToMoveRef = folderToMoveInfo.getNodeRef(); @@ -372,8 +363,34 @@ public class FileFolderServiceImplTest extends TestCase { // expected } + + txn.rollback(); + txn = transactionService.getNonPropagatingUserTransaction(); + txn.begin(); + + // Move a file to a new location + FileInfo fileA = getByName(NAME_L1_FILE_A, false); + FileInfo folderB = getByName(NAME_L0_FOLDER_B, true); + fileFolderService.copy(fileA.getNodeRef(), folderB.getNodeRef(), null); + try + { + // Move to a target folder without a rename and expecting a name clash + fileFolderService.move(fileA.getNodeRef(), folderB.getNodeRef(), null); + fail("Duplicately-named file in target folder was not detected"); + } + catch (FileExistsException e) + { + // Expected + } + + txn.rollback(); + txn = transactionService.getNonPropagatingUserTransaction(); + txn.begin(); + + // Move to a target folder but with a rename to avoid the name clash + fileFolderService.move(fileA.getNodeRef(), folderB.getNodeRef(), NAME_L1_FILE_B); } - + public void testCopy() throws Exception { FileInfo folderToCopyInfo = getByName(NAME_L1_FOLDER_A, true); @@ -400,7 +417,7 @@ public class FileFolderServiceImplTest extends TestCase // expected } } - + public void testCreateFolder() throws Exception { // we are testing failures as well @@ -408,7 +425,7 @@ public class FileFolderServiceImplTest extends TestCase // start a new one txn = transactionService.getNonPropagatingUserTransaction(); txn.begin(); - + FileInfo parentFolderInfo = getByName(NAME_L0_FOLDER_A, true); assertNotNull(parentFolderInfo); NodeRef parentFolderRef = parentFolderInfo.getNodeRef(); @@ -461,7 +478,8 @@ public class FileFolderServiceImplTest extends TestCase M2Type testType = testModel.createType("t111:subfolder"); testType.setParentName("cm:" + ContentModel.TYPE_FOLDER.getLocalName()); dictionaryDAO.putModel(testModel); - fileFolderService.create(parentFolderRef, "Legal subtype of folder", QName.createQName(testNs, "subfolder")); + fileFolderService + .create(parentFolderRef, "Legal subtype of folder", QName.createQName(testNs, "subfolder")); } catch (Throwable e) { @@ -478,17 +496,17 @@ public class FileFolderServiceImplTest extends TestCase assertTrue("Node not created", nodeService.exists(fileInfo.getNodeRef())); assertFalse("File type expected", fileInfo.isFolder()); } - + public void testCreateFile() throws Exception { - + } - + public void testCreateInRoot() throws Exception { fileFolderService.create(rootNodeRef, "New Folder", ContentModel.TYPE_FOLDER); } - + public void testMakeFolders() throws Exception { // create a completely new path below the root @@ -497,12 +515,14 @@ public class FileFolderServiceImplTest extends TestCase namePath.add("BBB"); namePath.add("CCC"); namePath.add("DDD"); - - FileInfo lastFileInfo = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, ContentModel.TYPE_FOLDER); + + FileInfo lastFileInfo = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, + ContentModel.TYPE_FOLDER); assertNotNull("First makeFolder failed", lastFileInfo); // check that a repeat works - - FileInfo lastFileInfoAgain = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, ContentModel.TYPE_FOLDER); + + FileInfo lastFileInfoAgain = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, + ContentModel.TYPE_FOLDER); assertNotNull("Repeat makeFolders failed", lastFileInfoAgain); assertEquals("Repeat created new leaf", lastFileInfo.getNodeRef(), lastFileInfoAgain.getNodeRef()); // check that it worked @@ -518,7 +538,7 @@ public class FileFolderServiceImplTest extends TestCase i++; } } - + /** * Lucene only indexes terms that are 3 characters or more */ @@ -530,34 +550,36 @@ public class FileFolderServiceImplTest extends TestCase namePath.add("B"); namePath.add("C"); namePath.add("D"); - - FileInfo lastFileInfo = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, ContentModel.TYPE_FOLDER); + + FileInfo lastFileInfo = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, + ContentModel.TYPE_FOLDER); assertNotNull("First makeFolder failed", lastFileInfo); // check that a repeat works - - FileInfo lastFileInfoAgain = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, ContentModel.TYPE_FOLDER); + + FileInfo lastFileInfoAgain = FileFolderServiceImpl.makeFolders(fileFolderService, rootNodeRef, namePath, + ContentModel.TYPE_FOLDER); assertNotNull("Repeat makeFolders failed", lastFileInfoAgain); assertEquals("Repeat created new leaf", lastFileInfo.getNodeRef(), lastFileInfoAgain.getNodeRef()); } - + public void testGetNamePath() throws Exception { FileInfo fileInfo = getByName(NAME_L1_FILE_A, false); assertNotNull(fileInfo); NodeRef nodeRef = fileInfo.getNodeRef(); - + List infoPaths = fileFolderService.getNamePath(workingRootNodeRef, nodeRef); assertEquals("Not enough elements", 2, infoPaths.size()); assertEquals("First level incorrent", NAME_L0_FOLDER_A, infoPaths.get(0).getName()); assertEquals("Second level incorrent", NAME_L1_FILE_A, infoPaths.get(1).getName()); - + // pass in a null root and make sure that it still works infoPaths = fileFolderService.getNamePath(null, nodeRef); assertEquals("Not enough elements", 3, infoPaths.size()); assertEquals("First level incorrent", workingRootNodeRef.getId(), infoPaths.get(0).getName()); assertEquals("Second level incorrent", NAME_L0_FOLDER_A, infoPaths.get(1).getName()); assertEquals("Third level incorrent", NAME_L1_FILE_A, infoPaths.get(2).getName()); - + // check that a non-aligned path is detected NodeRef startRef = getByName(NAME_L0_FOLDER_B, true).getNodeRef(); try @@ -570,7 +592,7 @@ public class FileFolderServiceImplTest extends TestCase // expected } } - + public void testSearchSimple() throws Exception { FileInfo folderInfo = getByName(NAME_L0_FOLDER_A, true); @@ -586,28 +608,28 @@ public class FileFolderServiceImplTest extends TestCase FileInfo checkInfo = getByName(NAME_L1_FILE_A, false); assertEquals("Incorrect node found", checkInfo.getNodeRef(), fileNodeRef); } - + public void testResolveNamePath() throws Exception { FileInfo fileInfo = getByName(NAME_L1_FILE_A, false); List pathElements = new ArrayList(3); pathElements.add(NAME_L0_FOLDER_A); pathElements.add(NAME_L1_FILE_A); - + FileInfo fileInfoCheck = fileFolderService.resolveNamePath(workingRootNodeRef, pathElements); assertNotNull("File info not found", fileInfoCheck); assertEquals("Path not resolved to correct node", fileInfo.getNodeRef(), fileInfoCheck.getNodeRef()); } - + public void testGetReaderWriter() throws Exception { // testing a failure txn.commit(); txn = transactionService.getUserTransaction(); txn.begin(); - + FileInfo dirInfo = getByName(NAME_L0_FOLDER_A, true); - + UserTransaction rollbackTxn = null; try { @@ -624,9 +646,9 @@ public class FileFolderServiceImplTest extends TestCase { rollbackTxn.rollback(); } - + FileInfo fileInfo = getByName(NAME_L1_FILE_A, false); - + ContentWriter writer = fileFolderService.getWriter(fileInfo.getNodeRef()); assertNotNull("Writer is null", writer); // write some content @@ -638,23 +660,22 @@ public class FileFolderServiceImplTest extends TestCase String checkContent = reader.getContentString(); assertEquals("Content mismatch", content, checkContent); } - + public void testLongFileNames() throws Exception { - String fileName = - "12345678901234567890123456789012345678901234567890" + - "12345678901234567890123456789012345678901234567890" + - "12345678901234567890123456789012345678901234567890" + - "12345678901234567890123456789012345678901234567890" + - "12345678901234567890123456789012345678901234567890" + - "12345678901234567890123456789012345678901234567890"; + String fileName = "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890"; FileInfo fileInfo = fileFolderService.create(workingRootNodeRef, fileName, ContentModel.TYPE_CONTENT); // see if we can get it again NodeRef fileNodeRef = fileFolderService.searchSimple(workingRootNodeRef, fileName); assertNotNull("Long filename not found", fileNodeRef); assertEquals(fileInfo.getNodeRef(), fileNodeRef); } - + /** * Validates ACT-7225 */ diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index 56b435dfe8..ca70194c75 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -184,7 +184,8 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements static { DUPLICATE_CHILD_NAME_EXCEPTIONS = new Class[] { - ConstraintViolationException.class + ConstraintViolationException.class, + DataIntegrityViolationException.class }; } @@ -1956,75 +1957,97 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } } + /** + * Explicitly flushes the session looking out for {@link #DUPLICATE_CHILD_NAME_EXCEPTIONS exceptions} + * indicating that the child association name constraint has been violated. + *

+ * NOTE: The Hibernate session will be flushed prior to calling the callback. This is necessary + * to prevent legitimate other contstraint violations from being dressed up as + * {@link DuplicateChildNodeNameException}. + * + * @param childAssocChangingCallback the callback in which the child assoc is modified + * @return Returns the callback's result + */ + @SuppressWarnings("unchecked") + private Object writeChildAssocChanges( + HibernateCallback childAssocChangingCallback, + NodeRef parentNodeRef, + QName assocTypeQName, + String childName) + { + // Make sure there are no outstanding changes to flush + DirtySessionMethodInterceptor.flushSession(getSession(false)); + // Call the callback and dig into any exception + try + { + Object ret = getHibernateTemplate().execute(childAssocChangingCallback); + // Now flush. Note that we *force* it to flush as the dirty flag will not have been set. + DirtySessionMethodInterceptor.flushSession(getSession(false), true); + // No clashes + return ret; + } + catch (Throwable e) + { + Throwable constraintViolation = (Throwable) ExceptionStackUtil.getCause( + e, + DUPLICATE_CHILD_NAME_EXCEPTIONS); + if (constraintViolation == null) + { + // It was something else + RuntimeException ee = AlfrescoRuntimeException.makeRuntimeException( + e, "Exception while flushing child assoc to database"); + throw ee; + } + else + { + if (isDebugEnabled) + { + logger.debug( + "Duplicate child association detected: \n" + + " Parent node: " + parentNodeRef + "\n" + + " Child node name: " + childName, + e); + } + throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName); + } + } + } + public Pair newChildAssoc( Long parentNodeId, Long childNodeId, - boolean isPrimary, + final boolean isPrimary, final QName assocTypeQName, - QName assocQName, + final QName assocQName, String newName) { final Node parentNode = (Node) getSession().get(NodeImpl.class, parentNodeId); - Node childNode = (Node) getSession().get(NodeImpl.class, childNodeId); + final Node childNode = (Node) getSession().get(NodeImpl.class, childNodeId); final Pair childNameUnique = getChildNameUnique(assocTypeQName, newName); final ChildAssoc assoc = new ChildAssocImpl(); - assoc.setTypeQName(qnameDAO, assocTypeQName); - assoc.setChildNodeName(childNameUnique.getFirst()); - assoc.setChildNodeNameCrc(childNameUnique.getSecond()); - assoc.setQName(qnameDAO, assocQName); - assoc.setIsPrimary(isPrimary); - assoc.setIndex(-1); - // maintain inverse sets - assoc.buildAssociation(parentNode, childNode); - // Make sure that all changes to the session are persisted so that we know if any - // failures are from the constraint or not - DirtySessionMethodInterceptor.flushSession(getSession(false)); - Long assocId = (Long) getHibernateTemplate().execute(new HibernateCallback() + HibernateCallback newAssocCallback = new HibernateCallback() { - @SuppressWarnings("unchecked") - public Object doInHibernate(Session session) + public Object doInHibernate(Session session) throws HibernateException, SQLException { - try - { - try - { - Object result = session.save(assoc); - DirtySessionMethodInterceptor.flushSession(session, true); - return result; - } - catch (Throwable e) - { - ConstraintViolationException constraintViolation = (ConstraintViolationException) ExceptionStackUtil.getCause( - e, - DUPLICATE_CHILD_NAME_EXCEPTIONS); - if (constraintViolation == null) - { - // It was something else - RuntimeException ee = AlfrescoRuntimeException.makeRuntimeException( - e, "Exception while flushing child assoc to database"); - throw ee; - } - else - { - throw constraintViolation; - } - } - } - catch (ConstraintViolationException e) - { - // There is already an entity - if (isDebugEnabled) - { - logger.debug("Duplicate child association detected: \n" + " Parent Node: " - + parentNode.getId() + "\n" + " Child Name Used: " + childNameUnique, e); - } - throw new DuplicateChildNodeNameException(parentNode.getNodeRef(), assocTypeQName, childNameUnique - .getFirst()); - } + assoc.setTypeQName(qnameDAO, assocTypeQName); + assoc.setChildNodeName(childNameUnique.getFirst()); + assoc.setChildNodeNameCrc(childNameUnique.getSecond()); + assoc.setQName(qnameDAO, assocQName); + assoc.setIsPrimary(isPrimary); + assoc.setIndex(-1); + // maintain inverse sets + assoc.buildAssociation(parentNode, childNode); + // Save it + return session.save(assoc); } - }); + }; + Long assocId = (Long) writeChildAssocChanges( + newAssocCallback, + parentNode.getNodeRef(), + assocTypeQName, + childNameUnique.getFirst()); // Add it to the cache Set oldParentAssocIds = parentAssocsCache.get(childNode.getId()); @@ -2085,32 +2108,14 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { childAssoc.setChildNodeName(childNameUnique.getFirst()); childAssoc.setChildNodeNameCrc(childNameUnique.getSecond().longValue()); - // Flush again to force a DB constraint here - try - { - DirtySessionMethodInterceptor.flushSession(session, true); - // Done - return null; - } - catch (ConstraintViolationException e) - { - // There is already an entity - if (isDebugEnabled) - { - logger.debug("Duplicate child association detected: \n" + " Parent Node: " - + parentNode.getId() + "\n" + " Child Name Used: " + childNameUnique, e); - } - - throw new DuplicateChildNodeNameException(parentNode.getNodeRef(), childAssoc - .getTypeQName(qnameDAO), childNameUnique.getFirst()); - } + return null; } }; - - // Make sure that all changes to the session are persisted so that we know if any - // failures are from the constraint or not - DirtySessionMethodInterceptor.flushSession(getSession(false)); - getHibernateTemplate().execute(callback); + writeChildAssocChanges( + callback, + parentNode.getNodeRef(), + childAssoc.getTypeQName(qnameDAO), + childName); // Done if (isDebugEnabled) @@ -2169,9 +2174,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements Long childAssocId, Long parentNodeId, Long childNodeId, - QName assocTypeQName, - QName assocQName, - int index, + final QName assocTypeQName, + final QName assocQName, + final int index, String childName) { final ChildAssoc childAssoc = getChildAssocNotNull(childAssocId); @@ -2182,19 +2187,31 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements final Node newParentNode = getNodeNotNull(parentNodeId); final Node newChildNode = getNodeNotNull(childNodeId); final NodeRef newChildNodeRef = newChildNode.getNodeRef(); + final Pair childNameUnique = getChildNameUnique(assocTypeQName, childName); // Reset the cm:name duplicate handling. This has to be redone, if required. - Pair childNameUnique = getChildNameUnique(assocTypeQName, childName); - childAssoc.setChildNodeName(childNameUnique.getFirst()); - childAssoc.setChildNodeNameCrc(childNameUnique.getSecond()); - - childAssoc.buildAssociation(newParentNode, newChildNode); - childAssoc.setTypeQName(qnameDAO, assocTypeQName); - childAssoc.setQName(qnameDAO, assocQName); - if (index >= 0) + HibernateCallback updateChildAssocCallback = new HibernateCallback() { - childAssoc.setIndex(index); - } + public Object doInHibernate(Session session) throws HibernateException, SQLException + { + childAssoc.setChildNodeName(childNameUnique.getFirst()); + childAssoc.setChildNodeNameCrc(childNameUnique.getSecond()); + + childAssoc.buildAssociation(newParentNode, newChildNode); + childAssoc.setTypeQName(qnameDAO, assocTypeQName); + childAssoc.setQName(qnameDAO, assocQName); + if (index >= 0) + { + childAssoc.setIndex(index); + } + return null; + } + }; + writeChildAssocChanges( + updateChildAssocCallback, + newParentNode.getNodeRef(), + assocTypeQName, + childNameUnique.getFirst()); // Record change ID if (oldChildNodeRef.equals(newChildNodeRef)) @@ -3446,6 +3463,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } }; NodeAssoc result = (NodeAssoc) getHibernateTemplate().execute(callback); + if (result == null) + { + return null; + } Pair ret = new Pair(result.getId(), result.getNodeAssocRef(qnameDAO)); return ret; } diff --git a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java index 181ad4419a..9234ead3bb 100644 --- a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java +++ b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java @@ -219,8 +219,18 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptor implements MethodInte { this.defaultMode = defaultMode; } - + /** + * Is snapshot triggered indexing enabled + * + * @return true if indexing is enabled for AVM + */ + public boolean isIndexingEnabled() + { + return enableIndexing; + } + + /** * @param store * @param before * @param after diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java index 457bf74581..fdcd8dc270 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerAndSearcherFactory.java @@ -171,12 +171,16 @@ public abstract class AbstractLuceneIndexerAndSearcherFactory implements LuceneI private int mergerMergeFactor = 5; + private int mergerMergeBlockingFactor = 1; + private int mergerMinMergeDocs = 1000; private int mergerTargetIndexCount = 5; private int mergerTargetOverlayCount = 5; + private int mergerTargetOverlaysBlockingFactor = 2; + private int termIndexInterval =IndexWriter.DEFAULT_TERM_INDEX_INTERVAL; private boolean useNioMemoryMapping = true; @@ -1686,6 +1690,16 @@ public abstract class AbstractLuceneIndexerAndSearcherFactory implements LuceneI { this.mergerMergeFactor = mergerMergeFactor; } + + public int getMergerMergeBlockingFactor() + { + return mergerMergeBlockingFactor; + } + + public void setMergerMergeBlockingFactor(int mergerMergeBlockingFactor) + { + this.mergerMergeBlockingFactor = mergerMergeBlockingFactor; + } public int getMergerMinMergeDocs() { @@ -1716,6 +1730,16 @@ public abstract class AbstractLuceneIndexerAndSearcherFactory implements LuceneI { this.mergerTargetOverlayCount = mergerTargetOverlayCount; } + + public int getMergerTargetOverlaysBlockingFactor() + { + return mergerTargetOverlaysBlockingFactor; + } + + public void setMergerTargetOverlaysBlockingFactor(int mergerTargetOverlaysBlockingFactor) + { + this.mergerTargetOverlaysBlockingFactor = mergerTargetOverlaysBlockingFactor; + } public int getTermIndexInterval() { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneConfig.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneConfig.java index fe896b4a22..4e92ccd51b 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneConfig.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneConfig.java @@ -138,6 +138,14 @@ public interface LuceneConfig */ public int getMergerMergeFactor(); + /** + * The factor by which the merge factor is multiplied to determine the allowable number of indexes before blocking. + * + * @return the factor by which the merge factor is multiplied to determine the allowable number of indexes before + * blocking + */ + public int getMergerMergeBlockingFactor(); + /** * Lucene merger config * @return @@ -150,6 +158,15 @@ public interface LuceneConfig */ public int getMergerTargetOverlayCount(); + /** + * The factor by which the target overlay count is multiplied to determine the allowable number of overlays before + * blocking. + * + * @return the factor by which the target overlay count is multiplied to determine the allowable number of overlays + * before blocking + */ + public int getMergerTargetOverlaysBlockingFactor(); + /** * Target index count. Over this indexes will be merged together. * @return @@ -228,4 +245,5 @@ public interface LuceneConfig * @return */ public ConfigurableApplicationContext getApplicationContext(); + } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index 8d8c8b946c..cc3c8a05c2 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -228,7 +228,7 @@ public class IndexInfo implements IndexMonitor * Lock for the index entries */ private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - + /** * Read only index readers that also do reference counting. */ @@ -315,12 +315,16 @@ public class IndexInfo implements IndexMonitor private int mergerMergeFactor = 5; + private int mergerMergeBlockingFactor = 1; + private int mergerMaxMergeDocs = 1000000; private boolean mergerUseCompoundFile = true; private int mergerTargetOverlays = 5; + private int mergerTargetOverlaysBlockingFactor = 2; + // Common properties for indexers private long writeLockTimeout = IndexWriter.WRITE_LOCK_TIMEOUT; @@ -404,9 +408,11 @@ public class IndexInfo implements IndexMonitor this.writerMaxMergeDocs = config.getWriterMaxMergeDocs(); this.mergerMinMergeDocs = config.getMergerMinMergeDocs(); this.mergerMergeFactor = config.getMergerMergeFactor(); + this.mergerMergeBlockingFactor = config.getMergerMergeBlockingFactor(); this.mergerMaxMergeDocs = config.getMergerMaxMergeDocs(); this.termIndexInterval = config.getTermIndexInterval(); this.mergerTargetOverlays = config.getMergerTargetOverlayCount(); + this.mergerTargetOverlaysBlockingFactor = config.getMergerTargetOverlaysBlockingFactor(); // Work out the relative path of the index try { @@ -1261,7 +1267,38 @@ public class IndexInfo implements IndexMonitor getReadLock(); try { - transition.beforeWithReadLock(id, toDelete, read); + // beforeWithReadLock may indicate that we need to block for the merger to do some work + while (!transition.beforeWithReadLock(id, toDelete, read)) + { + synchronized (merger) + { + // If the merger is scheduled, let's wait for it... + int count = merger.getScheduledCount(); + if (count <= 0) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("CAN'T THROTTLE: " + indexEntries.size()); + } + break; + } + if (s_logger.isDebugEnabled()) + { + s_logger.debug("THROTTLING: " + indexEntries.size()); + } + releaseReadLock(); + try + { + merger.wait(); + } + catch (InterruptedException e) + { + } + } + getReadLock(); + } + + releaseReadLock(); getWriteLock(); try @@ -1361,7 +1398,7 @@ public class IndexInfo implements IndexMonitor */ private interface Transition { - void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException; + boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException; void transition(String id, Set toDelete, Set read) throws IOException; @@ -1375,9 +1412,9 @@ public class IndexInfo implements IndexMonitor */ private class PreparingTransition implements Transition { - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { - // Nothing to do + return true; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -1411,9 +1448,10 @@ public class IndexInfo implements IndexMonitor */ private class PreparedTransition implements Transition { - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { - + // We want to block until the merger has executed if we have more than a certain number of indexes + return indexEntries.size() <= mergerMergeBlockingFactor * mergerMergeFactor + mergerTargetOverlaysBlockingFactor * mergerTargetOverlays; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -1479,9 +1517,9 @@ public class IndexInfo implements IndexMonitor private class CommittingTransition implements Transition { - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { - + return true; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -1513,13 +1551,14 @@ public class IndexInfo implements IndexMonitor ThreadLocal tl = new ThreadLocal(); - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { // Make sure we have set up the reader for the data // ... and close it so we do not up the ref count closeDelta(id); IndexEntry entry = indexEntries.get(id); tl.set(buildReferenceCountingIndexReader(id, entry.getDocumentCount())); + return true; } /** @@ -1594,9 +1633,9 @@ public class IndexInfo implements IndexMonitor private class RollingBackTransition implements Transition { - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { - + return true; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -1628,11 +1667,12 @@ public class IndexInfo implements IndexMonitor { ThreadLocal tl = new ThreadLocal(); - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { closeDelta(id); IndexEntry entry = indexEntries.get(id); tl.set(buildReferenceCountingIndexReader(id, entry.getDocumentCount())); + return true; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -1682,9 +1722,9 @@ public class IndexInfo implements IndexMonitor private class DeletableTransition implements Transition { - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { - + return true; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -1727,9 +1767,9 @@ public class IndexInfo implements IndexMonitor private class ActiveTransition implements Transition { - public void beforeWithReadLock(String id, Set toDelete, Set read) throws IOException + public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { - + return true; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -2906,6 +2946,13 @@ public class IndexInfo implements IndexMonitor private abstract class AbstractSchedulable implements Schedulable, Runnable { ScheduledState scheduledState = ScheduledState.UN_SCHEDULED; + + private int scheduledCount; + + public synchronized int getScheduledCount() + { + return scheduledCount; + } public synchronized void schedule() { @@ -2917,6 +2964,7 @@ public class IndexInfo implements IndexMonitor break; case UN_SCHEDULED: scheduledState = ScheduledState.SCHEDULED; + scheduledCount++; threadPoolExecutor.execute(this); break; case RECOVERY_SCHEDULED: @@ -2931,8 +2979,10 @@ public class IndexInfo implements IndexMonitor { switch (scheduledState) { - case RECOVERY_SCHEDULED: case SCHEDULED: + scheduledCount--; + notifyAll(); + case RECOVERY_SCHEDULED: scheduledState = ScheduledState.UN_SCHEDULED; break; case FAILED: @@ -2977,8 +3027,10 @@ public class IndexInfo implements IndexMonitor { switch (scheduledState) { - case RECOVERY_SCHEDULED: case SCHEDULED: + scheduledCount--; + notifyAll(); + case RECOVERY_SCHEDULED: scheduledState = ScheduledState.FAILED; break; case FAILED: diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index af9ac01c2f..5d13c7abfb 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -418,7 +418,9 @@ public class RetryingTransactionHelper { // Sleep a random amount of time before retrying. // The sleep interval increases with the number of retries. - int sleepIntervalRandom = count > 0 ? random.nextInt(count * retryWaitIncrementMs) : minRetryWaitMs; + int sleepIntervalRandom = (count > 0 && retryWaitIncrementMs > 0) + ? random.nextInt(count * retryWaitIncrementMs) + : minRetryWaitMs; int sleepInterval = Math.min(maxRetryWaitMs, sleepIntervalRandom); sleepInterval = Math.max(sleepInterval, minRetryWaitMs); if (logger.isInfoEnabled() && !logger.isDebugEnabled())