/* * 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.index.lucene; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.modeshape.jcr.ValidateQuery.validateQuery; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.query.Query; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.util.FileUtil; import org.modeshape.jcr.ClusteringHelper; import org.modeshape.jcr.JcrRepository; import org.modeshape.jcr.TestingUtil; import org.modeshape.jcr.api.Repository; /** * Unit tests for generic operations involving a repository which has a {@link LuceneIndexProvider} configured. Tests which are * not index/search specific should go here. All others should go in {@link LuceneIndexProviderTest} * * @author Horia Chiorean (hchiorea@redhat.com) */ public class LuceneRepositoryTest { private Repository repository; @BeforeClass public static void beforeClass() throws Exception { ClusteringHelper.bindJGroupsToLocalAddress(); } @AfterClass public static void afterClass() throws Exception { ClusteringHelper.removeJGroupsBindings(); } @After public void after() { if (repository != null) { TestingUtil.killRepositories(repository); } } @Test public void shouldAllowAdvancedLuceneConfiguration() throws Exception { repository = TestingUtil.startRepositoryWithConfig( "config/repo-config-persistent-lucene-provider-advanced-settings.json"); //add a node in the default ws Session defaultSession = repository.login(); Node node = defaultSession.getRootNode().addNode("node1"); node.addMixin("mix:title"); node.setProperty("jcr:title", "title1"); defaultSession.save(); //add a node in the other ws Session otherSession = repository.login("other"); node = otherSession.getRootNode().addNode("node2"); node.addMixin("mix:title"); node.setProperty("jcr:title", "title2"); otherSession.save(); //test that each query is local to its own workspace.... Query query = defaultSession.getWorkspace().getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] LIKE 'title%'", Query.JCR_SQL2); validateQuery().hasNodesAtPaths("/node1").useIndex("titleIndex").validate(query, query.execute()); query = otherSession.getWorkspace().getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] LIKE 'title%'", Query.JCR_SQL2); validateQuery().hasNodesAtPaths("/node2").useIndex("titleIndex").validate(query, query.execute()); } @Test @FixFor( "MODE-1903" ) public void shouldReindexContentInClusterIncrementally() throws Exception { TestingUtil.waitUntilFolderCleanedUp("target/clustered"); JcrRepository repository1 = null; JcrRepository repository2 = null; try { // Start the first process completely ... String clusterNode1 = UUID.randomUUID().toString(); repository1 = TestingUtil.startClusteredRepositoryWithConfig( "config/repo-config-clustered-incremental-indexes.json", clusterNode1); // Start the second process completely ... String clusterNode2 = UUID.randomUUID().toString(); repository2 = TestingUtil.startClusteredRepositoryWithConfig( "config/repo-config-clustered-incremental-indexes.json", clusterNode2); // make 1 change which should be propagated in the cluster Session session1 = repository1.login(); Node node = session1.getRootNode().addNode("repo1_node1"); node.addMixin("mix:title"); node.setProperty("jcr:title", "title1"); session1.save(); Thread.sleep(300); TestingUtil.killRepository(repository2); // add a new node in the first repo node = session1.getRootNode().addNode("repo1_node2"); node.addMixin("mix:title"); node.setProperty("jcr:title", "title2"); session1.save(); // start the 2nd repo back up - at the end of this the journals should be up-to-date repository2 = TestingUtil.startClusteredRepositoryWithConfig( "config/repo-config-clustered-incremental-indexes.json", clusterNode2); // run a query to check that the index are not yet up-to-date Session session2 = repository2.login(); org.modeshape.jcr.api.Workspace workspace2 = (org.modeshape.jcr.api.Workspace)session2.getWorkspace(); // run queries to check that reindexing has worked Query query = workspace2.getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] = 'title2'", Query.JCR_SQL2); validateQuery().hasNodesAtPaths("/repo1_node2").useIndex("titleIndex").validate(query, query.execute()); // shut the second repo down TestingUtil.killRepository(repository2); Thread.sleep(100); // remove a node from the first repo and change a value for the other node session1.getNode("/repo1_node2").remove(); session1.getNode("/repo1_node1").setProperty("jcr:title", "title1_edited"); session1.save(); // start the 2nd repo back up - at the end of this the journals should be up-to-date and ISPN should've done the state // transfer repository2 = TestingUtil.startClusteredRepositoryWithConfig( "config/repo-config-clustered-incremental-indexes.json", clusterNode2); // run a query to check that the indexes are synced session2 = repository2.login(); workspace2 = (org.modeshape.jcr.api.Workspace)session2.getWorkspace(); query = workspace2.getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] = 'title2'", Query.JCR_SQL2); validateQuery().rowCount(0).useIndex("titleIndex").validate(query, query.execute()); query = workspace2.getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] = 'title1'", Query.JCR_SQL2); validateQuery().rowCount(0).useIndex("titleIndex").validate(query, query.execute()); query = workspace2.getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] = 'title1_edited'", Query.JCR_SQL2); validateQuery().hasNodesAtPaths("/repo1_node1").useIndex("titleIndex").validate(query, query.execute()); } finally { TestingUtil.killRepositories(repository1, repository2); } } @Test @FixFor( "MODE-2586" ) public void shouldReadIndexesFromLucene53() throws Exception { // unzip a folder with 53 indexes and data with 2 nodes: /node1 and /node2, both of which have mix:title and a title property FileUtil.unzip(LuceneRepositoryTest.class.getClassLoader().getResourceAsStream("lucene53_indexes.zip"), "target/indexes"); FileUtil.unzip(LuceneRepositoryTest.class.getClassLoader().getResourceAsStream("lucene53_repo.zip"), "target/repo"); // fire up the repository and check that the indexes and data are still being read repository = TestingUtil.startRepositoryWithConfig("config/repo-config-backward-compatibility-indexes.json"); Session session = repository.login(); Node node1 = session.getNode("/node1"); assertTrue(node1.isNodeType("mix:title")); assertEquals("title1", node1.getProperty("jcr:title").getString()); Node node2 = session.getNode("/node2"); assertTrue(node2.isNodeType("mix:title")); assertEquals("title2", node2.getProperty("jcr:title").getString()); //test that each query is local to its own workspace.... Query query = session.getWorkspace().getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] LIKE 'title%'", Query.JCR_SQL2); validateQuery().hasNodesAtPaths("/node1", "/node2").useIndex("titleIndex").validate(query, query.execute()); } @Test @FixFor( "MODE-2585" ) @Ignore(" perf test") public void shouldQueryLargeNumberOfNodes() throws Exception { assertTrue(FileUtil.delete("target/repo")); assertTrue(FileUtil.delete("target/indexes")); repository = TestingUtil.startRepositoryWithConfig("config/repo-config-backward-compatibility-indexes.json"); Session session = repository.login(); int nodeCount = 10003; int batchSize = 1000; IntStream.range(0, nodeCount).forEach(i -> { int idx = i + 1; try { Node node = session.getRootNode().addNode("node" + idx); node.addMixin("mix:title"); node.addMixin("mix:created"); node.setProperty("jcr:title", "title" + idx); if (i > 0 && i % batchSize == 0) { System.out.println("inserting batch [" + (i - batchSize) + "," + i + "]"); session.save(); } } catch (RepositoryException e) { throw new RuntimeException(e); } }); if (session.hasPendingChanges()) { System.out.printf("inserting last batch "); session.save(); } Query query = session.getWorkspace().getQueryManager().createQuery( "select node.[jcr:path] from [mix:title] as node where node.[jcr:title] LIKE 'title%' ORDER BY [jcr:created] ASC LIMIT 3 OFFSET 1000", Query.JCR_SQL2); long start = System.nanoTime(); validateQuery().rowCount(3).printDetail().useIndex("titleIndex").hasNodesAtPaths("/node1001", "/node1002", "/node1003").validate(query, query.execute()); System.out.println("Time to run query with limit and offset: " + TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)/1000d + " seconds"); } }