Предисловие

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

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

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

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

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

Подсистема полнотекстового поиска CUBA основана на фреймворке Apache Lucene, поэтому знакомство с его устройством будет полезным. Смотрите http://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.

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

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

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

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

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

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

  • Сначала искомая строка ищется в поле 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. Установка

Для добавления аддона в проект выполните следующие действия:

  1. Кликните дважды Add-ons в дереве проекта CUBA.

    addons
  2. Перейдите на вкладку Marketplace и найдите аддон Full Text Search.

    fts addon
  3. Кликните Install и затем Apply & Close.

    addon install
  4. Кликните Continue в появившемся диалоговом окне.

    addon continue

Аддон, соответствующий используемой версии платформы, будет добавлен в проект.

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

В данной главе мы рассмотрим применение подсистемы полнотекстового поиска в тестовом приложении Библиотека, исходный код которого доступен на GitHub.

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

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

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

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

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

  1. Загрузите и распакуйте исходный репозиторий приложения Библиотека или клонируйте проект, используя git:

      git clone https://github.com/cuba-platform/sample-library-cuba7
  2. Откройте проект Library, как описано в разделе Открытие существующего проекта Руководства пользователя CUBA Studio.

  3. Добавьте в проект аддон, как это описано в разделе Установка.

  4. Создайте базу данных на локальном сервере HyperSQL. Выберите пункт меню CUBACreate database.

  5. Запустите приложение: кликните на кнопку run_button рядом с выбранной конфигурацией CUBA Application в главной панели инструментов. Ссылка на локальный сервер в секции Runs at…​ позволит перейти к приложению непосредственно из Studio.

  6. Откройте приложение Библиотека.

    Логин и пароль пользователя − admin / admin.

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

    fts enabled true

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

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

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

jmx fts setup

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

2.1 project setup

3.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: false

  • Period, sec: 30

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

Tip

При работе с кластером серверов приложения, каждый сервер, предоставляющий функциональность поиска, должен поддерживать свою копию индекса. Для этого:

  1. Перечислите все сервера кластера в свойстве приложения fts.indexingHosts.

  2. Убедитесь что задача индексации, сконфигурированная выше, не имеет признака Singleton.

После этого изменения будут ставится в очередь для каждого сервера по отдельности; сервера будут брать из очереди свои собственные записи и обновлять свои индексы.

Warning

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

3.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

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

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

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

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

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

book publication new attribute

После сохранения изменений добавьте новый атрибут к существующему представлению bookPublication.full. Также следует добавить атрибут на экраны просмотра списка и редактирования сущности BookPublication. Для этого поместите курсор на строку, в которой описан атрибут, и нажмите Alt+Enter. Выберите Add entity attribute to screens и в появившемся окне выберите экраны, на которые нужно добавить новый атрибут.

Сгенерируйте новые скрипты обновления БД, выполните команду обновления базы данных и перезапустите сервер приложения. При пересоздании базы данных полнотекстовый поиск по умолчанию отключается. Снова включите флажок 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 ищите в соответствующих разделах основного руководства.

3.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
cuba.gui.genericFilterFtsTableTooltipsEnabled

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

Интерфейс: ClientConfig

Хранится в базе данных.

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

cuba.gui.genericFilterFtsDetailsActionEnabled

Флаг включает элемент контекстного меню "Детали полнотекстового поиска" в таблице или дата-гриде в случае, если поиск осуществлялся с помощью универсального фильтра.

Интерфейс: ClientConfig

Хранится в базе данных.

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

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

fts.enabled

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

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

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

fts.indexDir

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

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

fts.indexingHosts

Список (разделенный символом "|") серверов, которые должны поддерживать поисковый индекс в кластере. Каждый сервер представляется своим Server ID.

Например: cuba.fts.indexingHosts = host1:8080/app-core|host2:8080/app-core

Значение по умолчанию: не установлено, что означает, что постановка в очередь и индексирование производятся единственным текущим сервером.

fts.indexingBatchSize

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

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

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

fts.reindexBatchSize

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

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

fts.maxNumberOfSearchTermsInHitInfo

Максимальное количество вхождений искомого слова, которое будет добавлено в hit info для каждого из индексируемых полей сущности. Например, в сущности индексируется поле с типом FileDescriptor. Если значение свойства fts.maxNumberOfSearchTermsInHitInfo равно 2, то это значит, что в hit info будет добавлено только 2 первых вхождения искомого слова в файл. То же самое будет со всеми остальными индексируемыми полями сущности.

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

fts.maxSearchResults

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

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

. . .