10.11.2012

Vaadin Internationlization localization.

Впервые познакомившись с Vaadin я был в восторге от него, но со временем интерес к нему прошел, из-за ряда вещей но кое-какие наработки остались.Вот одна из наработок - это как наиболле просто локализовать программу, точнее где правильно хранить локаль пользователя. Правильно хранить локаль и другие данные нужно в глобальной сессии пользователя, для этого нужно создать ThreadLocal сингильтон который будет содержать объекты ResourceBundle bundle, Locale locale, Application app и сингильтон при этом должен наследовать interface ApplicationContext.TransactionListener - у этого интерфейса есть 2 метода:
public void transactionStart(Application application,
                                 Object transactionData) {     
        if (this.app == application)
            instance.set(this);
    }
который связывает данные пользователя с текушим потоком во ThreadLocal и
public void transactionEnd(Application application,
                               Object transactionData) {
        // Clear the reference to avoid potential problems
        if (this.app == application)
            instance.set(null);
    }
который разрывает эту связь. Коротко что мы здесь сделали - ThreadLocal позволяет нам хранить разные данные для каждого пользователя, т.е. для каждого потока(сессии пользователя) будут разные значения объектов ResourceBundle bundle, Locale locale.
package com.vit;

import com.vaadin.Application;
import com.vaadin.service.ApplicationContext;

import java.io.Serializable;
import java.util.Locale;
import java.util.ResourceBundle;

/** Holds data for one user session. */
public class AppData implements ApplicationContext.TransactionListener, Serializable {
    private ResourceBundle bundle;
    private Locale locale;   // Current locale
    private Application app; // For distinguishing between apps

    private static ThreadLocal instance =
        new ThreadLocal();

    public AppData(Application app) {
        this.app = app;
        // It's usable from now on in the current request
        instance.set(this);
    }

    @Override
    public void transactionStart(Application application,
                                 Object transactionData) {
        // Set this data instance of this application
        // as the one active in the current thread.
        if (this.app == application)
            instance.set(this);
    }

    @Override
    public void transactionEnd(Application application,
                               Object transactionData) {
        // Clear the reference to avoid potential problems
        if (this.app == application)
            instance.set(null);
    }

    public static void initLocale(Locale locale,
                                  String bundleName) {
        instance.get().locale = locale;
        instance.get().bundle =
            ResourceBundle.getBundle(bundleName, locale);
    }

    public static Locale getLocale() {
        return instance.get().locale;
    }

    public static String getMessage(String msgId) {
        return instance.get().bundle.getString(msgId);
    }
}
Дальше создаем ComboBox который позволяет нам менять текушую локаль и соответственно настроить под неё ResourceBundle.
package com.vit;

import com.vaadin.ui.ComboBox;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

public class ComboBoxLocale extends ComboBox{
    private final List locales = Arrays.asList(
            new Locale("ru", "RU"), Locale.US, Locale.ENGLISH, AppData.getLocale());

    public ComboBoxLocale(String caption) {
        super(caption);
        for (Locale l:locales) {
            addItem(l);
            setItemCaption(l,l.getDisplayName());
        }
        setNullSelectionAllowed(false);
        setImmediate(true);
        setVisible(true);
    }

    @Override
    public void attach() {
        super.attach();
        setValue(AppData.getLocale());
    }
}
Далее создаем файл ресурсов для английского языка MyVaadinApplication_en.properties:
Button.text=Click me English
Label.text=Thank you for clicking Еnglish
и для русского MyVaadinApplication_ru_RU.properties:
Button.text=Click me Russian
Label.text=Thank you for clicking Russian
И наконец все встраиваем в наше веб приложение.
package com.vit;

import com.vaadin.Application;
import com.vaadin.data.Property;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.*;
import com.vaadin.ui.themes.Runo;

import java.util.Locale;

public class MyVaadinApplication extends Application {
    private Window window;
    public ComboBoxLocale comboBoxLocale;
    public Button button;

    @Override
    public void init() {
        setTheme("runo");
        window = new Window("Hello Vaadin!");
        setMainWindow(window);
        window.setImmediate(true);
        // Create the application data instance
        AppData sessionData = new AppData(this);
        // Register it as a listener in the application context
        getContext().addTransactionListener(sessionData);
        // Initialize the session-global data
        AppData.initLocale(getLocale(),
                MyVaadinApplication.class.getSimpleName());

        comboBoxLocale = new ComboBoxLocale("Change Locale:");
        comboBoxLocale.setWidth("150px");
        comboBoxLocale.setValue(getLocale());
        Property.ValueChangeListener listener = new Property.ValueChangeListener() {
            public void valueChange(Property.ValueChangeEvent event) {
                AppData.initLocale((Locale) comboBoxLocale.getValue(),
                        MyVaadinApplication.class.getSimpleName());
                setLocale((Locale) comboBoxLocale.getValue());
                window.showNotification(
                        ((Locale) comboBoxLocale.getValue()).getDisplayName() + " : " +
                                AppData.getLocale().getLanguage() );
                button.setLocale((Locale) comboBoxLocale.getValue());
                button.setCaption(AppData.getMessage("Button.text"));
            }
        };
        comboBoxLocale.addListener(listener);
        window.addComponent(comboBoxLocale);

        button = new Button(AppData.getMessage("Button.text"));
        button.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                window.addComponent(new Label(AppData.getMessage("Label.text")));
            }
        });
        button.addStyleName(Runo.BUTTON_SMALL);
        window.addComponent(button);
    }
}
Опишу пару строк важных строк создаем наш сингильтон - AppData sessionData = new AppData(this); регистрируем наш обработчик - getContext().addTransactionListener(sessionData); устанвливаем текушую локаль и связываем с ним ResourceBundle - AppData.initLocale(getLocale(),MyVaadinApplication.class.getSimpleName()); создаем кнопку и выдергиваем строчку Button.text из ResourceBundle - button = new Button(AppData.getMessage("Button.text")); Вот и все на последок привожу файл pom.xml с зависимостями:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.vit</groupId>
  <artifactId>VaadinTest11</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Vaadin Web Application</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <vaadin.version>6.6.6</vaadin.version>
    <gwt.version>2.3.0</gwt.version>
    <gwt.plugin.version>2.2.0</gwt.plugin.version>
  </properties>

  <build>
    <finalName>test</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>

      <!-- A simple Jetty test server at http://localhost:8080/VaadinTest11 can be launched with the Maven goal jetty:run 
        and stopped with jetty:stop -->
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.24</version>
        <configuration>
          <stopPort>9966</stopPort>
          <stopKey>VaadinTest11</stopKey>
          <!-- Redeploy every x seconds if changes are detected, 0 for no automatic redeployment -->
          <scanIntervalSeconds>0</scanIntervalSeconds>
          <!-- make sure Jetty also finds the widgetset -->
          <webAppConfig>
            <contextPath>/VaadinTest11</contextPath>
            <baseResource implementation="org.mortbay.resource.ResourceCollection">
              <!-- Workaround for Maven/Jetty issue http://jira.codehaus.org/browse/JETTY-680 -->
              <!-- <resources>src/main/webapp,${project.build.directory}/${project.build.finalName}</resources> -->
              <resourcesAsCSV>src/main/webapp,${project.build.directory}/${project.build.finalName}</resourcesAsCSV>
            </baseResource>
          </webAppConfig>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <repositories>
    <repository>
      <id>vaadin-snapshots</id>
      <url>http://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>vaadin-addons</id>
      <url>http://maven.vaadin.com/vaadin-addons</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>com.vaadin</groupId>
      <artifactId>vaadin</artifactId>
      <version>${vaadin.version}</version>
    </dependency>
  </dependencies>

</project>

10.10.2012

Apache Ivy

Познакомитесь Apache Ivy очень интересный проект для программистов Java. В чем его интересность - он позволяет разработчику управлять зависимостями java библиотек при компиляции и развертывании java  приложений т.е. автоматизировать сборку приложения в области закачки нужных вам java библиотек. Ivy тесно интегрирован с проектом Apache Ant - java-утилитой для автоматизации процесса сборки java приложений. В принципе как я понял из документации Ivy можно использовать и отдельно без ant, но куда интереснее в связке с Ant. При работе над проектом все недостаюшие вам ява библиотеки будут скачаны  из репозитария. По умолчанию используется центральный Maven репозитарий, но если необходимо то можно использовать свой допустим Apache Archiva - http://archiva.apache.org, все гибко настраивается в Ivy. Давайте расмотрим все по порядку: установку,настройку, и работу примера. Для установки  Apache Ivy вам нужно скачать с официального сайта последнию версию, распаковать архив, с копировать ivy*.jar (в моем случае ivy-2.2.0.jar) в lib директорию Apache Ant, т.е. у вас на компьютере ivy*.jar должна находиться в ANT_HOME/lib. Давайте перейдем к простому примеру который поставляется вместе с Ivy: src/example/hello-ivy, пример состоит из 3-х файлов - Hello.java простой файл при компиляции которому нужны 2 java библиотеки: commons-lang-2.0.jar и commons-cli-1.0.jar т.е. можно сказать что наше приложение зависит от 2 артифактов, которые мы каким-то образом должны получить. Все зависимости мы должны описать в файле конфигурации ivy.xml:
<ivy-module version="2.0"> - все настройки начинаются этим тегом и соотвественно заканчиваются, версия файла настройки 2.0
<info organisation="org.apache" module="hello-ivy"/> информационный тег существенной роли не играет, нужен для формирования имени файлов отчета в кеше - org.apache-hello-ivy-default.xml.
<dependencies> - начало описания наших зависимотей
        <dependency org="commons-lang" name="commons-lang" rev="2.0"/> - 1-й артифакт который нам нужен 
        <dependency org="commons-cli" name="commons-cli" rev="1.0"/>   - и 2-й артифакт который нам нужен 
</dependencies>
Т.к. по умолчанию используется публичный репозиторий ibiblio http://repo2.maven.org/maven2 , будем разрешать зависимости используя его - найти нужную зависимость можно вручную:) Для этого, необходимо нам Maven POM Dependency(pom.xml если вы мигрируете с Maven'a) или maven-metadata.xml. Допустим мне нужна poi библиотека(артифакт) я захожу на репозитарий http://repo2.maven.org/maven2/poi/poi-2.5-final/ открываю maven-metadata.xml и переписываю зависимость

  <dependency>
    <groupId>poi</groupId>
    <artifactId>poi-2.5-final</artifactId>
    <version>20040302</version>
  </dependency>
как <dependency org="groupId" name="artifactId" rev="version"/>:
 <dependency org="poi" name="poi-2.5-final" rev="20040302"/>
Вот разобрались с нашими зависимостями, перейдем к файлу конфигурации ant - build.xml:
<project name="hello-ivy" default="run" xmlns:ivy="antlib:org.apache.ivy.ant"> 
    1-е нам нужно подключить Ivy - делается это следующим образом подключаем новое 
    пространство имен xmlns:ivy="antlib:org.apache.ivy.ant" - и все что начинается с <ivy: ... />
    будет относится к вызову команд ivy а не ant'a например: <ivy:retrieve/> - команда ivy.
    <!-- переменные нашего проекта -->
    <property name="lib.dir" value="lib" />
    <property name="build.dir" value="build" />
    <property name="src.dir" value="src" />
    
    <!-- пути где лежат наши библиотеки(артифакты) они будут 
     использоваться при компиляции и выполнении программы  -->
    <path id="lib.path.id">
        <fileset dir="${lib.dir}" />
 </path>
    <path id="run.path.id">
        <path refid="lib.path.id" />
        <path location="${build.dir}" />
    </path>
    
    <!-- ================================= 
          target: resolve              
         ================================= -->
    <target name="resolve" description="--> retreive dependencies with ivy">
        <ivy:retrieve/> - с начала происходит загрузка наших зависимостей в локальный репозитарий,
         по умолчанию в ваш домашний каталог (user home), в директорию .ivy2/cache. Оттуда они
        загружается в нашу lib папку проекта. При следующей компиляции проекта  
        сначало будет просмотрен наш кеш на наличие загруженных артифактов(java библиотек)
        и соответственно с репозитария Maven'a ни чего не загрузится что приведет к боллее быстрой
        компиляции и выполнению проекта.
    </target>    
    

    <!-- ================================= 
          target: run сдесь собирается и выполняется наш проект.
         ================================= -->
    <target name="run" depends="resolve" description="--> compile and run the project">
        <mkdir dir="${build.dir}" />
        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" />
     <property name="msg" value="hello ivy !"/>
        <java classpathref="run.path.id" classname="example.Hello">
         <arg value="-message"/>
         <arg value="${msg}"/>
     </java>
    </target>

</project>

В качестве cache у Ivy используется один единственный .ivy2/сache по умолчанию и один единственный - публичный ibiblio resolver. Сдесь я ввел новое понятие - resolver место от куда мы загружаем наши артифакты, мы можем организовывать цепочку (chain) из resolver'ов: заглянем  в проект chained-resolvers/settings/ivysettings.xml:
<ivysettings>
  <settings defaultResolver="chain-example"/>
  <resolvers>
    <chain name="chain-example"> - наша цепочка - объявление
      <filesystem name="libraries"> - сначало ищем артифакты в файловой системе
        <artifact pattern="${ivy.settings.dir}/repository/[artifact]-[revision].[ext]" />
      </filesystem>
      <ibiblio name="ibiblio" m2compatible="true" /> - потом обрашаемся к Maven ibiblio
    </chain>
  </resolvers>
</ivysettings>
Существуют различные resolver'ы способные загружать артифакты не только с web-серверов, но и ssh, ftp, smb, webdav и других, полный список смотрите в документации. Так вернемся к нашим баранам нам осталось только запустить  наш проект на выполнение. Сразу предупрежу если вы выходите в интернет через proxy-server то вам нужно настроить соединение для Ivy установите переменную окружения:
 ANT_OPTS=-Dhttp.proxyHost=myproxyhost -Dhttp.proxyPort=8080 -Dhttp.proxyUserName=myproxyusername -Dhttp.proxyPassword=myproxypassword -Dhttps.proxyHost=myproxyhost -Dhttps.proxyPort=8080
Кратко о главном - как использовать Ivy с Ant для этого нам надо:
  1. Описать артефакты, необходимые для сборки проекта (файл ivy.xml)
  2. Описать репозитории - цепочки (chain), из которых эти артефакты можно получить (файл ivysettings.xml)
  3. Описать в Ant - build.xml вызов задач Ivy.

JSF Spring совместное ипользование.

В свое время я не нашел в интернете простого примера как связать JSF и Spring. Давайте напишем вместе простое hello world - приложение, оно будет состоять из 2 - объектов, 1-й helloWorldMessage - создается spring'om  и вставляется в jsf-объект helloWorld. Для связки 2-х технологий нужно только правильно настроить файлы конфигураций JSF и Spring. Нам понадобится - Apace Tomcat, набор библиотек jsf1.2, набор библиотек spring-framework-2.5.5:
 commons-codec.jar
 commons-logging.jar
 jsf-api.jar
 jsf-impl.jar
 jstl.jar
 jtds-1.2.4.jar
 spring-security-core-2.0.4.jar
 spring-webmvc.jar
 spring.jar
 standard.jar 

Первое нам надо, добавить org.springframework.web.context.ContextLoaderListener в  web.xml файл настройки, для того чтобы включить механизм Dependency Injection - spring'a.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">  
 
  <!-- настраиваем spring для работы -->
    <listener>
      <listener-class>
        org.springframework.web.context.ContextLoaderListener
      </listener-class>
    </listener>

 <!-- обработчик  jsf -->
    <servlet>
      <servlet-name>faces</servlet-name>
      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>faces</servlet-name>
      <url-pattern>*.faces</url-pattern>
    </servlet-mapping>
    <!-- welcome-file  -->
    <welcome-file-list>
         <welcome-file>index.jsp</welcome-file>
     </welcome-file-list>
</web-app>

По умолчанию будем использовать файл настройки spring - applicationContext.xml для конфигурирования наших bean's:
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
        "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <!-- создаем bean c именем helloWorldMessage и устанавливаем его свойство message равным Hello World! Vit -->
    <bean id="helloWorldClass" class="com.vit.HelloWolrd" >
     <property name="message" value="Hello World! Vit" />
    </bean>
</beans>
Ниже привожу код объекта создаваемого spring'om:
 
package com.vit;

public class HelloWorld  {
    // наше свойство устанавливаемое spring
    private String message; 

    public HelloWorld() {
    }
    
    public String getMessage() {
        return message;
    }
    // метод вызываемый spring  при установке свойства message
    public void setMessage(String message) {
        this.message = message;
    }
}
Настроим jsf - нам нужно определить наш jsf bean делается это все в файле faces-config.xml:
 
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" version="1.2">
    <!-- нужно вставить обработчик который передает jsf-контексту, bean'ы созданные spring'om-->
    <application>
      <variable-resolver>
        org.springframework.web.jsf.DelegatingVariableResolver
      </variable-resolver>
    </application>

    <managed-bean>
        <managed-bean-name>helloWorld</managed-bean-name>
        <managed-bean-class>
            com.jsf.HelloWorldJSF
        </managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>hello</property-name>
            <!-- вставляем наш созданный sprin'om объект helloWorldMessage в jsf-объект класса HelloWorldJSF -->
            <value>#{helloWorldClass}</value>
        </managed-property>
    </managed-bean>
</faces-config>
Ниже привожу код класса создаваемого jsf:
 
package com.jsf;

import com.vit.HelloWolrd;

public class HelloWorldJSF {
    //свойство куда вставляется helloWorldClass-объект
    //который любезно создан sprin'om 
    HelloWolrd hello;

    public HelloWolrd getHello() {
        return hello;
    }

    public void setHello(HelloWolrd hello) {
        this.hello = hello;
    }
}
На страничке index.jsp мы переадресуем наш запрос(request) нашей jsf - страничке:
 
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <META HTTP-EQUIV="Refresh" CONTENT="0;URL=success.faces">
</head>
<body>
<p>Loading ...</p>
</body>
</html>
страничка success.jsp:
 
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Hello World</title>
</head>
<body>
<f:view>
    <h:outputLabel>The message:</h:outputLabel>
    <h:outputText value="#{helloWorld.hello.message}" />
</f:view>
</br>
 Сдесь мы перемещаемся по свойствам объектов(helloWorld.hello.message) и выводим  сообщение  The message:Hello World! Vit 
</body>
</html>
Вот и все!

10.09.2012

Lucene java.

Apache Lucene набор библиотек java при помощи которых можно организовать полноценный
поиск в интерисующих вас данных(текстовых данных). На данный момент существует несколько поисковых движков использующих api Lucene таких как Apache Solr, Nutch, Hibernate Search. Без основ трудно разобраться и настроить их (движки), как бы вам не хотелось нужна база, основа понимания Lucene. Эту статью я хочу посвятить основам поиска на Lucene и так начнем наше погружение. Начнем как не странно с теории. Пусть у нас есть 2 книги: 

Книга 1:
название первой книги - "Java in Action" 
ISBN "1-932394-28-2"
год выпуска - "2011"

Книга 2:
название второй книги - "Java and Flex" 
ISBN "1-932394-28-1"
год выпуска - "2010"


Давайте введем понятие Document. Document - это объект для поиска, который состоит
из полей(Fields). В данном примере Книга 1, Книга 2 - это Документы, которые состоят из полей:
Java in Action, 1-932394-28-2, 2011 и Java and Flex, 1-932394-28-1, 2010.
Структура Lucene индекса такова что он содержит последовательность документов(Documents),
котрые в свою очередь состоит из полей, поля имеют имена, количество и последовательность полей в каждом документе должна совпадать, а так же и их "тип данных", т. е. если в Документе первое поле содержит название книги, то и во втором Документе первое поле должно содержать название книги, а не год выпуска. 
Процесс поиска Lucene состоит в том что текстовое поле с помощью анализатора Lucene
преобразуется в специальный индексный вид, terms и они сохраняются в индексе Lucene.
Эти terms в дальнейшем используются для поиска в запросах по индексу Lucene. Важную
роль при разбивки поля на terms играет анализатор, в зависимости от того какой анализатор
вы выбрали вы получите те или иные terms. Анализатор преобразует текстовое поле обычно
несколькими операциями, такими как извлечение "слова", сброс пунктуации (знаков припинания и т. д.), удаление частиц стоп-слов (stop words): in, a, the, at and e.t.c. приведение всех слов к нижнему регистру букв (lowercasing) - иногда называемую нормализацией (normalizing), удаление  повторяющихся "слов", нахождение основы слова("корня") - стемминг (stemming), или получение из слова его нормальной/словарной формы - lemmatization.  Процесс разбитетия текста по выше перечисленным правилам называется - tokenization, а сами части текста - tokens. Если кратко сказать term - это комбинация имени поля и token'a. Настало время закрепить всю теорию на практике и начнем все с анализаторов. Перед индексированием поле проходит через анализатор который разбивает поле на terms, анализаторы бывают разные и предназначенны они для различных случаев.
Перичислю некоторые из них:
WhitespaceAnalyzer - Разделяет на terms по пробелам.
SimpleAnalyzer - Производит деление текста по словам используя в качестве разделителей
любые символы кроме букв, переводит в нижний регистр.
StopAnalyzer - Убирает стоп-слова(Естественно Английские :) ) и переводит текст в нижний регистр.
StandardAnalyzer - Анализирует текст, используя сложные грамматические правила. Переводит текст в нижний регистр и убирает стоп-слова.
EnglishAnalyzer и RussianAnalyzer (в предыдущих версиях носил название SnowballAnalyzer)
- Переводит текст в нижний регистр, убирает стоп-слова и преобразует слово используя стемминг(stemming):
Давайте рассмотрим код - он разбивает три строки
"The quick brown fox jumped over the LaZy dogs",
"Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала",
"XY&Z Corporation - xyz@Example.com":


package org.vit;

import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.en.EnglishAnalyzer;
import org.apache.lucene.analysis.en.EnglishMinimalStemmer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.ru.RussianAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.util.Version;
import org.apache.lucene.util.AttributeSource;

import java.io.IOException;
import java.io.StringReader;


public class AnalysisDemo {
    private static final String[] strings = {
            "The quick brown fox jumped over the LaZy dogs",
            "Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала",
            "XY&Z Corporation - xyz@Example.com"};

    private static final Analyzer[] analyzers = new Analyzer[]{
            new WhitespaceAnalyzer(Version.LUCENE_31),
            new SimpleAnalyzer(Version.LUCENE_31),
            new EnglishAnalyzer(Version.LUCENE_31),
            new RussianAnalyzer(Version.LUCENE_31),
            new StopAnalyzer(Version.LUCENE_31),
            new StandardAnalyzer(Version.LUCENE_31)
    };

    public static void main(String[] args) throws IOException {
        for (int i = 0; i < strings.length; i++) {
            analyze(strings[i]);
        }
    }

    private static void analyze(String text) throws IOException {
        System.out.println("Analzying \"" + text + "\"");
        for (int i = 0; i < analyzers.length; i++) {
            Analyzer analyzer = analyzers[i];
            System.out.println("\t" + analyzer.getClass().getName() + ":");
            System.out.print("\t\t");
            TokenStream stream = analyzer.tokenStream("contents", new StringReader(text));
            while (true) {
                if (!stream.incrementToken()) break;
                AttributeSource token = stream.cloneAttributes();
                CharTermAttribute term =(CharTermAttribute) token.addAttribute(CharTermAttribute.class);
                System.out.print("[" + term.toString() + "] "); //2
            }
            System.out.println("\n");
        }
    }

}
Результат работы программы:
Analzying "The quick brown fox jumped over the LaZy dogs"
 org.apache.lucene.analysis.WhitespaceAnalyzer:
  [The] [quick] [brown] [fox] [jumped] [over] [the] [LaZy] [dogs] 

 org.apache.lucene.analysis.SimpleAnalyzer:
  [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] 

 org.apache.lucene.analysis.en.EnglishAnalyzer:
  [quick] [brown] [fox] [jump] [over] [lazi] [dog] 

 org.apache.lucene.analysis.ru.RussianAnalyzer:
  [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] 

 org.apache.lucene.analysis.StopAnalyzer:
  [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] 

 org.apache.lucene.analysis.standard.StandardAnalyzer:
  [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] 

Analzying "Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала"
 org.apache.lucene.analysis.WhitespaceAnalyzer:
  [Быстрая,] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] 

 org.apache.lucene.analysis.SimpleAnalyzer:
  [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] 

 org.apache.lucene.analysis.en.EnglishAnalyzer:
  [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] 

 org.apache.lucene.analysis.ru.RussianAnalyzer:
  [быстр] [рыж] [лисиц] [препрыгнул] [ленив] [собак] [быст] [убежа] 

 org.apache.lucene.analysis.StopAnalyzer:
  [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] 

 org.apache.lucene.analysis.standard.StandardAnalyzer:
  [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] 

Analzying "XY&Z Corporation - xyz@Example.com"
 org.apache.lucene.analysis.WhitespaceAnalyzer:
  [XY&Z] [Corporation] [-] [xyz@Example.com] 

 org.apache.lucene.analysis.SimpleAnalyzer:
  [xy] [z] [corporation] [xyz] [example] [com] 

 org.apache.lucene.analysis.en.EnglishAnalyzer:
  [xy] [z] [corpor] [xyz] [example.com] 

 org.apache.lucene.analysis.ru.RussianAnalyzer:
  [xy] [z] [corporation] [xyz] [example.com] 

 org.apache.lucene.analysis.StopAnalyzer:
  [xy] [z] [corporation] [xyz] [example] [com] 

 org.apache.lucene.analysis.standard.StandardAnalyzer:
  [xy] [z] [corporation] [xyz] [example.com]


Самый продвинутый StandardAnalyzer для английского текста но если копнуть глубже то любой анализатор это набор фильтров, так что для себя любимого можно написать свой анализатор если вас неустраивают стандартные. В lucene так же есть различные языковые анализаторы  такие как EnglishAnalyzer и RussianAnalyzer и для других языков (snowball) - котрые производят обрезку окончаний для своей локали (stemming).
В принципе для поиска по индексу, нужно - первое создать индекс и второе написать програму поиска по индексу, чем мы и займемся. Прежде всего нужно выяснить что мы будем искать и где - работать будем с текстом, искать слова в тексте.
Формирует Индекс объект класса IndexWriter - ему нужно указать где создавать индекс - в памяти, на диске, в БД; какой анализатор использовать, версию индекса, создать новый
индекс или добавить в существующий. Далее для каждого документа, создать документ, в него добавить поля, сам документ добавить в объект класса IndexWriter, в конце оптимизировать индекс для более быстрого поиска. Давайте расмотрим пример:
package org.vit;

import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.ru.RussianAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.util.Version;

import java.io.IOException;
import java.io.File;

public class IndexerDemo {
    public static void main(String[] args){
        try {
            FSDirectory FSD =  FSDirectory.open(new File(".//Index"));  //индекс будем хранить в директории ./Index
            RussianAnalyzer analyzer = new  RussianAnalyzer(Version.LUCENE_31);  //какой используем анализатор
            IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_31,analyzer); //наш конфиг 
            iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE); //содаем всегда новый индекс
            IndexWriter writer = new IndexWriter(FSD, iwc); //создаем объект IndexWriter - или по другому индекс
            Document doc = new Document(); //создаем документ
            doc.add(new Field("id","1",Field.Store.YES,Field.Index.NOT_ANALYZED)); //добавляем 1-е поле в документ
            doc.add(new Field("name","Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала"
            ,Field.Store.YES,Field.Index.ANALYZED)); //добавляем 2-е поле в документ
            writer.addDocument(doc); //добавляем документ в индекс
            Document doc1 = new Document(); //повторяем тежe действия
            doc1.add(new Field("id","2",Field.Store.YES,Field.Index.NOT_ANALYZED));
            doc1.add(new Field("name","Рыжий лис",Field.Store.YES,Field.Index.ANALYZED));
            doc1.setBoost(10.0f);
            writer.addDocument(doc1);
            Document doc2 = new Document();
            doc2.add(new Field("id","3",Field.Store.YES,Field.Index.NOT_ANALYZED));
            doc2.add(new Field("name","Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала"
            ,Field.Store.YES,Field.Index.ANALYZED));
            writer.addDocument(doc2);
            Document doc3 = new Document();
            doc3.add(new Field("id","3",Field.Store.YES,Field.Index.NOT_ANALYZED));
            doc3.add(new Field("name","Рыжий лис",Field.Store.YES,Field.Index.NOT_ANALYZED));
            writer.addDocument(doc3);
            writer.optimize(); //оптимизируем индекс
            writer.close();  //все закрываем
            FSD.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Запустив программу вы получите lucene индекс в директории ./Index.Хочу обратить ваше внимание на еще одну важную деталь это как создаются поля:
new Field("name", - имя поля
"Рыжий лис" - текст поля
,Field.Store.YES, - текст поля будет сохраняться в индексе или нет Field.Store.NO
Field.Index.* - отвечает за то как будет индексироваться поле т.е. его текст
возможные варианты:
NO - текст поля не индексируется, поле не может участвовать в поиске оно просто сохраняется
используется как набор  Field.Store.YES,Field.Index.NO, набор  Field.Store.NO,Field.Index.NO -
не допустим;
ANALYZED - текст поля пропускается через наш анализатор и получаем tokens которые потом сохраняются в индексе, поле участвует в поиске + расчитывается приоритет который в дальнейшем может быть использован при поиске;
NOT_ANALYZED - текст поля не пропускается через анализатор, весь текст считается одним единственным token который потом сохраняется в индексе, поле участвует в поиске + расчитывается приоритет который в дальнейшем может быть использован при поиске;
NOT_ANALYZED_NO_NORMS - для экспертов, текст поля не пропускается через анализатор, весь текст считается одним единственным token который потом сохраняется в индексе, поле участвует в поиске + еще у поля отключена фишка как приоритет при поиске;
ANALYZED_NO_NORMS - для экспертов, текст поля пропускается через наш анализатор и получаем tokens которые потом сохраняются в индексе, поле участвует в поиске + еще у поля отключена фишка как приоритет при поиске; на счет приоритета по умолчанию он стоит 1.0f у всех полей, установить его можно setBoost(10.0f) и чем выше число тем выше приоритет при поиске.
Переходим к поиску, ищет в индексе - объект класса IndexSearcher, ему нужно указать где лежит индекс - в памяти, на диске, в БД; какой анализатор использовать/неиспользовать, версию индекса, поле поиска, что ищем (token). Расмотрим пример:

package org.vit;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.search.*;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.analysis.ru.RussianAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.util.Version;
import java.io.File;
import java.io.IOException;

public class SearchDemo {
    public static void main(String[] args) {
        try {
            Directory directory = FSDirectory.open(new File(".//Index")); //где находится индекс
            IndexSearcher is = new IndexSearcher(directory); //объект поиска
            QueryParser parser = new QueryParser(Version.LUCENE_31, "name", new RussianAnalyzer(Version.LUCENE_31));//поле поиска + анализатор  
            Query query = parser.parse("лиса"); //что ищем 
            TopDocs results =is.search(query, null, 10); //включаем поиск ограничиваемся 10 документами, results содержит ...
            System.out.println("getMaxScore()="+results.getMaxScore()+" totalHits="+results.totalHits); // MaxScore - наилучший результат(приоритет), totalHits - количество найденных документов
            for (ScoreDoc hits:results.scoreDocs) { // получаем подсказки
                Document doc = is.doc(hits.doc); //получаем документ по спец сылке doc
                System.out.println("doc="+hits.doc+" score="+hits.score);//выводим спец сылку doc + приоритет
                System.out.println(doc.get("id")+" | "+doc.get("name"));//выводим поля найденного документа 
            }
            directory.close();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

выполнив программу получаем результат:

getMaxScore()=7.0 totalHits=3 //наилучший приоритет 7.0 всего совпадений 3
doc=1 score=7.0 //внутреннея сылка lucene 1 приоритет 7.0
2 | Рыжий лис //поля найденного документа
doc=0 score=0.3125 //если приоритет совпадает 0.3125(doc=0) и 0.3125(doc=2) то сортируются результаты по doc
1 | Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала
doc=2 score=0.3125
3 | Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала 

Обратите внимание еще на одну вещь поиск не вывел документ doc3, при создании индекса
мы указали характеристи поля так:
doc3.add(new Field("name","Рыжий лис",Field.Store.YES,Field.Index.NOT_ANALYZED));
так как при сохранении индекса было указано что поле не будет обрабатываться анализатором
(Field.Index.NOT_ANALYZED) то и значение поля "Рыжий лис" - это один целый token,
а так как мы ищем token "лис" то у нас не происходит совпадения этих token'ов,
и документ не находится.

в программе использовал следующие библиотеки:
lucene-analyzers-3.1.0.jar
lucene-core-3.1.0.jar

PS lucene работает с текстом в кодировке UTF-8 если вам захочется проиндексировать текст допустим в кодировке windows-1251  то вам прежде нужно перевести его в UTF-8 а затем индексировать.

hello blog

Привет это мой первый пост в блоге посвященный Java, и так полетели  в мой мир ...