Статьи

Разработка собственного фильтра Solr – часть 2

В предыдущей статье « Разработка собственного фильтра Solr » мы показали, как реализовать простой фильтр и как использовать его в Apache Solr. Недавно один из наших читателей спросил, можем ли мы расширить тему и показать, как записать более одного токена в поток токенов. Мы решили пойти на это и расширить предыдущую запись в блоге о реализации фильтра.

Предположения

Давайте предположим, что мы хотим не только вернуть перевернутое слово, но и сохранить оригинальное. Так что, если бы мы передать  прекрасное  слово для анализа мы хотели бы иметь  штраф  и  ените freturned. Для этого мы изменим фильтр, созданный в предыдущей записи.

Мы опускаем все детали конфигурации и установки, поэтому, если вы хотите прочитать о них, пожалуйста, обратитесь к  предыдущему  посту.

Версия Solr

Мы также хотели бы отметить, что в этом посте мы решили использовать новейшую версию Solr, поэтому мы использовали  4.1 . Чтобы следующий фильтр работал на  3.6,  просто использовали фабрику фильтров, показанную в предыдущей записи.

Завод фильтров

Единственная разница, когда дело доходит до фабрики фильтров, это класс, который мы расширяем. Поскольку мы используем Solr 4.1, мы расширяем  TokenFilterFactory  из   пакета org.apache.lucene.analysis.util :

public class ReverseFilterFactory extends TokenFilterFactory {
 @Override
 public TokenStream create(TokenStream ts) {
  return new ReverseFilter(ts);
 }
}

Фильтр

Фильтр был немного изменен и выглядит так:

public final class ReverseFilter extends TokenFilter {
 private CharTermAttribute charTermAttr;
 private PositionIncrementAttribute posIncAttr;
 private Queue<char[]> terms;

 protected ReverseFilter(TokenStream ts) {
  super(ts);
  this.charTermAttr = addAttribute(CharTermAttribute.class);
  this.posIncAttr = addAttribute(PositionIncrementAttribute.class);
  this.terms = new LinkedList<char[]>();
 }

 @Override
 public boolean incrementToken() throws IOException {
  if (!terms.isEmpty()) {
   char[] buffer = terms.poll();
   charTermAttr.setEmpty();
   charTermAttr.copyBuffer(buffer, 0, buffer.length);
   posIncAttr.setPositionIncrement(1);
   return true;
  }

  if (!input.incrementToken()) {
   return false;
  } else {
   // we reverse the token
   int length = charTermAttr.length();
   char[] buffer = charTermAttr.buffer();
   char[] newBuffer = new char[length];
   for (int i = 0; i < length; i++) {
    newBuffer[i] = buffer[length - 1 - i];
   }
   // we place the new buffer in the terms list
   terms.add(newBuffer);
   // we return true and leave the original token unchanged
   return true;
  }
 }
}

Описание реализации

Давайте поговорим о различиях между вышеупомянутым фильтром и версией, показанной в предыдущем сообщении в блоге:

  • Строка 4  — список, который будет использоваться для хранения буфера терминов, который должен быть записан в поток токенов.
  • Строка 9  — мы добавляем атрибут, который отвечает за установку положения токена в потоке токенов.
  • Строка 10  — инициализация списка, который мы определили в строке 4.
  • Строка 15 — 21  — условие, которое проверяет, есть ли у нас токены в списках, которые нам нужно обработать. Если есть такие токены, мы берем первый токен из списка (и удаляем его), мы устанавливаем термин «буфер», мы устанавливаем его позицию в потоке токенов и возвращаем  true  , чтобы сообщить, что обработка должна быть продолжена. Стоит отметить, что мы не получили новый токен из потока токенов, потому что мы не вызывали метод input.incrementToken ()  .
  • Строки 23 — 25  — мы проверяем, остались ли токены для обработки. Если нет, мы просто возвращаем  false  и заканчиваем обработку.
  • Строки 27 — 36  — мы обращаем токен и добавляем его в список, объявленный в строке 4. Мы не изменили токен, который фактически присутствует в токене, и возвращаем  true . Сделав это, мы сообщаем Solr, что хотим продолжить обработку, и Solr должен вызвать метод  incrementToken ()  нашего фильтра. После следующего вызова мы закончим выполнение кода из строк 15 — 21, потому что наш список будет содержать новый токен.

На наш взгляд, стоит отметить еще одну вещь — атрибут приращения позиции. В описанной выше реализации каждый обратный токен будет записан в следующей позиции в потоке токенов по сравнению с исходным токеном. Проще говоря — эти токены будут рассматриваться как отдельные слова. В нескольких из них мы проверим, что произойдет, когда обращенные токены будут размещены в тех же позициях, что и исходные.

Это работает ?

Работу указанного фильтра можно проиллюстрировать с помощью панели администрирования Solr:

wlasny_filtr_with_increment

Как вы можете видеть, оба исходных значения:  alama  и  kota  и перевернутые значения  alaam  и  atok  были установлены в новых позициях (   атрибут position ). Так что работает как задумано

Давайте изменим приращение позиции

Итак, давайте проверим, что произойдет, когда мы изменим следующую строку:

1 posIncAttr.setPositionIncrement(1);

на следующий:

1 posIncAttr.setPositionIncrement(0);

Как это работает

Опять же, давайте проиллюстрируем, как это работает, посмотрев на панель администрирования Solr:

wlasny_filtr_without_incrementКак видите, после изменения как оригинальный токен, так и его обратная версия были поставлены в одну и ту же позицию, чего мы и хотели достичь. Благодаря этому теперь мы можем запускать запросы типа  q = ”ala am kota”  (посмотрите на   слово am ). Что мы получаем, так это возможность использовать оригинальные или обращенные токены в запросах фраз.

Подводить итоги

Как вы можете видеть, создание собственных фильтров — это не ракетостроение, по крайней мере, когда речь идет о Lucene и Solr. То, что мы получаем от Lucene и Solr, — это хороший набор функций, которые мы можем использовать для управления тем, что будет окончательно помещено в поток токенов, например, благодаря атрибутам потока токенов. Конечно, сложность кода будет зависеть от вашей бизнес-логики, но это далеко выходит за рамки этого поста. :)