/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to you 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.apache.geode.cache.lucene; import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.*; import static org.junit.Assert.*; import java.io.Serializable; import java.util.concurrent.TimeUnit; import com.jayway.awaitility.Awaitility; import org.junit.Test; import org.junit.experimental.categories.Category; import org.apache.geode.cache.ExpirationAction; import org.apache.geode.cache.ExpirationAttributes; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.lucene.internal.LuceneIndexForPartitionedRegion; import org.apache.geode.cache.lucene.internal.LuceneIndexImpl; import org.apache.geode.cache.lucene.internal.LuceneIndexStats; import org.apache.geode.cache.lucene.internal.filesystem.FileSystemStats; import org.apache.geode.cache.lucene.test.LuceneTestUtilities; import org.apache.geode.test.junit.categories.IntegrationTest; @Category(IntegrationTest.class) public class LuceneIndexMaintenanceIntegrationTest extends LuceneIntegrationTest { private static int WAIT_FOR_FLUSH_TIME = 10000; @Test public void indexIsNotUpdatedIfTransactionHasNotCommittedYet() throws Exception { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); Region region = createRegion(REGION_NAME, RegionShortcut.PARTITION); region.put("object-1", new TestObject("title 1", "hello world")); region.put("object-2", new TestObject("title 2", "this will not match")); region.put("object-3", new TestObject("title 3", "hello world")); region.put("object-4", new TestObject("hello world", "hello world")); LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); LuceneQuery query = luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, "description:\"hello world\"", DEFAULT_FIELD); PageableLuceneQueryResults<Integer, TestObject> results = query.findPages(); assertEquals(3, results.size()); // begin transaction cache.getCacheTransactionManager().begin(); region.put("object-1", new TestObject("title 1", "updated")); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); assertEquals(3, query.findPages().size()); } @Test public void indexIsUpdatedAfterTransactionHasCommitted() throws Exception { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); Region region = createRegion(REGION_NAME, RegionShortcut.PARTITION); region.put("object-1", new TestObject("title 1", "hello world")); region.put("object-2", new TestObject("title 2", "this will not match")); region.put("object-3", new TestObject("title 3", "hello world")); region.put("object-4", new TestObject("hello world", "hello world")); LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); LuceneQuery query = luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, "description:\"hello world\"", DEFAULT_FIELD); PageableLuceneQueryResults<Integer, TestObject> results = query.findPages(); assertEquals(3, results.size()); cache.getCacheTransactionManager().begin(); region.put("object-1", new TestObject("title 1", "updated")); cache.getCacheTransactionManager().commit(); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); assertEquals(2, query.findPages().size()); } @Test public void indexIsNotUpdatedAfterTransactionRollback() throws Exception { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); Region region = createRegion(REGION_NAME, RegionShortcut.PARTITION); region.put("object-1", new TestObject("title 1", "hello world")); region.put("object-2", new TestObject("title 2", "this will not match")); region.put("object-3", new TestObject("title 3", "hello world")); region.put("object-4", new TestObject("hello world", "hello world")); LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); LuceneQuery query = luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, "description:\"hello world\"", DEFAULT_FIELD); PageableLuceneQueryResults<Integer, TestObject> results = query.findPages(); assertEquals(3, results.size()); cache.getCacheTransactionManager().begin(); region.put("object-1", new TestObject("title 1", "updated")); cache.getCacheTransactionManager().rollback(); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); assertEquals(3, query.findPages().size()); } @Test public void statsAreUpdatedAfterACommit() throws Exception { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); Region region = createRegion(REGION_NAME, RegionShortcut.PARTITION); region.put("object-1", new TestObject("title 1", "hello world")); region.put("object-2", new TestObject("title 2", "this will not match")); region.put("object-3", new TestObject("title 3", "hello world")); region.put("object-4", new TestObject("hello world", "hello world")); LuceneIndexForPartitionedRegion index = (LuceneIndexForPartitionedRegion) luceneService.getIndex(INDEX_NAME, REGION_NAME); index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); FileSystemStats fileSystemStats = index.getFileSystemStats(); LuceneIndexStats indexStats = index.getIndexStats(); await(() -> assertEquals(4, indexStats.getDocuments())); await(() -> assertTrue(fileSystemStats.getFiles() > 0)); await(() -> assertTrue(fileSystemStats.getChunks() > 0)); await(() -> assertTrue(fileSystemStats.getBytes() > 0)); } @Test public void indexShouldBeUpdatedWithRegionExpirationDestroyOperation() throws Exception { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); // Configure PR with expiration operation set to destroy Region region = cache.createRegionFactory(RegionShortcut.PARTITION) .setEntryTimeToLive(new ExpirationAttributes(1, ExpirationAction.DESTROY)) .create(REGION_NAME); populateRegion(region); // Wait for expiration to destroy region entries. The region should be // left with zero entries. Awaitility.await().atMost(60, TimeUnit.SECONDS).until(() -> { assertEquals(0, region.size()); }); LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME); // Wait for events to be flushed from AEQ. index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); // Execute query to fetch all the values for "description" field. LuceneQuery query = luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, "description:\"hello world\"", DEFAULT_FIELD); PageableLuceneQueryResults<Integer, TestObject> results = query.findPages(); // The query should return 0 results. assertEquals(0, results.size()); } @Test public void nullValuesShouldNotCauseAnException() throws Exception { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); // Configure PR with expiration operation set to destroy Region region = cache.createRegionFactory(RegionShortcut.PARTITION).create(REGION_NAME); region.create(0, null); region.put(113, new TestObject("hello world", "hello world")); LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME); // Wait for events to be flushed from AEQ. index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS); // Execute query to fetch all the values for "description" field. LuceneQuery query = luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, "description:\"hello world\"", DEFAULT_FIELD); PageableLuceneQueryResults<Integer, TestObject> results = query.findPages(); assertEquals(1, results.size()); } @Test public void entriesFlushedToIndexAfterWaitForFlushCalled() throws InterruptedException { luceneService.createIndex(INDEX_NAME, REGION_NAME, "title", "description"); Region region = createRegion(REGION_NAME, RegionShortcut.PARTITION); LuceneTestUtilities.pauseSender(cache); region.put("object-1", new TestObject("title 1", "hello world")); region.put("object-2", new TestObject("title 2", "this will not match")); region.put("object-3", new TestObject("title 3", "hello world")); region.put("object-4", new TestObject("hello world", "hello world")); LuceneIndexImpl index = (LuceneIndexImpl) luceneService.getIndex(INDEX_NAME, REGION_NAME); assertFalse(index.waitUntilFlushed(500, TimeUnit.MILLISECONDS)); LuceneTestUtilities.resumeSender(cache); assertTrue(index.waitUntilFlushed(WAIT_FOR_FLUSH_TIME, TimeUnit.MILLISECONDS)); assertEquals(4, index.getIndexStats().getCommits()); } private void populateRegion(Region region) { region.put("object-1", new TestObject("title 1", "hello world")); region.put("object-2", new TestObject("title 2", "this will not match")); region.put("object-3", new TestObject("title 3", "hello world")); region.put("object-4", new TestObject("hello world", "hello world")); } private void await(Runnable runnable) { Awaitility.await().atMost(30, TimeUnit.SECONDS).until(runnable); } private static class TestObject implements Serializable { String title; String description; public TestObject(String title, String description) { this.title = title; this.description = description; } } }