Более новая версия доступна в разделе документации.

Предисловие

Данный документ является руководством по применению подсистемы полнотекстового поиска (full text search, FTS) платформы CUBA.

Целевая аудитория

Данное руководство предназначено для разработчиков, создающих на платформе CUBA приложения с возможностью полнотекстового поиска. Предполагается, что читатель ознакомлен с Руководством по разработке приложений, доступным по адресу www.cuba-platform.ru/manual.

Дополнительные материалы

Настоящее Руководство, а также другая документация по платформе CUBA доступны по адресу www.cuba-platform.ru/manual.

Подсистема полнотекстового поиска CUBA основана на фреймворке Apache Lucene, поэтому знакомство с его устройством будет полезным. См. lucene.apache.org/core.

Обратная связь

Если у Вас имеются предложения по улучшению данного руководства, мы будем рады принять ваши pull request’ы и issues в исходниках документации на GitHub. Если вы увидели ошибку или несоответствие в документе - пожалуйста, форкните репозиторий и исправьте проблему. Заранее спасибо!

1. Обзор подсистемы FTS

Полнотекстовый поиск (full text search, FTS) платформы CUBA предоставляет возможность неструктурированного поиска по значениям атрибутов сущностей и по содержимому загруженных файлов.

Особенностью реализации полнотекстового поиска CUBA является его ориентация на использование в бизнес-приложениях со сложными моделями данных. В частности, в результатах поиска отображаются не только сущности, напрямую содержащие в некотором атрибуте искомую строку, но и связанные сущности, при отображении которых используется этот атрибут. Например, если сущность Order (заказ) содержит ссылку на Customer (покупателя), и строка поиска содержит название покупателя, то в результатах поиска будет отображен и найденный покупатель, и заказ, на него ссылающийся. Это поведение является логичным для пользователя, который обычно видит название покупателя в экране редактирования заказа.

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

Подсистема полнотекстового поиска содержит два взаимосвязанных механизма: индексирование и собственно поиск.

1.1. Индексирование

Если в приложении подключен компонент fts и включено свойство fts.enabled, то при каждом сохранении в базу данных сущности, подлежащей индексированию, ее идентификатор записывается в очередь на индексацию - таблицу SYS_FTS_QUEUE.

Чтобы процесс индексирования запускался автоматически в фоновом режиме, необходимо создать и активировать назначенное задание. Отдельный асинхронный процесс периодически извлекает идентификаторы изменившихся сущностей из очереди, загружает экземпляры сущностей и индексирует их. Индексация производится с помощью библиотеки Apache Lucene. Документ Lucene содержит следующие поля:

  • Имя сущности и идентификатор экземпляра.

  • Поле all - конкатенация индексируемых атрибутов сущности, но только локальных и типа FileDescriptor. Если атрибут имеет тип FileDescriptor, то индексируется содержимое соответствующего файла. Локальные атрибуты могут быть одного из следующих типов: строка, число, дата, перечисление.

  • Поле links - конкатенация идентификаторов сущностей, которые содержатся в ссылочных индексируемых атрибутах.

Под индексируемыми атрибутами понимаются атрибуты данной сущности и, возможно, связанных с ней сущностей, объявленные в дескрипторе FTS.

Индекс хранится в файловой системе, по умолчанию в подкаталоге ftsindex рабочего каталога приложения (задаваемого свойством cuba.dataDir), в стандартном варианте развертывания это tomcat/work/app-core/ftsindex. Расположение индекса можно изменить с помощью свойства fts.indexDir.

Поиск ведется по следующим правилам:

  • если искомая строка обрамлена кавычками, то ищется соответствующая фраза - набор таких же слов в том же порядке, игнорируя знаки пунктуации;

  • если искомая строка начинается с символа "*", то производится поиск по вхождению строки в любой части слова индексированных данных;

  • в противном случае поиск производится по совпадению искомой строки с началом слов индексированных данных.

Для русского и английского языка поиск производится с учетом морфологии.

Алгоритм поиска состоит из двух этапов:

  • Cначала искомая строка ищется в поле all документов Lucene. Найденные сущности добавляются в список результатов.

  • Если что-то найдено на первом этапе, то идентификаторы найденных сущностей ищутся в поле links документов Lucene. Найденные на втором этапе сущности также добавляются в список результатов.

Warning

Если строка поиска состоит из нескольких слов (и не обрамлена кавычками), то будет произведен поиск всех слов по отдельности по условию ИЛИ. То есть в результаты поиска попадут сущности, содержащие хотя бы одно из введенных слов.

1.3. Пример индексирования и поиска

Рассмотрим приведенный выше простейший пример со связанными сущностями Order и Customer.

Example1Classes

В данном случае, если все атрибуты объектов являются индексируемыми, при индексации двух связанных экземпляров Order и Customer будут созданы два документа Lucene примерно следующего содержания:

id: Order.id = "b671dbfc-c431-4586-adcc-fe8b84ca9617"
all: Order.number + Order.date + Order.amount = "001^2013-11-14^1000"
links: Customer.id = "f18e32bb-32c7-477a-980f-06e9cc4e7f40"
id: Customer.id = "f18e32bb-32c7-477a-980f-06e9cc4e7f40"
all: Customer.name + Customer.email = "John Doe^john.doe@mail.com"

Теперь предположим, что ищется строка "john":

  • Сначала производится поиск в полях all всех документов. Будет найден Customer, и добавлен в результаты поиска.

  • Затем будет произведен поиск идентификатора найденного покупателя в полях links всех документов. Будет найден Order, и он также будет добавлен в результаты поиска.

2. Быстрый старт

В данной главе мы рассмотрим применение подсистемы полнотекстового поиска в приложении-примере Библиотека, который может быть загружен с помощью CUBA Studio.

Разобьем задачу на следующие этапы:

  1. Подключим функциональность поиска к проекту, настроим вызов процесса индексирования и убедимся в его работоспособности.

  2. Настроим конфигурационный файл FTS для работы с сущностями модели данных примера Библиотека.

  3. Для иллюстрации возможностей поиска по загруженным файлам используем сущность BookPublication и функциональность загрузки файлов, описанную в разделе Хранилище файлов в Руководстве по разработке приложений.

2.1. Настройка проекта

  1. Запустите CUBA Studio, перейдите в окно Open project > Samples и загрузите проект Library.

  2. Откройте проект Library в Studio.

  3. Откройте окно свойств проекта Project propertiesEdit и в списке App components включите компонент fts, затем сохраните изменения. Studio предложит пересоздать скрипты Gradle - согласитесь.

  4. Запустите RunDeploy. На этом этапе будет произведена сборка приложения и оно будет развернуто на сервере Tomcat в подкаталоге build/tomcat.

  5. Создайте базу данных приложения: RunCreate database.

  6. Запустите сервер приложения: RunStart application server.

  7. Откройте веб-интерфейс приложения по адресу http://localhost:8080/app. Войдите в систему с именем admin и паролем admin.

  8. Для того, чтобы включить функциональность полнотекстового поиска, в главном меню приложения откройте AdministrationApplication properties, найдите и откройте список fts в таблице свойств, двойным щелчком откройте атрибут fts.enabled и выберите true в поле флажок Current value.

    fts enabled true

После выполнения вышеописанных действий функциональность полнотекстового поиска подключена к приложению и готова к работе. Если выйти из системы и снова выполнить логин, в правой части верхней панели главного окна приложения появится поле поиска. Кроме того, полнотекстовый поиск может использоваться в UI-компоненте Filter.

Однако поиск не будет давать результатов, так как никакие данные еще не проиндексированы.

Для однократного запуска индексации текущего состояния базы данных (а точнее, сущностей, описанных в конфигурационном файле FTS по умолчанию), откройте в главном меню AdministrationJMX Console, найдите JMX-бин app-core.fts:type=FtsManager и вызовите последовательно сначала метод reindexAll(), а затем processQueue().

jmx fts setup

После этого поиск, например, строки "adm" должен выдавать следующие результаты:

2.1 project setup

2.2. Настройка вызова процесса индексирования

Для периодического вызова процесса индексирования удобно воспользоваться механизмом назначенных заданий платформы (см. Руководство по разработке приложенийНазначенные задания CUBA).

Сначала необходимо активировать весь механизм запуска задач. Добавьте в файл app.properties модуля core проекта приложения следующее свойство:

cuba.schedulingActive = true

Перезапустите сервер приложения, войдите в систему пользователем admin, откройте экран JMX Console, найдите и откройте JMX-бин app-core.cuba:type=Scheduling и убедитесь, что атрибут Active имеет значение true.

Далее откройте экран AdministrationScheduled Tasks, нажмите Create и задайте следующие значения атрибутов новой задачи:

  • Defined by: Bean

  • Bean name: cuba_FtsManager

  • Method name: processQueue()

  • Singleton: true

  • Period, sec: 30

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

Warning

Автоматическое индексирование не затрагивает сущности, созданные до его запуска. Для постановки таких сущностей в очередь на индексацию воспользуйтесь методами reindexAll() или asyncReindexAll() JMX-бина app-core.fts:type=FtsManager. Подробнее см. Запуск и настройка переиндексации сущностей.

2.3. Настройка файла конфигурации

При добавлении базового проекта fts в модуле core создаётся новый файл fts.xml. Проверьте его содержимое и при необходимости отредактируйте, включив в него те сущности, которые должны участвовать в полнотекстовом поиске.

<fts-config>
    <entities>

        <entity class="com.sample.library.entity.Author">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.Book">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.BookInstance">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.BookPublication">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.LibraryDepartment">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.LiteratureType">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.Publisher">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.Town">
            <include re=".*"/>
        </entity>

    </entities>
</fts-config>

Это файл конфигурации FTS, в данном случае включающий в индексирование все сущности предметной области со всеми их атрибутами.

Добавьте в файл app.properties модуля core приложения следующее свойство:

cuba.ftsConfig = +com/sample/library/fts.xml

В результате индексироваться будут и сущности, определенные в платформе в файле cuba-fts.xml, и описанные в файле проекта fts.xml.

Перезапустите сервер приложения. На данном этапе полнотекстовый поиск должен работать по всем сущностям модели приложения, а также по сущностям подсистемы безопасности платформы: Role, Group, User.

2.4. Поиск по содержимому загруженных файлов

Теперь добавим функциональность загрузки файлов для каждой публикации и их отображение на экране списка сущности BookPublication.

Для начала необходимо внести изменения в BookPublication. Добавьте новый атрибут file, который будет являться ссылкой на сущность FileDescriptor с отношением много-к-одному. FileDescriptor - это описатель загруженного файла (не путать с java.io.FileDescriptor), позволяющий ссылаться на файл из объектов модели данных. После сохранения изменений добавьте новый атрибут к существующему представлению bookPublication.full и экранам просмотра списка и редактирования сущности BookPublication с помощью Studio.

book publication new attribute

Сгенерируйте новые скрипты обновления БД, выполните команду обновления базы данных и перезапустите сервер приложения. При пересоздании базы данных полнотекстовый поиск по умолчанию отключается. Снова включите флажок Value для атрибута Enable в экране JMX Console, выполните индексацию всех файлов, выйдите из системы снова выполните логин.

Так как мы добавили новый атрибут, в таблице публикаций на экране списка сущности BookPublication теперь появился новый пустой столбец: File. Чтобы его заполнить, откройте экран редактирования строки таблицы, с помощью нового поля File загрузите текстовый файл в систему и нажмите OK. По умолчанию CUBA поддерживает следующие форматы файлов: RTF, TXT, DOC, DOCX, XLS, XSLX, ODT, ODS и PDF.

book publication file is not

Новые файлы теперь отображаются в таблице. Внешний вид таблицы можно отредактировать.

book publication files uploaded

Чтобы переиндексировать имеющиеся в базе данных сущности и файлы в соответствии с новой конфигурацией поиска, откройте в экране JMX Console JMX-бин app-core.fts:type=FtsManager и вызовите последовательно сначала метод reindexAll(), а затем processQueue(). Все вновь добавляемые и изменяемые данные будут индексироваться автоматически, с задержкой, определяемой интервалом вызова назначенного задания, т.е. не более 30 секунд.

В результате, полнотекстовый поиск будет выводить все результаты, включая вхождения в содержимом загруженных файлов.

book publication fts result

Более подробную информацию о FileStorageAPI и FileDescriptor вы можете найти в соответствующих разделах основного руководства.

2.5. Запуск и настройка переиндексации сущностей

Если полнотекстовый поиск был подключен в момент, когда в систему уже внесены какие-либо данные, то эти данные нужно проиндексировать. Добавление записей в очередь на индексацию осуществляется с помощью методов JMX-бина app-core.fts:type=FtsManager. Удобный способ вызвать метод JMX-бина это воспользоваться экраном JMX Console пункта меню Администрирование.

JMX-бин app-core.fts:type=FtsManager предоставляет два метода для постановки сущностей в очередь на индексацию:

  • reindexAll() - синхронно добавляет все сущности, описанные в файле конфигурации FTS, в очередь на индексацию. При больших объемах данных этот процесс может занять длительное время, и в этом случае рекомендуется воспользоваться методом asyncReindexAll().

  • asyncReindexAll() - сущности добавляются в очередь на индексацию пакетами с помощью метода FtsManager.reindexNextBatch(). Размер пакета задается конфигурационным параметром fts.reindexBatchSize. Метод FtsManager.reindexNextBatch() должен вызываться механизмом назначенных заданий или с помощью планировщика Spring. Пока формирование очереди не завершено, индексация не производится.

Приложение A: Файл конфигурации FTS

Файл конфигурации полнотекстового поиска представляет собой XML-файл, как правило располагающийся в каталоге src модуля core и содержащий описание индексируемых сущностей и их атрибутов.

Файл конфигурации FTS задается в свойстве приложения cuba.ftsConfig.

Рассмотрим структуру файла.

fts-config - корневой элемент.

Элементы fts-config:

  • entities - список сущностей, подлежащих индексированию и поиску.

    Элементы entities:

    • entity - описание индексируемой сущности.

      Атрибуты entity:

      • class - Java класс сущности.

      • show - должна ли данная сущность показываться в результатах поиска самостоятельно. Значение false используется для сущностей-связей, которые не интересны пользователю сами по себе, но нужны, например, для связи загруженных файлов и сущностей предметной области. По умолчанию true.

      Элементы entity:

      • include - включить атрибут или несколько атрибутов сущности в индекс.

        Атрибуты include:

        • re - регулярное выражение для отбора атрибутов по имени.

        • name - имя атрибута. Может быть путем (через точку) по ссылочным атрибутам. Тип не проверяется, однако если имя является путем, то возможны два случая:

          1. Конечный атрибут должен быть сущностью (не embeddable), а не простым типом (атрибут простого типа не имеет здесь смысла, он должен индексироваться в своей сущности).

          2. Конечный атрибут должен быть простым типом встраиваемой (embeddable) сущности. Например, если индексируемая сущность имеет поле "address" типа Address (embeddable сущность), то имя атрибута в конфиге fts должно быть "address.city" или "address.street", а не просто "address".

  • exclude - исключить ранее включенный атрибут. Возможные атрибуты такие же, как в элементе include.

  • searchables - Groovy-скрипт для добавления в очередь на индексирование произвольных сущностей, связанных с измененной.

    Например, когда изменяется (добавляется, удаляется) экземпляр CardAttachment, мы должны также переиндексировать связанный с ним экземпляр Card, так как сам собой Card в очередь не встанет, ибо не менялся.

    При запуске в скрипт передаются следующие переменные:

    • searchables - список сущностей, который нужно пополнять.

    • entity - текущий экземпляр сущности, помещаемый в очередь автоматически.

    Пример скрипта:

    <entity class="com.haulmont.workflow.core.entity.CardAttachment" show="false">
        ...
        <searchables>
            searchables.add(entity.card)
        </searchables>
    </entity>
  • searchableIf - Groovy-скрипт для ограничения помещения в очередь некоторых экземпляров индексируемой сущности.

    Например, может быть не нужно индексировать старые версии документов.

    При запуске в скрипт передается переменная entity - текущий экземпляр сущности. Скрипт должен вернуть булевское значение - true для того чтобы индексировать текущий экземпляр, false чтобы игнорировать его.

    Пример скрипта:

    <entity class="com.haulmont.docflow.core.entity.Contract">
        ...
        <searchableIf>
            entity.versionOf == null
        </searchableIf>
    </entity>

Пример файла конфигурации FTS:

<fts-config>
    <entities>

        <entity class="com.sample.library.entity.Author">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.Book">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.BookInstance">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.BookPublication">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.Publisher">
            <include re=".*"/>
        </entity>

        <entity class="com.sample.library.entity.EBook">
            <include name="publication.book"/>
            <include name="attachments.file"/>
        </entity>

        <entity class="com.haulmont.workflow.core.entity.CardAttachment" show="false">
            <include re=".*"/>
            <exclude name="card"/>

            <searchables>
                searchables.add(entity.card)
            </searchables>
        </entity>

    </entities>
</fts-config>

Приложение B: Свойства приложения

В данном разделе перечислены свойства приложения, имеющие отношение к подсистеме полнотекстового поиска.

cuba.ftsConfig

Аддитивный конфигурационный параметр, задает файл конфигурации FTS проекта.

Файл загружается с помощью интерфейса Resources, поэтому может быть расположен в classpath или в конфигурационном каталоге.

Используется в блоке Middleware.

Пример:

cuba.ftsConfig = +com/company/sample/fts.xml

Все свойства, описанные ниже, являются параметрами времени выполнения, хранятся в базе данных и доступны в коде приложения через конфигурационный интерфейс FtsConfig.

fts.enabled

Флаг, разрешающий использование функциональности FTS в проекте.

Значение данного флага может быть оперативно изменено с помощью атрибута Enabled JMX-бина app-core.fts:type=FtsManager.

Значение по умолчанию: false

fts.indexDir

Абсолютный путь к каталогу для хранения индексных файлов. Если не установлен, используется подкаталог ftsindex рабочего каталога приложения (задаваемого свойством cuba.dataDir), в стандартном варианте развертывания это tomcat/work/app-core/ftsindex.

Значение по умолчанию: не установлено

fts.indexingBatchSize

Количество записей, извлекаемое из очереди на индексирование за один вызов метода processQueue().

Данное ограничение актуально для ситуации, когда в очереди на индексацию оказывается сразу очень большое число записей, например после выполнения метода reindexAll() JMX-бина app-core.fts:type=FtsManager. В этом случае индексация выполняется порциями, что занимает больше времени, но создает ограниченную и предсказуемую нагрузку на сервер.

Значение по умолчанию: 300

fts.reindexBatchSize

Количество записей, помещаемое в очередь на индексацию за один вызов метода reindexNextBatch().

Значение по умолчанию: 5000

fts.maxSearchResults

Максимальное количество результатов поиска.

Значение по умолчанию: 100

. . .