blob: cc31fb652cf85532280b209de3120d68fef1a55e [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.DEFAULT_FIELD;
import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.IntRangeQueryProvider;
import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.verifyQueryKeyAndValues;
import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.verifyQueryKeys;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.KeywordAnalyzer;
import org.apache.lucene.analysis.core.LowerCaseFilter;
import org.apache.lucene.analysis.phonetic.DoubleMetaphoneFilter;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.standard.StandardTokenizer;
import org.apache.lucene.analysis.util.CharTokenizer;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.apache.geode.cache.CacheLoader;
import org.apache.geode.cache.CacheLoaderException;
import org.apache.geode.cache.LoaderHelper;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.lucene.internal.LuceneIndexFactoryImpl;
import org.apache.geode.cache.lucene.internal.LuceneServiceImpl;
import org.apache.geode.cache.lucene.test.TestObject;
import org.apache.geode.pdx.JSONFormatter;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.test.junit.categories.LuceneTest;
/**
* This class contains tests of lucene queries that can fit
*/
@Category({LuceneTest.class})
public class LuceneQueriesIntegrationTest extends LuceneIntegrationTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
protected static final String INDEX_NAME = "index";
public static final String REGION_NAME = "index";
protected Region region;
protected Region createRegionAndIndex(Map<String, Analyzer> fields) {
((LuceneIndexFactoryImpl) luceneService.createIndexFactory().setFields(fields))
.create(INDEX_NAME, REGION_NAME, LuceneServiceImpl.LUCENE_REINDEX);
region = cache.createRegionFactory(RegionShortcut.PARTITION).create(REGION_NAME);
return region;
}
protected Region createRegionAndIndex(RegionShortcut regionShortcut, String... fields) {
((LuceneIndexFactoryImpl) luceneService.createIndexFactory().setFields(fields))
.create(INDEX_NAME, REGION_NAME, LuceneServiceImpl.LUCENE_REINDEX);
region = cache.createRegionFactory(regionShortcut).create(REGION_NAME);
return region;
}
@Test()
public void shouldNotTokenizeWordsWithKeywordAnalyzer() throws Exception {
Map<String, Analyzer> fields = new HashMap<String, Analyzer>();
fields.put("field1", new StandardAnalyzer());
fields.put("field2", new KeywordAnalyzer());
region = createRegionAndIndex(fields);
// Put two values with some of the same tokens
String value1 = "one three";
String value2 = "one two three";
String value3 = "one@three";
region.put("A", new TestObject(value1, value1));
region.put("B", new TestObject(value2, value2));
region.put("C", new TestObject(value3, value3));
// The value will be tokenized into following documents using the analyzers:
// <field1:one three> <field2:one three>
// <field1:one two three> <field2:one two three>
// <field1:one@three> <field2:one@three>
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
// standard analyzer with double quote
// this query string will be parsed as "one three"
// but standard analyzer will parse value "one@three" to be "one three"
// query will be--fields1:"one three"
// so C will be hit by query
verifyQuery("field1:\"one three\"", DEFAULT_FIELD, "A", "C");
// standard analyzer will not tokenize by '_'
// this query string will be parsed as "one_three"
// query will be--field1:one_three
verifyQuery("field1:one_three", DEFAULT_FIELD);
// standard analyzer will tokenize by '@'
// this query string will be parsed as "one" "three"
// query will be--field1:one field1:three
verifyQuery("field1:one@three", DEFAULT_FIELD, "A", "B", "C");
HashMap expectedResults = new HashMap();
expectedResults.put("A", new TestObject(value1, value1));
expectedResults.put("B", new TestObject(value2, value2));
expectedResults.put("C", new TestObject(value3, value3));
verifyQuery("field1:one@three", DEFAULT_FIELD, expectedResults);
// keyword analyzer, this query will only match the entry that exactly matches
// this query string will be parsed as "one three"
// but keyword analyzer will parse one@three to be "one three"
// query will be--field2:one three
verifyQuery("field2:\"one three\"", DEFAULT_FIELD, "A");
// keyword analyzer without double quote. It should be the same as
// with double quote
// query will be--field2:one@three
verifyQuery("field2:one@three", DEFAULT_FIELD, "C");
}
@Test()
public void shouldQueryUsingIntRangeQueryProvider() throws Exception {
// Note: range query on numeric field has some limitations. But IntRangeQueryProvider
// provided basic functionality
region = createRegionAndIndex(RegionShortcut.PARTITION, LuceneService.REGION_VALUE_FIELD);
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
region.put("primitiveInt0", 122);
region.put("primitiveInt1", 123);
region.put("primitiveInt2", 223);
region.put("primitiveInt3", 224);
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
verifyQueryUsingCustomizedProvider(LuceneService.REGION_VALUE_FIELD, 123, 223, "primitiveInt1",
"primitiveInt2");
}
@Test()
public void shouldQueryUsingSoundexAnalyzer() throws Exception {
Map<String, Analyzer> fields = new HashMap<String, Analyzer>();
fields.put("field1", new StandardAnalyzer());
fields.put("field2", new DoubleMetaphoneAnalyzer());
region = createRegionAndIndex(fields);
// Put two values with some of the same tokens
String value1 = "Stefan";
String value2 = "Steph";
String value3 = "Stephen";
String value4 = "Steve";
String value5 = "Steven";
String value6 = "Stove";
String value7 = "Stuffin";
region.put("A", new TestObject(value1, value1));
region.put("B", new TestObject(value2, value2));
region.put("C", new TestObject(value3, value3));
region.put("D", new TestObject(value4, value4));
region.put("E", new TestObject(value5, value5));
region.put("F", new TestObject(value6, value6));
region.put("G", new TestObject(value7, value7));
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
// soundex search
verifyQuery("field2:Stephen", DEFAULT_FIELD, "A", "C", "E", "G");
// compare with Ste* search on soundex analyzer will not find anything
// but on standard analyzer will find 5 matchs
verifyQuery("field2:Ste*", DEFAULT_FIELD);
verifyQuery("field1:Ste*", DEFAULT_FIELD, "A", "B", "C", "D", "E");
}
@Ignore
@Test()
public void queryParserCannotQueryByRange() throws Exception {
// Note: range query on numeric field has some limitations. But IntRangeQueryProvider
// provided basic functionality
region = createRegionAndIndex(RegionShortcut.PARTITION, LuceneService.REGION_VALUE_FIELD);
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
region.put("primitiveInt0", 122);
region.put("primitiveInt1", 123);
region.put("primitiveInt2", 223);
region.put("primitiveInt3", 224);
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
// Note: current QueryParser cannot query by range. It's a known issue in lucene
verifyQuery(LuceneService.REGION_VALUE_FIELD + ":[123 TO 223]",
LuceneService.REGION_VALUE_FIELD);
region.put("primitiveDouble1", 123.0);
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
thrown.expectMessage("java.lang.IllegalArgumentException");
verifyQueryUsingCustomizedProvider(LuceneService.REGION_VALUE_FIELD, 123, 223, "primitiveInt1",
"primitiveInt2");
}
@Test()
public void shouldPaginateResults() throws Exception {
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(2);
final PageableLuceneQueryResults<Object, Object> pages = query.findPages();
assertTrue(pages.hasNext());
assertEquals(7, pages.size());
final List<LuceneResultStruct<Object, Object>> page1 = pages.next();
final List<LuceneResultStruct<Object, Object>> page2 = pages.next();
final List<LuceneResultStruct<Object, Object>> page3 = pages.next();
final List<LuceneResultStruct<Object, Object>> page4 = pages.next();
List<LuceneResultStruct<Object, Object>> allEntries = new ArrayList<>();
allEntries.addAll(page1);
allEntries.addAll(page2);
allEntries.addAll(page3);
allEntries.addAll(page4);
assertEquals(region.keySet(),
allEntries.stream().map(entry -> entry.getKey()).collect(Collectors.toSet()));
assertEquals(region.values(),
allEntries.stream().map(entry -> entry.getValue()).collect(Collectors.toSet()));
}
@Test
public void shouldReturnValuesFromFindValues() throws Exception {
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(2);
assertEquals(region.values(), new HashSet(query.findValues()));
}
private LuceneQuery<Object, Object> addValuesAndCreateQuery(int pagesize)
throws InterruptedException {
region = createRegionAndIndex(RegionShortcut.PARTITION, "field1", "field2");
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
// Put two values with some of the same tokens
String value1 = "one three";
String value2 = "one two three";
String value3 = "one@three";
region.put("A", new TestObject(value1, value1));
region.put("B", new TestObject(value2, value2));
region.put("C", new TestObject(value3, value3));
region.put("D", new TestObject(value1, value1));
region.put("E", new TestObject(value2, value2));
region.put("F", new TestObject(value3, value3));
region.put("G", new TestObject(value1, value2));
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
return luceneService.createLuceneQueryFactory().setPageSize(pagesize).create(INDEX_NAME,
REGION_NAME, "one", "field1");
}
@Test()
public void shouldTokenizeUsingMyCharacterAnalyser() throws Exception {
Map<String, Analyzer> fields = new HashMap<String, Analyzer>();
// not to specify field1's analyzer, it should use standard analyzer
// Note: fields has to contain "field1", otherwise, field1 will not be tokenized
fields.put("field1", null);
fields.put("field2", new MyCharacterAnalyzer());
region = createRegionAndIndex(fields);
// Put two values with some of the same tokens
String value1 = "one three";
String value4 = "two_four";
String value3 = "two@four";
region.put("A", new TestObject(value1, value4));
region.put("B", new TestObject(value1, value3));
region.put("C", new TestObject(value3, value3));
region.put("D", new TestObject(value4, value4));
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
verifyQuery("field1:one AND field2:two_four", DEFAULT_FIELD, "A");
verifyQuery("field1:one AND field2:two", DEFAULT_FIELD, "A");
verifyQuery("field1:three AND field2:four", DEFAULT_FIELD, "A");
}
@Test()
public void shouldAllowNullInFieldValue() throws Exception {
Map<String, Analyzer> fields = new HashMap<String, Analyzer>();
fields.put("field1", null);
fields.put("field2", null);
region = createRegionAndIndex(fields);
// Put two values with some of the same tokens
String value1 = "one three";
region.put("A", new TestObject(value1, null));
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
verifyQuery("field1:one", DEFAULT_FIELD, "A");
}
@Test()
public void queryJsonObject() throws Exception {
Map<String, Analyzer> fields = new HashMap<String, Analyzer>();
fields.put("name", null);
fields.put("lastName", null);
fields.put("address", null);
region = createRegionAndIndex(fields);
// Put two values with some of the same tokens
PdxInstance pdx1 = insertAJson(region, "jsondoc1");
PdxInstance pdx2 = insertAJson(region, "jsondoc2");
PdxInstance pdx10 = insertAJson(region, "jsondoc10");
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
HashMap expectedResults = new HashMap();
expectedResults.put("jsondoc1", pdx1);
expectedResults.put("jsondoc10", pdx10);
verifyQuery("name:jsondoc1*", DEFAULT_FIELD, expectedResults);
}
@Test()
public void waitUntilFlushThrowsIllegalStateExceptionWhenAEQNotFound() throws Exception {
Map<String, Analyzer> fields = new HashMap<>();
fields.put("name", null);
fields.put("lastName", null);
fields.put("address", null);
region = createRegionAndIndex(fields);
// This is to send IllegalStateException from WaitUntilFlushedFunction
String nonCreatedIndex = "index2";
boolean result = false;
try {
result = luceneService.waitUntilFlushed(nonCreatedIndex, REGION_NAME, 60000,
TimeUnit.MILLISECONDS);
fail(
"Should have got the exception because the queue does not exist for the non created index ");
} catch (Exception ex) {
assertEquals(ex.getMessage(),
"java.lang.IllegalStateException: The AEQ does not exist for the index index2 region /index");
assertFalse(result);
}
}
@Test()
public void shouldAllowQueryOnRegionWithStringValue() throws Exception {
region = createRegionAndIndex(RegionShortcut.PARTITION, LuceneService.REGION_VALUE_FIELD);
final LuceneIndex index = luceneService.getIndex(INDEX_NAME, REGION_NAME);
region.put("A", "one three");
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
verifyQuery("one", LuceneService.REGION_VALUE_FIELD, "A");
}
@Test()
public void throwFunctionExceptionWhenGivenBadQuery() throws Exception {
LuceneService luceneService = LuceneServiceProvider.get(cache);
region = createRegionAndIndex(RegionShortcut.PARTITION, "text");
// Create a query that throws an exception
final LuceneQuery<Object, Object> query =
luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, (index) -> {
throw new LuceneQueryException("Bad query");
});
thrown.expect(LuceneQueryException.class);
query.findPages();
}
@Test
public void shouldReturnAllResultsWhenPaginationIsDisabled() throws Exception {
// Pagination disabled by setting page size = 0.
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(0);
final PageableLuceneQueryResults<Object, Object> pages = query.findPages();
assertTrue(pages.hasNext());
assertEquals(7, pages.size());
final List<LuceneResultStruct<Object, Object>> page = pages.next();
assertFalse(pages.hasNext());
assertEquals(region.keySet(),
page.stream().map(entry -> entry.getKey()).collect(Collectors.toSet()));
assertEquals(region.values(),
page.stream().map(entry -> entry.getValue()).collect(Collectors.toSet()));
}
@Test
public void shouldReturnCorrectResultsOnDeletionAfterQueryExecution() throws Exception {
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(2);
final PageableLuceneQueryResults<Object, Object> pages = query.findPages();
List<LuceneResultStruct<Object, Object>> allEntries = new ArrayList<>();
assertTrue(pages.hasNext());
assertEquals(7, pages.size());
// Destroying an entry from the region after the query is executed.
region.destroy("C");
final List<LuceneResultStruct<Object, Object>> page1 = pages.next();
assertEquals(2, page1.size());
final List<LuceneResultStruct<Object, Object>> page2 = pages.next();
assertEquals(2, page2.size());
final List<LuceneResultStruct<Object, Object>> page3 = pages.next();
assertEquals(2, page3.size());
assertFalse(pages.hasNext());
allEntries.addAll(page1);
allEntries.addAll(page2);
allEntries.addAll(page3);
assertEquals(region.keySet(),
allEntries.stream().map(entry -> entry.getKey()).collect(Collectors.toSet()));
assertEquals(region.values(),
allEntries.stream().map(entry -> entry.getValue()).collect(Collectors.toSet()));
}
@Test
public void shouldReturnCorrectResultsOnDeletionAfterQueryExecutionWithLoader() throws Exception {
final int pageSize = 2;
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(pageSize);
region.getAttributesMutator().setCacheLoader(new CacheLoader() {
@Override
public Object load(final LoaderHelper helper) throws CacheLoaderException {
return new TestObject("should not", "load this");
}
@Override
public void close() {
}
});
final PageableLuceneQueryResults<Object, Object> pages = query.findPages();
List<LuceneResultStruct<Object, Object>> allEntries = new ArrayList<>();
assertTrue(pages.hasNext());
assertEquals(7, pages.size());
// Destroying an entry from the region after the query is executed.
region.destroy("C");
final List<LuceneResultStruct<Object, Object>> page1 = pages.next();
assertEquals(pageSize, page1.size());
final List<LuceneResultStruct<Object, Object>> page2 = pages.next();
assertEquals(pageSize, page2.size());
final List<LuceneResultStruct<Object, Object>> page3 = pages.next();
assertEquals(pageSize, page3.size());
assertFalse(pages.hasNext());
allEntries.addAll(page1);
allEntries.addAll(page2);
allEntries.addAll(page3);
assertEquals(region.keySet(),
allEntries.stream().map(entry -> entry.getKey()).collect(Collectors.toSet()));
assertEquals(region.values(),
allEntries.stream().map(entry -> entry.getValue()).collect(Collectors.toSet()));
}
@Test
public void shouldReturnCorrectResultsOnMultipleDeletionsAfterQueryExecution() throws Exception {
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(2);
final PageableLuceneQueryResults<Object, Object> pages = query.findPages();
List<LuceneResultStruct<Object, Object>> allEntries = new ArrayList<>();
assertTrue(pages.hasNext());
assertEquals(7, pages.size());
// Destroying an entry from the region after the query is executed.
region.destroy("C");
allEntries.addAll(pages.next());
// Destroying an entry from allEntries and the region after it is fetched through pages.next().
Object removeKey = ((LuceneResultStruct) allEntries.remove(0)).getKey();
region.destroy(removeKey);
allEntries.addAll(pages.next());
// Destroying a region entry which has't been fetched through pages.next() yet.
Set resultKeySet = allEntries.stream().map(entry -> entry.getKey()).collect(Collectors.toSet());
for (Object key : region.keySet()) {
if (!resultKeySet.contains(key)) {
region.destroy(key);
break;
}
}
allEntries.addAll(pages.next());
assertFalse(pages.hasNext());
assertEquals(region.keySet(),
allEntries.stream().map(entry -> entry.getKey()).collect(Collectors.toSet()));
assertEquals(region.values(),
allEntries.stream().map(entry -> entry.getValue()).collect(Collectors.toSet()));
}
@Test
public void shouldReturnCorrectResultsOnAllDeletionsAfterQueryExecution() throws Exception {
final LuceneQuery<Object, Object> query = addValuesAndCreateQuery(2);
final PageableLuceneQueryResults<Object, Object> pages = query.findPages();
assertTrue(pages.hasNext());
assertEquals(7, pages.size());
region.destroy("A");
region.destroy("B");
region.destroy("C");
region.destroy("D");
region.destroy("E");
region.destroy("F");
region.destroy("G");
assertTrue(pages.hasNext());
final List<LuceneResultStruct<Object, Object>> page1 = pages.next();
assertEquals(2, page1.size());
assertFalse(pages.hasNext());
}
@Test()
public void soundexQueryReturnExpectedTruePositiveAndFalsePositive() throws Exception {
Map<String, Analyzer> fields = new HashMap<String, Analyzer>();
fields.put("field1", new DoubleMetaphoneAnalyzer());
fields.put("field2", null);
region = createRegionAndIndex(fields);
region.put("A", new TestObject("Stefan", "soundex"));
region.put("B", new TestObject("Steph", "soundex"));
region.put("C", new TestObject("Stephen", "soundex"));
region.put("D", new TestObject("Steve", "soundex"));
region.put("E", new TestObject("Steven", "soundex"));
region.put("F", new TestObject("Stove", "soundex"));
region.put("G", new TestObject("Stuffin", "soundex"));
luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, 60000, TimeUnit.MILLISECONDS);
verifyQuery("field1:Stephen", DEFAULT_FIELD, "A", "C", "E", "G");
}
private PdxInstance insertAJson(Region region, String key) {
String jsonCustomer = "{" + "\"name\": \"" + key + "\"," + "\"lastName\": \"Smith\","
+ " \"age\": 25," + "\"address\":" + "{" + "\"streetAddress\": \"21 2nd Street\","
+ "\"city\": \"New York\"," + "\"state\": \"NY\"," + "\"postalCode\": \"10021\"" + "},"
+ "\"phoneNumber\":" + "[" + "{" + " \"type\": \"home\"," + "\"number\": \"212 555-1234\""
+ "}," + "{" + " \"type\": \"fax\"," + "\"number\": \"646 555-4567\"" + "}" + "]" + "}";
PdxInstance pdx = JSONFormatter.fromJSON(jsonCustomer);
region.put(key, pdx);
return pdx;
}
private void verifyQueryUsingCustomizedProvider(String fieldName, int lowerValue, int upperValue,
String... expectedKeys) throws Exception {
IntRangeQueryProvider provider = new IntRangeQueryProvider(fieldName, lowerValue, upperValue);
LuceneQuery<String, Object> queryWithCustomizedProvider =
luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, provider);
verifyQueryKeys(queryWithCustomizedProvider, expectedKeys);
}
private void verifyQuery(String query, String defaultField, String... expectedKeys)
throws Exception {
final LuceneQuery<String, Object> queryWithStandardAnalyzer = luceneService
.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, query, defaultField);
verifyQueryKeys(queryWithStandardAnalyzer, expectedKeys);
}
private void verifyQuery(String query, String DEFAULT_FIELD, HashMap expectedResults)
throws Exception {
final LuceneQuery<String, Object> queryWithStandardAnalyzer = luceneService
.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, query, DEFAULT_FIELD);
verifyQueryKeyAndValues(queryWithStandardAnalyzer, expectedResults);
}
private static class MyCharacterTokenizer extends CharTokenizer {
@Override
protected boolean isTokenChar(final int character) {
return '_' != character;
}
}
private static class MyCharacterAnalyzer extends Analyzer {
@Override
protected TokenStreamComponents createComponents(final String field) {
Tokenizer tokenizer = new MyCharacterTokenizer();
TokenStream filter = new LowerCaseFilter(tokenizer);
return new TokenStreamComponents(tokenizer, filter);
}
}
private static class DoubleMetaphoneAnalyzer extends Analyzer {
@Override
protected TokenStreamComponents createComponents(final String field) {
Tokenizer tokenizer = new StandardTokenizer();
TokenStream stream = new DoubleMetaphoneFilter(tokenizer, 6, false);
return new TokenStreamComponents(tokenizer, stream);
}
}
}