/*
* 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.RegionShortcut.*;
import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.*;
import static junitparams.JUnitParamsRunner.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
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.LuceneIndexFactory;
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.IntegrationTest;
import com.jayway.awaitility.Awaitility;
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 junitparams.JUnitParamsRunner;
import junitparams.Parameters;
/**
* 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(IntegrationTest.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.createIndex(INDEX_NAME, REGION_NAME, analyzers);
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(Arrays.asList("field1"), field1Analyzer.analyzedfields);
assertEquals(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.createIndex(INDEX_NAME, REGION_NAME, analyzers);
Region region = createRegion();
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
assertTrue(index instanceof LuceneRawIndex);
region.put("key1", new TestObject());
verifyIndexFinishFlushing(cache, INDEX_NAME, REGION_NAME);
assertEquals(analyzers, index.getFieldAnalyzers());
assertEquals(Arrays.asList("field1"), field1Analyzer.analyzedfields);
assertEquals(Arrays.asList("field2"), field2Analyzer.analyzedfields);
} finally {
LuceneServiceImpl.luceneIndexFactory = new LuceneIndexFactory();
}
}
@Test(expected = IllegalStateException.class)
public void cannotCreateLuceneIndexAfterRegionHasBeenCreated()
throws IOException, ParseException {
createRegion();
createIndex("field1", "field2", "field3");
}
@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 cannotCreateLuceneIndexForRegionWithEviction() 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 cannotCreateLuceneIndexWithExistingIndexName() {
expectedException.expect(IllegalArgumentException.class);
createIndex("field1", "field2", "field3");
createIndex("field4", "field5", "field6");
}
@Test
public void shouldReturnAllDefinedIndexes() {
LuceneServiceImpl luceneServiceImpl = (LuceneServiceImpl) luceneService;
luceneServiceImpl.createIndex(INDEX_NAME, REGION_NAME, "field1", "field2", "field3");
luceneServiceImpl.createIndex("index2", "region2", "field4", "field5", "field6");
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 List<String> analyzedfields = new ArrayList<String>();
@Override
protected TokenStreamComponents createComponents(final String fieldName) {
analyzedfields.add(fieldName);
return new TokenStreamComponents(new KeywordTokenizer());
}
}
}