Solr: tworzenie własnego filtra część 2

We wpisie pod tytułem „Solr: tworzenie własnego filtra” pokazaliśmy implementację bardzo prostego filtra i jak użyć go w Apache Solr. Ostatnio, jeden z czytelników, poprosił nas o rozbudowanie tego tematu i opisanie jak można zwracać więcej, niż jeden term. Postanowiliśmy więc napisać kolejny wpis, który jest rozwinięciem poprzedniego.

Założenia

Załóżmy, że chcemy, aby nasz filtr nie tylko zapisywał do strumienia tokenów odwrócone słowo, ale także zachowywał oryginalne. Zatem ze słowa moje w strumieniu tokenów powinny pojawić się słowa moje oraz ejom. W tym celu zmodyfikujemy wcześniej stworzony filtr.

W tym wpisie pomijamy także wszelkie aspekty konfiguracji i poinformowania Solr o tym, gdzie szukać naszej biblioteki. Jeżeli tematy te nie są znane prosimy o przeczytanie poprzedniego wpisu.

Użyta wersja Solr

W odróżnieniu od poprzedniego wpisu opartego o Solr 3.6, tym razem zdecydowaliśmy się wykorzystać najnowszą, dostępną wersję tego silnika wyszukiwania. W związku z tym implementacja oparta jest o Solr w wersji 4.1. Aby filtr działał w Solr 3.6, wystarczy skorzystać z fabryki przedstawionej w poprzednim wpisie.

Fabryka

Jedyną różnicą w przypadku fabryki jest tylko i wyłącznie klasa jaką rozszerza. W tym wypadku jest to TokenFilterFactory z pakietu org.apache.lucene.analysis.util:

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

Filtr

Sam filtr został mocniej zmodyfikowany i wygląda następująco:

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;
  }
 }
}

Kilka słów wyjaśnienia

Omówmy zatem różnice przedstawionej implementacji w stosunku do pierwotnej wersji filtra:

  • Linia 4 – lista, która będzie nam służyć do przechowywania tokenów.
  • Linia 9 – dodanie atrybutu odpowiedzialnego za przechowywanie pozycji tokena w strumieniu tokenów.
  • Linia 10 – inicjalizacja listy zadeklarowanej w linii 4.
  • Linie 15 – 21 – warunek, który sprawdza, czy na liście są obecne tokeny, które trzeba zapisać do strumienia. Jeżeli są pobiera pierwszy token z listy (usuwając go), ustawia atrybut odpowiedzialny za treść terma, ustawia atrybut odpowiedzialny za pozycję terma i zwraca wartość true, aby poinformować, że przetwarzanie powinno być kontynuowane. Warto zauważyć, iż ze strumienia nie został pobrany żaden token – nie było wywołania input.incrementToken().
  • Linie 23 – 25 – sprawdzenie, czy w strumieniu został jakiś token. Jeżeli nie, to zwracamy wartość false i kończymy przetwarzanie.
  • Linie 27 – 36 – odwrócenie tokena i zapisanie go do listy zadeklarowanej w linii 4. Nie modyfikując tokena, który aktualnie znajduje się w strumieniu i który odwróciliśmy zwracamy wartość true i tym samym informujemy Solr, że przetwarzanie należy kontynuować, a zatem Solr wywoła metodę incrementToken() naszego filtra po raz kolejny i po tym wywołaniu zostanie wywołany kod z linii 15 – 21 ze względu na to, że w liście terms znajduje się token dodany tam w linii 34.

Warto zwrócić uwagę na atrybut umożliwiający nam ustawianie pozycji w strumieniu tokenów. W przypadku powyższej implementacji, każdy odwrócony token, będzie zapisany na następnej pozycji w strumieniu w stosunku do oryginalnego tokena. Tłumacząc najprościej oznacza to, iż tokeny te będą traktowane jako oddzielne słowa. Za chwilę sprawdzimy co się stanie, jeżeli umieścimy odwrócony token na tej samej pozycji co oryginalny.

Działanie

Działanie powyższego filtra postanowiliśmy zilustrować korzystając z panelu administracyjnego Solr:

wlasny_filtr_with_increment

Jak widać, zostały zapisane zarówno oryginalne wartości ala, ma oraz kota, a także ich odwrotności, czyli ala, am i atok. Dodatkowo warto zauważyć, iż każdy z tokenów został zapisany na innej, kolejnej pozycji (atrybut position). A zatem wszystko zadziałało tak jak należy.

Zmiana atrybutu odpowiedzialnego za pozycję w strumieniu

Sprawdźmy jak zmieni się działanie filtra kiedy zmienimy linię:

posIncAttr.setPositionIncrement(1);

na

posIncAttr.setPositionIncrement(0);

Działanie

wlasny_filtr_without_incrementPo zmianie oryginalny token i jego odwrócona wersja zostały umieszczone na tej samej pozycji, co jest dokładnie tym co chcieliśmy osiągnąć. Tym samym będziemy np. w stanie zadać zapytanie w stylu q=”ala am kota”, czyli wymienić oryginalny token na jego odwróconą wersję w przypadku zapytań o frazę.

Kilka słów na koniec

Jak widać tworzenie własnych filtrów nie jest trudne, przynajmniej jeżeli chodzi o warstwę Lucene/Solr. Dostajemy do rąk dość duże możliwości, jeżeli chodzi o kontrolę tego, co finalnie znajdzie się w strumieniu tokenów m.in. dzięki atrybutom strumienia. Oczywiście skomplikowanie danego filtra zależy od logiki, jaką chcemy zaimplementować, jednak nie jest to tematem tego wpisu 🙂

 

This post is also available in: angielski

This entry was posted on poniedziałek, Luty 4th, 2013 at 09:11 and is filed under Lucene, Solr. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.