/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeTypeTemplate; import javax.jcr.query.Query; import javax.jcr.security.AccessControlList; import javax.jcr.security.AccessControlManager; import javax.jcr.security.AccessControlPolicyIterator; import javax.jcr.security.Privilege; import org.junit.Before; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.util.FileUtil; import org.modeshape.common.util.IoUtil; import org.modeshape.connector.mock.MockConnector; import org.modeshape.jcr.api.Workspace; import org.modeshape.jcr.api.federation.FederationManager; import org.modeshape.jcr.api.index.IndexColumnDefinitionTemplate; import org.modeshape.jcr.api.index.IndexDefinition; import org.modeshape.jcr.api.index.IndexDefinitionTemplate; import org.modeshape.jcr.api.index.IndexManager; import org.modeshape.jcr.cache.CachedNode; import org.modeshape.jcr.cache.ChildReference; import org.modeshape.jcr.cache.ChildReferences; import org.modeshape.jcr.cache.MutableCachedNode; import org.modeshape.jcr.cache.RepositoryCache; import org.modeshape.jcr.cache.SessionCache; import org.modeshape.jcr.cache.document.DocumentStore; import org.modeshape.jcr.query.JcrQuery; import org.modeshape.jcr.security.SimplePrincipal; import org.modeshape.jcr.value.BinaryKey; import org.modeshape.jcr.value.Property; import org.modeshape.jcr.value.PropertyFactory; import org.modeshape.jcr.value.binary.FileSystemBinaryStore; import org.modeshape.schematic.document.EditableArray; import org.modeshape.schematic.document.EditableDocument; import org.modeshape.schematic.document.Editor; import org.modeshape.schematic.internal.document.MutableDocument; /** * Tests that related to repeatedly starting/stopping repositories (without another repository configured in the @Before and @After * methods). * * @author rhauch * @author hchiorean */ public class JcrRepositoryStartupTest extends MultiPassAbstractTest { @Before public void before() throws Exception { TestingUtil.waitUntilFolderCleanedUp("target/persistent_repository"); } @Test @FixFor( {"MODE-1526", "MODE-1512", "MODE-1617"} ) public void shouldKeepPersistentDataAcrossRestart() throws Exception { String repositoryConfigFile = "config/repo-config-binaries-fs.json"; startRunStop(repository -> { Session session = repository.login(); session.getRootNode().addNode("testNode"); session.save(); // create 2 new workspaces session.getWorkspace().createWorkspace("ws1"); session.getWorkspace().createWorkspace("ws2"); session.logout(); }, repositoryConfigFile); startRunStop(repository -> { Session session = repository.login(); assertNotNull(session.getNode("/testNode")); session.logout(); // check the workspaces were persisted Session newWsSession = repository.login("ws1"); newWsSession.getRootNode().addNode("newWsTestNode"); newWsSession.save(); newWsSession.logout(); Session newWs1Session = repository.login("ws2"); newWs1Session.getWorkspace().deleteWorkspace("ws2"); newWs1Session.logout(); }, repositoryConfigFile); startRunStop(repository -> { Session newWsSession = repository.login("ws1"); assertNotNull(newWsSession.getNode("/newWsTestNode")); newWsSession.logout(); // check a workspace was deleted try { repository.login("ws2"); fail("Workspace was not deleted from the repository"); } catch (NoSuchWorkspaceException e) { // expected } }, repositoryConfigFile); } @Test public void shouldNotImportInitialContentIfWorkspaceContentsChanged() throws Exception { String repositoryConfigFile = "config/repo-config-persistent-cache-initial-content.json"; startRunStop(repository -> { Session ws1Session = repository.login("ws1"); // check the initial import Node node = ws1Session.getNode("/cars"); assertNotNull(node); // remove the node initially imported and add a new one node.remove(); ws1Session.getRootNode().addNode("testNode"); ws1Session.save(); }, repositoryConfigFile); startRunStop(repository -> { Session ws1Session = repository.login("ws1"); try { ws1Session.getNode("/cars"); fail("The initial content should be be re-imported if a workspace is not empty"); } catch (PathNotFoundException e) { // expected } ws1Session.getNode("/testNode"); }, repositoryConfigFile); } @Test @FixFor( "MODE-1716" ) public void shouldPersistExternalProjectionToFederatedNodeMappings() throws Exception { String repositoryConfigFile = "config/repo-config-mock-federation-persistent.json"; startRunStop(repository -> { Session session = repository.login(); Node testRoot = session.getRootNode().addNode("testRoot"); session.save(); FederationManager federationManager = ((Workspace)session.getWorkspace()).getFederationManager(); federationManager.createProjection("/testRoot", "mock-source", MockConnector.DOC1_LOCATION, "federated1"); federationManager.createProjection("/testRoot", "mock-source", MockConnector.DOC2_LOCATION, null); Node doc1Federated = session.getNode("/testRoot/federated1"); assertNotNull(doc1Federated); assertEquals(testRoot.getIdentifier(), doc1Federated.getParent().getIdentifier()); }, repositoryConfigFile); startRunStop(repository -> { Session session = repository.login(); Node testRoot = session.getNode("/testRoot"); assertNotNull(testRoot); Node doc1Federated = session.getNode("/testRoot/federated1"); assertNotNull(doc1Federated); assertEquals(testRoot.getIdentifier(), doc1Federated.getParent().getIdentifier()); Node doc2Federated = session.getNode("/testRoot" + MockConnector.DOC2_LOCATION); assertNotNull(doc2Federated); assertEquals(testRoot.getIdentifier(), doc2Federated.getParent().getIdentifier()); }, repositoryConfigFile); } @Test public void shouldKeepPreconfiguredProjectionsAcrossRestart() throws Exception { String repositoryConfigFile = "config/repo-config-federation-persistent-projections.json"; RepositoryOperation checkPreconfiguredProjection = repository -> { Session session = repository.login(); assertNotNull(session.getNode("/preconfiguredProjection")); }; startRunStop(checkPreconfiguredProjection, repositoryConfigFile); startRunStop(checkPreconfiguredProjection, repositoryConfigFile); } @Test public void shouldCleanStoredProjectionsIfNodesAreDeleted() throws Exception { String repositoryConfigFile = "config/repo-config-mock-federation-persistent.json"; startRunStop(repository -> { Session session = repository.login(); session.getRootNode().addNode("testRoot"); session.save(); FederationManager federationManager = ((Workspace)session.getWorkspace()).getFederationManager(); federationManager.createProjection("/testRoot", "mock-source", MockConnector.DOC1_LOCATION, "federated1"); federationManager.createProjection("/testRoot", "mock-source", MockConnector.DOC2_LOCATION, "federated2"); Node projection = session.getNode("/testRoot/federated1"); assertNotNull(projection); projection.remove(); session.save(); }, repositoryConfigFile); startRunStop(repository -> { Session session = repository.login(); // check the 2nd projection assertNotNull(session.getNode("/testRoot/federated2")); try { session.getNode("/testRoot/federated1"); fail("Projection has not been cleaned up"); } catch (PathNotFoundException e) { // expected } }, repositoryConfigFile); } @Test @FixFor( "MODE-1844" ) public void shouldNotRemainInInconsistentStateIfErrorsOccurOnStartup() throws Exception { // try and start with a config that will produce an exception try { startRunStop(repository -> {}, "config/invalid-repo-config-persistent-initial-content.json"); fail("Expected a repository exception"); } catch (RuntimeException e) { if (!(e.getCause() instanceof RepositoryException)) { throw e; } } ExecutorService executorService = Executors.newSingleThreadExecutor(); try { executorService.submit(()-> startRunStop(repository -> {}, "config/repo-config-persistent-cache-initial-content.json")) .get(10, TimeUnit.SECONDS); // wait the repo to restart or fail } catch (java.util.concurrent.TimeoutException e) { fail("Repository did not restart in the expected amount of time"); } finally { executorService.shutdownNow(); } } @Test @FixFor( "MODE-2031" ) public void shouldRestartWithModifiedCNDFile() throws Exception { startRunStop(repository -> { Session session = repository.login(); Node content = session.getRootNode().addNode("content", "jj:content"); content.setProperty("_name", "name"); content.setProperty("_type", "type"); session.save(); }, "config/repo-config-jj-initial.json"); RepositoryOperation operation = (repository) -> { Session session = repository.login(); Node content = session.getNode("/content"); assertEquals("name", content.getProperty("_name").getString()); assertEquals("type", content.getProperty("_type").getString()); }; startRunStop(operation, "config/repo-config-jj-modified.json"); startRunStop(operation, "config/repo-config-jj-modified.json"); } @Test @FixFor( "MODE-2044" ) public void shouldRun3_6_0UpgradeFunction() throws Exception { // first run is empty, so no upgrades will be performed startRunStop(repository -> { // modify the repository-info document to force an upgrade on the next restart changeLastUpgradeId(repository, Upgrades.ModeShape_3_6_0.INSTANCE.getId() - 1); // create a non-session lock on a node JcrSession session = repository.login(); PropertyFactory propertyFactory = session.context().getPropertyFactory(); AbstractJcrNode node = session.getRootNode().addNode("/test"); node.addMixin("mix:lockable"); session.save(); session.lockManager().lock(node, true, false, Long.MAX_VALUE, null); // manipulate that lock using the system cache to simulate corrupt data SessionCache systemSession = repository.createSystemSession(repository.runningState().context(), false); SystemContent systemContent = new SystemContent(systemSession); ChildReferences childReferences = systemContent.locksNode().getChildReferences(systemSession); assertFalse("No locks found", childReferences.isEmpty()); for (ChildReference childReference : childReferences) { MutableCachedNode lock = systemSession.mutable(childReference.getKey()); lock.setProperty(systemSession, propertyFactory.create(ModeShapeLexicon.IS_DEEP, true)); lock.setProperty(systemSession, propertyFactory.create(ModeShapeLexicon.LOCKED_KEY, node.key().toString())); lock.setProperty(systemSession, propertyFactory.create(ModeShapeLexicon.SESSION_SCOPE, false)); } systemSession.save(); }, "config/repo-config-persistent-no-indexes.json"); // second run should run the upgrade startRunStop(repository -> { // manipulate that lock using the system cache to simulate corrupt data SessionCache systemSession = repository.createSystemSession(repository.runningState().context(), true); SystemContent systemContent = new SystemContent(systemSession); ChildReferences childReferences = systemContent.locksNode().getChildReferences(systemSession); assertFalse("No locks found", childReferences.isEmpty()); for (ChildReference childReference : childReferences) { CachedNode lock = systemSession.getNode(childReference.getKey()); assertNull("Property not removed", lock.getProperty(ModeShapeLexicon.IS_DEEP, systemSession)); assertNull("Property not removed", lock.getProperty(ModeShapeLexicon.LOCKED_KEY, systemSession)); assertNull("Property not removed", lock.getProperty(ModeShapeLexicon.SESSION_SCOPE, systemSession)); } }, "config/repo-config-persistent-no-indexes.json"); } @Test @FixFor( "MODE-2049" ) public void shouldStartAndStopRepositoryWithoutMonitoringConfigured() throws Exception { startRunStop(repository -> repository.login().logout(), "config/repo-config-inmemory-local-environment-no-monitoring.json"); } @Test @FixFor( "MODE-1683" ) public void shouldAppendJournalEntriesBetweenRestarts() throws Exception { final List<Integer> recordsOnStartup = new ArrayList<>(2); RepositoryOperation operation = repository -> { Session session = repository.login(); session.getRootNode().addNode("node1"); session.save(); session.logout(); Thread.sleep(100); int records = repository.runningState().journal().allRecords(false).size(); assertTrue(records > 0); recordsOnStartup.add(records); }; startRunStop(operation, "config/repo-config-journaling.json"); startRunStop(operation, "config/repo-config-journaling.json"); int countFirstTime = recordsOnStartup.get(0); int countSecondTime = recordsOnStartup.get(1); assertTrue(countSecondTime > countFirstTime); } @Test @FixFor( "MODE-2100" ) public void shouldAddPredefinedWorkspacesOnRestartViaConfigUpdate() throws Exception { RepositoryConfiguration config = startRunStop(repository -> { JcrSession session = repository.login("default"); session.logout(); session = repository.login("ws1"); session.getWorkspace().createWorkspace("ws2"); session.logout(); session = repository.login("ws2"); session.logout(); }, "config/repo-config-persistent-predefined-ws.json"); final Editor editor = config.edit(); EditableArray predefinedWs = editor.getDocument(RepositoryConfiguration.FieldName.WORKSPACES) .getArray(RepositoryConfiguration.FieldName.PREDEFINED); predefinedWs.add("ws3"); predefinedWs.add("ws4"); JcrRepository repository = new JcrRepository(new RepositoryConfiguration(editor.asMutableDocument(), "updated_config")); repository.start(); try { repository.apply(editor.getChanges()); JcrSession session = repository.login("default"); session.logout(); session = repository.login("ws1"); session.logout(); session = repository.login("ws2"); session.logout(); session = repository.login("ws3"); session.logout(); session = repository.login("ws4"); session.logout(); } finally { TestingUtil.killRepositories(repository); } } @Test @FixFor( "MODE-2100" ) public void shouldAddPredefinedWorkspacesOnRestartViaConfigChange() throws Exception{ startRunStop(repository -> { JcrSession session = repository.login("ws1"); session.getWorkspace().createWorkspace("ws3"); session.logout(); session = repository.login("ws3"); session.logout(); }, "config/repo-config-persistent-predefined-ws.json"); startRunStop(repository -> { JcrSession session = repository.login("default"); session.logout(); session = repository.login("ws1"); session.logout(); session = repository.login("ws2"); session.logout(); session = repository.login("ws3"); session.logout(); }, "config/repo-config-persistent-predefined-ws-update.json"); startRunStop(repository -> repository.login("ws2").logout(), "config/repo-config-persistent-predefined-ws.json"); } @Test @FixFor( "MODE-2142" ) public void shouldAllowChangingNamespacePrefixesInSession() throws Exception { String repositoryConfigFile = "config/repo-config-binaries-fs.json"; final String prefix = "admb"; final String uri = "http://www.admb.be/modeshape/admb/1.0"; final String nodeTypeName = prefix + ":test"; startRunStop(repository -> { Session session = repository.login(); NodeTypeManager nodeTypeManager = session.getWorkspace().getNodeTypeManager(); // First create a namespace for the nodeType which is going to be added session.getWorkspace().getNamespaceRegistry().registerNamespace(prefix, uri); // Start creating a nodeTypeTemplate, keep it basic. NodeTypeTemplate nodeTypeTemplate = nodeTypeManager.createNodeTypeTemplate(); nodeTypeTemplate.setName(nodeTypeName); nodeTypeManager.registerNodeType(nodeTypeTemplate, false); session.getRootNode().addNode("testNode", nodeTypeName); session.save(); Node node = session.getNode("/testNode"); assertEquals(nodeTypeName, node.getPrimaryNodeType().getName()); session.setNamespacePrefix("newPrefix", uri); assertEquals("newPrefix:test", node.getPrimaryNodeType().getName()); }, repositoryConfigFile); startRunStop(repository -> { Session session = repository.login(); Node node = session.getNode("/testNode"); assertEquals(nodeTypeName, node.getPrimaryNodeType().getName()); }, repositoryConfigFile); } @Test @FixFor( "MODE-2167" ) public void shouldDisableACLsIfAllPoliciesAreRemoved() throws Exception { String repositoryConfigFile = "config/repo-config-binaries-fs.json"; startRunStop(repository -> { Session session = repository.login(); Node testNode = session.getRootNode().addNode("testNode"); testNode.addNode("node1"); testNode.addNode("node2"); session.save(); AccessControlManager acm = session.getAccessControlManager(); AccessControlList aclNode1 = getACL(acm, "/testNode/node1"); aclNode1.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acm.setPolicy("/testNode/node1", aclNode1); AccessControlList aclNode2 = getACL(acm, "/testNode/node2"); aclNode2.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acm.setPolicy("/testNode/node2", aclNode2); // access control should not be enabled yet because we haven't saved the session assertFalse(repository.runningState().repositoryCache().isAccessControlEnabled()); session.save(); assertTrue(repository.runningState().repositoryCache().isAccessControlEnabled()); }, repositoryConfigFile); startRunStop(repository -> { assertTrue(repository.runningState().repositoryCache().isAccessControlEnabled()); Session session = repository.login(); AccessControlManager acm = session.getAccessControlManager(); // TODO author=Horia Chiorean date=25-Mar-14 description=Why null here !?! acm.removePolicy("/testNode/node1", null); acm.removePolicy("/testNode/node2", null); session.save(); assertFalse(repository.runningState().repositoryCache().isAccessControlEnabled()); session.getNode("/testNode").remove(); session.save(); }, repositoryConfigFile); } @Test @FixFor( "MODE-2167" ) public void shouldRun4_0_0_Alpha1_UpgradeFunction() throws Exception { String config = "config/repo-config-persistent-no-indexes.json"; // first run is empty, so no upgrades will be performed startRunStop(repository -> { changeLastUpgradeId(repository, Upgrades.ModeShape_4_0_0_Alpha1.INSTANCE.getId() - 1); // modify some ACLs JcrSession session = repository.login(); session.getRootNode().addNode("testNode"); session.save(); AccessControlManager acm = session.getAccessControlManager(); AccessControlList acl = getACL(acm, "/testNode"); acl.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acm.setPolicy("/testNode", acl); session.save(); // remove the new property from 4.0 which actually stores the ACL count to simulate a pre 4.0 repository SessionCache systemSession = repository.createSystemSession(repository.runningState().context(), false); SystemContent systemContent = new SystemContent(systemSession); MutableCachedNode systemNode = systemContent.mutableSystemNode(); systemNode.removeProperty(systemSession, ModeShapeLexicon.ACL_COUNT); systemSession.save(); }, config); // second run should run the upgrade startRunStop(repository -> { // check that the upgrade function correctly added the new property SessionCache systemSession = repository.createSystemSession(repository.runningState().context(), false); SystemContent systemContent = new SystemContent(systemSession); MutableCachedNode systemNode = systemContent.mutableSystemNode(); Property aclCountProp = systemNode.getProperty(ModeShapeLexicon.ACL_COUNT, systemSession); assertNotNull("ACL count property not found after upgrade", aclCountProp); assertEquals(1, Long.valueOf(aclCountProp.getFirstValue().toString()).longValue()); // force a 2nd upgrade changeLastUpgradeId(repository, Upgrades.ModeShape_4_0_0_Alpha1.INSTANCE.getId() - 1); // remove all ACLs JcrSession session = repository.login(); AccessControlManager acm = session.getAccessControlManager(); // TODO author=Horia Chiorean date=25-Mar-14 description=Why null ?! acm.removePolicy("/testNode", null); session.save(); // remove the new property from 4.0 which actually stores the ACL count to simulate a pre 4.0 repository systemNode.removeProperty(systemSession, ModeShapeLexicon.ACL_COUNT); systemSession.save(); }, config); // check that the upgrade disabled ACLs startRunStop(repository -> { SessionCache systemSession = repository.createSystemSession(repository.runningState().context(), true); SystemContent systemContent = new SystemContent(systemSession); CachedNode systemNode = systemContent.systemNode(); Property aclCountProp = systemNode.getProperty(ModeShapeLexicon.ACL_COUNT, systemSession); assertNotNull("ACL count property not found after upgrade", aclCountProp); assertEquals(0, Long.valueOf(aclCountProp.getFirstValue().toString()).longValue()); assertFalse(repository.runningState().repositoryCache().isAccessControlEnabled()); }, config); } @Test @FixFor( "MODE-2176" ) public void shouldAllowExternalSourceChangesBetweenRestarts() throws Exception { prepareExternalDirectory("target/federation_persistent_1"); startRunStop(repository-> { JcrSession session = repository.login(); assertNotNull(session.getNode("/fs1")); assertNotNull(session.getNode("/fs1/file.txt")); assertNotNull(session.getNode("/fs2")); assertNotNull(session.getNode("/fs2/file.txt")); }, "config/repo-config-persistent-cache-fs-connector1.json"); FileUtil.delete("target/federation_persistent_1"); prepareExternalDirectory("target/federation_persistent_2"); // restart with a configuration file which a) has a new external source, b) has changed the directory path of "fs2" and // c) has removed the fs1 projection startRunStop(repository -> { JcrSession session = repository.login(); try { session.getNode("/fs1"); fail("The projection should not have been found"); } catch (PathNotFoundException e) { // expected } assertNotNull(session.getNode("/fs2")); assertNotNull(session.getNode("/fs2/file.txt")); }, "config/repo-config-persistent-cache-fs-connector2.json"); } @Test @FixFor( "MODE-2302" ) public void shouldRun4_0_0_Beta3_UpgradeFunction() throws Exception { FileUtil.delete("target/legacy_fs_binarystore"); String config = "config/repo-config-persistent-legacy-fsbinary.json"; // copy the test-resources legacy structure onto the configured one FileUtil.copy(new File("src/test/resources/legacy_fs_binarystore"), new File("target/legacy_fs_binarystore")); // this is coming from the test resources final BinaryKey binaryKey = new BinaryKey("ef2138973a86a8929eebe7bf52419b7cde73ba0a"); // first run is empty, so no upgrades will be performed but we'll decrement the last upgrade ID to force an upgrade next // restart startRunStop(repository -> { changeLastUpgradeId(repository, Upgrades.ModeShape_4_0_0_Beta3.INSTANCE.getId() - 1); FileSystemBinaryStore binaryStore = (FileSystemBinaryStore)repository.runningState().binaryStore(); assertFalse("No used binaries expected", binaryStore.getAllBinaryKeys().iterator().hasNext()); assertFalse("The binary should not be found", binaryStore.hasBinary(binaryKey)); File mainStorageDirectory = binaryStore.getDirectory(); File[] files = mainStorageDirectory.listFiles(); assertEquals("Just the trash directory was expected", 1, files.length); File trash = files[0]; assertTrue(trash.isDirectory()); }, config); // run the repo a second time, which should run the upgrade startRunStop(repository -> { FileSystemBinaryStore binaryStore = (FileSystemBinaryStore)repository.runningState().binaryStore(); assertFalse("No used binaries expected", binaryStore.getAllBinaryKeys().iterator().hasNext()); assertTrue("The binary should be found", binaryStore.hasBinary(binaryKey)); }, config); } @Test @FixFor( "MODE-2341" ) public void shouldAllowReindexingWithLocalProviderBetweenRestartsWhenMissing() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); // setup the external content prepareExternalDirectory("target/federation_persistent_2"); RepositoryOperation reindexingExternalContentOperation = reindexingExternalContentOperation(); // run 1 startRunStop(reindexingExternalContentOperation, "config/repo-config-persistent-cache-fs-connector2.json"); long indexFolderSize1 = FileUtil.size("target/startup_test_indexes"); // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); // run 2 startRunStop(reindexingExternalContentOperation, "config/repo-config-persistent-cache-fs-connector2.json"); long indexFolderSize2 = FileUtil.size("target/startup_test_indexes"); assertEquals("The sizes of the index folder are different between 2 identical reindex runs", indexFolderSize1, indexFolderSize2); } private RepositoryOperation reindexingExternalContentOperation() { return repository -> { JcrSession session = repository.login(); // sleep a bit to make sure reindexing completes Thread.sleep(100); try { AbstractJcrNode node = session.getNode("/fs2/file.txt"); String createdBy = node.getProperty("jcr:createdBy").getString(); assertNotNull(createdBy); JcrQueryManager jcrQueryManager = session.getWorkspace().getQueryManager(); Query query = jcrQueryManager.createQuery("select file.[jcr:path] from [nt:file] as file where file.[jcr:createdBy]='" + createdBy + "'", JcrQuery.JCR_SQL2); ValidateQuery.validateQuery() .useIndex("nodesByAuthor") .hasNodesAtPaths("/fs2/file.txt") .validate(query, query.execute()); session.getWorkspace().reindex(); ValidateQuery.validateQuery() .useIndex("nodesByAuthor") .hasNodesAtPaths("/fs2/file.txt") .validate(query, query.execute()); } finally { session.logout(); } }; } @Test @FixFor( "MODE-2391" ) public void shouldNotReindexBetweenRestartsLocalProviderIfExists() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(repository -> { long initialSize = FileUtil.size("target/startup_test_indexes"); JcrSession session = repository.login(); int nodeCount = 100; for (int i = 0; i < nodeCount; i++) { session.getRootNode().addNode("node_" + i); } session.save(); session.logout(); // the indexes are sync, so the FS size should've increased because of the new nodes assertTrue(initialSize < FileUtil.size("target/startup_test_indexes")); }, "config/repo-config-persistent-local-indexes.json"); long indexFolderSize = FileUtil.size("target/startup_test_indexes"); assertTrue(indexFolderSize > 0); startRunStop(repository -> { // wait a bit so that if reindexing was happening it would be finished before shutting down Thread.sleep(200); repository.login().logout(); }, "config/repo-config-persistent-local-indexes.json"); // if reindexing was happening, it would be async so the next assert would normally fail assertEquals("Re-indexing should not be happening", indexFolderSize, FileUtil.size("target/startup_test_indexes")); } @Test @FixFor( "MODE-2393 ") public void reindexingLocalProviderShouldRemoveExistingDataFirst() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(this::addNodeAndAssertIndexUsed, "config/repo-config-persistent-local-indexes.json"); startRunStop(repository -> { JcrSession session = repository.login(); // force a re-index of the entire workspace - this should clear the existing indexes first session.getWorkspace().reindex(); // then force a reindex of a certain path session.getWorkspace().reindex("/testRoot"); //then check that still only 1 node is returned String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("nodesByName").validate(query, query.execute()); }, "config/repo-config-persistent-local-indexes.json"); } @FixFor( "MODE-2292" ) @Test public void shouldUseIndexesAfterRestarting() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(this::addNodeAndAssertIndexUsed, "config/repo-config-persistent-local-indexes.json"); startRunStop(repository -> { JcrSession session = repository.login(); String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("nodesByName").validate(query, query.execute()); session.logout(); }, "config/repo-config-persistent-local-indexes.json"); } @Test @FixFor( "MODE-2583 ") public void shouldNotUseIndexesWhichHaveBeenRemovedFromConfiguration() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); RepositoryConfiguration configuration = startRunStop(this::addNodeAndAssertIndexUsed, "config/repo-config-persistent-local-indexes.json"); MutableDocument configDoc = configuration.edit().asMutableDocument(); configDoc.remove(RepositoryConfiguration.FieldName.INDEXES); TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(repository -> { JcrSession session = repository.login(); // force a re-index of the entire workspace session.getWorkspace().reindex(); //then check that still only 1 node is returned String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useNoIndexes().validate(query, query.execute()); session.logout(); }, new RepositoryConfiguration(configDoc, "updated_config")); } @Test @FixFor("MODE-2644") public void shouldUseDynamicallyRegisteredIndexes() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(this::registerIndexDefinitionAndCheckUsage, "config/repo-config-persistent-local-indexes.json"); startRunStop(repository -> { JcrSession session = repository.login(); String sql = "select [jcr:path] from [nt:unstructured] where testProp = 'test'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("testProp").validate(query, query.execute()); session.logout(); }, "config/repo-config-persistent-local-indexes.json"); } private void addNodeAndAssertIndexUsed(JcrRepository repository) throws RepositoryException, InterruptedException { JcrSession session = repository.login(); session.getRootNode().addNode("testRoot"); session.save(); Thread.sleep(100); String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("nodesByName").validate(query, query.execute()); session.logout(); } private void registerIndexDefinitionAndCheckUsage(JcrRepository repository) throws Exception { JcrSession session = repository.login(); try { IndexManager indexManager = session.getWorkspace().getIndexManager(); IndexDefinitionTemplate indexDefinitionTemplate = indexManager.createIndexDefinitionTemplate(); // we suppose there's only an index provider indexDefinitionTemplate.setProviderName("local"); indexDefinitionTemplate.setWorkspace("default"); indexDefinitionTemplate.setAllWorkspaces(); indexDefinitionTemplate.setKind(IndexDefinition.IndexKind.VALUE); indexDefinitionTemplate.setSynchronous(true); indexDefinitionTemplate.setNodeTypeName("nt:unstructured"); indexDefinitionTemplate.setName("testProp"); IndexColumnDefinitionTemplate indexColumnDefinitionTemplate = indexManager.createIndexColumnDefinitionTemplate(); indexColumnDefinitionTemplate.setPropertyName("testProp"); indexColumnDefinitionTemplate.setColumnType(PropertyType.STRING); indexDefinitionTemplate.setColumnDefinitions(indexColumnDefinitionTemplate); indexManager.registerIndex(indexDefinitionTemplate, true); Node testNode = session.getRootNode().addNode("testRoot"); testNode.setProperty("testProp", "test"); session.save(); Thread.sleep(100); String sql = "select [jcr:path] from [nt:unstructured] where testProp = 'test'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("testProp").validate(query, query.execute()); session.logout(); } finally { session.logout(); } } private void prepareExternalDirectory( String dirpath ) throws IOException { FileUtil.delete(dirpath); new File(dirpath).mkdir(); File file = new File(dirpath + "/file.txt"); IoUtil.write(JcrRepositoryStartupTest.class.getClassLoader().getResourceAsStream("io/file1.txt"), new FileOutputStream(file)); } protected void changeLastUpgradeId( JcrRepository repository, int value ) throws Exception { // modify the repository-info document to force an upgrade on the next restart DocumentStore documentStore = repository.documentStore(); documentStore.localStore().runInTransaction(() -> { EditableDocument editableDocument = documentStore.localStore().edit(RepositoryCache.REPOSITORY_INFO_KEY, true); editableDocument.set("lastUpgradeId", value); documentStore.localStore().put(RepositoryCache.REPOSITORY_INFO_KEY, editableDocument); return null; }, 0); } protected AccessControlList getACL( AccessControlManager acm, String absPath ) throws Exception { AccessControlPolicyIterator it = acm.getApplicablePolicies(absPath); if (it.hasNext()) { return (AccessControlList)it.nextAccessControlPolicy(); } return (AccessControlList)acm.getPolicies(absPath)[0]; } }