blob: 7f324b67f1d5165b5b0ce57d0e38ec421a466f01 [file] [log] [blame]
/*
* 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.INDEX_NAME;
import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.REGION_NAME;
import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.verifyIndexFinishFlushing;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
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.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.KeywordTokenizer;
import org.apache.lucene.queryparser.classic.ParseException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.apache.geode.cache.EvictionAction;
import org.apache.geode.cache.EvictionAttributes;
import org.apache.geode.cache.ExpirationAttributes;
import org.apache.geode.cache.FixedPartitionAttributes;
import org.apache.geode.cache.PartitionAttributesFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionFactory;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.lucene.internal.LuceneIndexCreationProfile;
import org.apache.geode.cache.lucene.internal.LuceneIndexFactoryImpl;
import org.apache.geode.cache.lucene.internal.LuceneIndexImplFactory;
import org.apache.geode.cache.lucene.internal.LuceneRawIndex;
import org.apache.geode.cache.lucene.internal.LuceneRawIndexFactory;
import org.apache.geode.cache.lucene.internal.LuceneServiceImpl;
import org.apache.geode.cache.lucene.test.LuceneTestUtilities;
import org.apache.geode.cache.lucene.test.TestObject;
import org.apache.geode.internal.cache.BucketNotFoundException;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.test.junit.categories.LuceneTest;
/**
* Tests of creating lucene indexes on regions. All tests of index creation use cases should be in
* classes starting with LuceneIndexCreation*. Most tests belong in this class, except for:
* <ul>
* <li>Tests that use persistence are in {@link LuceneIndexCreationPersistenceIntegrationTest}</li>
* <li>Tests that use offheap are in {@link LuceneIndexCreationOffHeapIntegrationTest}</li>
* </ul>
*/
@Category({LuceneTest.class})
@RunWith(JUnitParamsRunner.class)
public class LuceneIndexCreationIntegrationTest extends LuceneIntegrationTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void shouldCreateIndexWriterWithAnalyzersWhenSettingPerFieldAnalyzers()
throws BucketNotFoundException, InterruptedException {
Map<String, Analyzer> analyzers = new HashMap<>();
final RecordingAnalyzer field1Analyzer = new RecordingAnalyzer();
final RecordingAnalyzer field2Analyzer = new RecordingAnalyzer();
analyzers.put("field1", field1Analyzer);
analyzers.put("field2", field2Analyzer);
luceneService.createIndexFactory().setFields(analyzers).create(INDEX_NAME, REGION_NAME);
Region region = createRegion();
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
region.put("key1", new TestObject());
verifyIndexFinishFlushing(cache, INDEX_NAME, REGION_NAME);
assertEquals(analyzers, index.getFieldAnalyzers());
assertEquals(new HashSet(Arrays.asList("field1")), field1Analyzer.analyzedfields);
assertEquals(new HashSet(Arrays.asList("field2")), field2Analyzer.analyzedfields);
}
@Test
@Parameters({"0", "1", "2"})
public void shouldUseRedundancyForInternalRegionsWhenUserRegionHasRedundancy(int redundancy) {
createIndex("text");
PartitionAttributesFactory paf = new PartitionAttributesFactory();
paf.setRedundantCopies(redundancy);
cache.createRegionFactory(RegionShortcut.PARTITION_REDUNDANT)
.setPartitionAttributes(paf.create()).create(REGION_NAME);
verifyInternalRegions(region -> {
assertEquals(redundancy,
region.getAttributes().getPartitionAttributes().getRedundantCopies());
});
}
@Test
public void shouldNotUseIdleTimeoutForInternalRegionsWhenUserRegionHasIdleTimeout() {
createIndex("text");
cache.createRegionFactory(RegionShortcut.PARTITION)
.setEntryIdleTimeout(new ExpirationAttributes(5)).create(REGION_NAME);
verifyInternalRegions(region -> {
assertEquals(0, region.getAttributes().getEntryIdleTimeout().getTimeout());
});
}
@Test
public void shouldNotUseTTLForInternalRegionsWhenUserRegionHasTTL() {
createIndex("text");
cache.createRegionFactory(RegionShortcut.PARTITION)
.setEntryTimeToLive(new ExpirationAttributes(5)).create(REGION_NAME);
verifyInternalRegions(region -> {
assertEquals(0, region.getAttributes().getEntryTimeToLive().getTimeout());
});
}
@Test
public void shouldUseFixedPartitionsForInternalRegions() {
createIndex("text");
PartitionAttributesFactory partitionAttributesFactory = new PartitionAttributesFactory<>();
final FixedPartitionAttributes fixedAttributes =
FixedPartitionAttributes.createFixedPartition("A", true, 1);
partitionAttributesFactory.addFixedPartitionAttributes(fixedAttributes);
cache.createRegionFactory(RegionShortcut.PARTITION)
.setPartitionAttributes(partitionAttributesFactory.create()).create(REGION_NAME);
verifyInternalRegions(region -> {
// Fixed partitioned regions don't allow you to specify the partitions on the colocated region
assertNull(region.getAttributes().getPartitionAttributes().getFixedPartitionAttributes());
assertTrue(((PartitionedRegion) region).isFixedPartitionedRegion());
});
}
@Test
public void shouldCreateRawIndexIfSpecifiedItsFactory()
throws BucketNotFoundException, InterruptedException {
Map<String, Analyzer> analyzers = new HashMap<>();
final RecordingAnalyzer field1Analyzer = new RecordingAnalyzer();
final RecordingAnalyzer field2Analyzer = new RecordingAnalyzer();
analyzers.put("field1", field1Analyzer);
analyzers.put("field2", field2Analyzer);
LuceneServiceImpl.luceneIndexFactory = new LuceneRawIndexFactory();
try {
luceneService.createIndexFactory().setFields(analyzers).create(INDEX_NAME, REGION_NAME);
Region region = createRegion();
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
assertThat(index).isInstanceOf(LuceneRawIndex.class);
region.put("key1", new TestObject());
verifyIndexFinishFlushing(cache, INDEX_NAME, REGION_NAME);
assertEquals(analyzers, index.getFieldAnalyzers());
assertEquals(new HashSet(Arrays.asList("field1")), field1Analyzer.analyzedfields);
assertEquals(new HashSet(Arrays.asList("field2")), field2Analyzer.analyzedfields);
} finally {
LuceneServiceImpl.luceneIndexFactory = new LuceneIndexImplFactory();
}
}
@Test(expected = IllegalStateException.class)
public void cannotCreateLuceneIndexAfterRegionHasBeenCreated()
throws IOException, ParseException {
createRegion();
createIndex("field1", "field2", "field3");
}
@Test()
public void canCreateLuceneIndexAfterRegionCreatedIfAllowFlagIsSet()
throws IOException, ParseException, InterruptedException, LuceneQueryException {
Region region = createRegion();
LuceneService luceneService = LuceneServiceProvider.get(cache);
final LuceneIndexFactoryImpl indexFactory =
(LuceneIndexFactoryImpl) luceneService.createIndexFactory();
indexFactory.setFields("field1", "field2").create(INDEX_NAME, REGION_NAME, true);
LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
assertNotNull(index);
region.put("key1", new TestObject("hello", "world"));
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 1, TimeUnit.MINUTES);
LuceneQuery<Object, Object> query = luceneService.createLuceneQueryFactory().create(INDEX_NAME,
REGION_NAME, "field1:hello", "field1");
assertEquals(Collections.singletonList("key1"), query.findKeys());
}
@Test()
public void creatingDuplicateLuceneIndexFails()
throws IOException, ParseException, InterruptedException, LuceneQueryException {
Region region = createRegion();
LuceneService luceneService = LuceneServiceProvider.get(cache);
final LuceneIndexFactoryImpl indexFactory =
(LuceneIndexFactoryImpl) luceneService.createIndexFactory();
indexFactory.setFields("field1", "field2").create(INDEX_NAME, REGION_NAME, true);
LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
assertNotNull(index);
expectedException.expect(LuceneIndexExistsException.class);
indexFactory.setFields("field1", "field2").create(INDEX_NAME, REGION_NAME, true);
}
@Test
public void cannotCreateLuceneIndexForReplicateRegion() throws IOException, ParseException {
try {
createIndex("field1", "field2", "field3");
this.cache.createRegionFactory(RegionShortcut.REPLICATE).create(REGION_NAME);
fail("Should not have been able to create index");
} catch (UnsupportedOperationException e) {
assertEquals("Lucene indexes on replicated regions are not supported", e.getMessage());
assertNull(cache.getRegion(REGION_NAME));
}
}
@Test
public void cannotCreateLuceneIndexForRegionWithEvictionWithLocalDestroy()
throws IOException, ParseException {
try {
createIndex("field1", "field2", "field3");
RegionFactory regionFactory = this.cache.createRegionFactory(RegionShortcut.PARTITION);
regionFactory.setEvictionAttributes(
EvictionAttributes.createLIFOEntryAttributes(100, EvictionAction.LOCAL_DESTROY));
regionFactory.create(REGION_NAME);
} catch (UnsupportedOperationException e) {
assertEquals(
"Lucene indexes on regions with eviction and action local destroy are not supported",
e.getMessage());
assertNull(cache.getRegion(REGION_NAME));
}
}
@Test
public void canCreateLuceneIndexForRegionWithEvictionWithOverflowToDisk()
throws IOException, ParseException {
try {
createIndex("field1", "field2", "field3");
RegionFactory regionFactory = this.cache.createRegionFactory(RegionShortcut.PARTITION);
regionFactory.setEvictionAttributes(
EvictionAttributes.createLRUHeapAttributes(null, EvictionAction.OVERFLOW_TO_DISK));
regionFactory.create(REGION_NAME);
} catch (Exception e) {
fail("Should have been able to create Lucene Index");
e.printStackTrace();
}
}
@Test
public void cannotCreateLuceneIndexWithExistingIndexName() {
expectedException.expect(LuceneIndexExistsException.class);
createIndex("field1", "field2", "field3");
createIndex("field4", "field5", "field6");
}
@Test
public void shouldReturnAllDefinedIndexes() {
LuceneServiceImpl luceneServiceImpl = (LuceneServiceImpl) luceneService;
luceneServiceImpl.createIndexFactory().setFields("field1", "field2", "field3")
.create(INDEX_NAME, REGION_NAME);
luceneServiceImpl.createIndexFactory().setFields("field4", "field5", "field6").create("index2",
"region2");
final Collection<LuceneIndexCreationProfile> indexList =
luceneServiceImpl.getAllDefinedIndexes();
assertEquals(Arrays.asList(INDEX_NAME, "index2"), indexList.stream()
.map(LuceneIndexCreationProfile::getIndexName).sorted().collect(Collectors.toList()));
createRegion();
assertEquals(Collections.singletonList("index2"), indexList.stream()
.map(LuceneIndexCreationProfile::getIndexName).collect(Collectors.toList()));
}
@Test
public void shouldRemoveDefinedIndexIfRegionAlreadyExists() {
try {
createRegion();
createIndex("field1", "field2", "field3");
} catch (IllegalStateException e) {
assertEquals("The lucene index must be created before region", e.getMessage());
assertEquals(0, ((LuceneServiceImpl) luceneService).getAllDefinedIndexes().size());
}
}
private void verifyInternalRegions(Consumer<LocalRegion> verify) {
LuceneTestUtilities.verifyInternalRegions(luceneService, cache, verify);
}
private Region createRegion() {
return createRegion(REGION_NAME, RegionShortcut.PARTITION);
}
private void createIndex(String... fieldNames) {
LuceneTestUtilities.createIndex(cache, fieldNames);
}
private static class RecordingAnalyzer extends Analyzer {
private Set<String> analyzedfields = new HashSet<String>();
@Override
protected TokenStreamComponents createComponents(final String fieldName) {
analyzedfields.add(fieldName);
return new TokenStreamComponents(new KeywordTokenizer());
}
}
}