Предисловие
Данный документ является руководством по применению подсистемы полнотекстового поиска (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.
1.2. Поиск
Поиск ведется по следующим правилам:
-
если искомая строка обрамлена кавычками, то ищется соответствующая фраза - набор таких же слов в том же порядке, игнорируя знаки пунктуации;
-
если искомая строка начинается с символа "*", то производится поиск по вхождению строки в любой части слова индексированных данных;
-
в противном случае поиск производится по совпадению искомой строки с началом слов индексированных данных.
Для русского и английского языка поиск производится с учетом морфологии.
Алгоритм поиска состоит из двух этапов:
-
Cначала искомая строка ищется в поле
all
документов Lucene. Найденные сущности добавляются в список результатов. -
Если что-то найдено на первом этапе, то идентификаторы найденных сущностей ищутся в поле
links
документов Lucene. Найденные на втором этапе сущности также добавляются в список результатов.
Warning
|
Если строка поиска состоит из нескольких слов (и не обрамлена кавычками), то будет произведен поиск всех слов по отдельности по условию ИЛИ. То есть в результаты поиска попадут сущности, содержащие хотя бы одно из введенных слов. |
1.3. Пример индексирования и поиска
Рассмотрим приведенный выше простейший пример со связанными сущностями Order
и Customer
.
В данном случае, если все атрибуты объектов являются индексируемыми, при индексации двух связанных экземпляров 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.
Разобьем задачу на следующие этапы:
-
Подключим функциональность поиска к проекту, настроим вызов процесса индексирования и убедимся в его работоспособности.
-
Настроим конфигурационный файл FTS для работы с сущностями модели данных примера Библиотека.
-
Для иллюстрации возможностей поиска по загруженным файлам используем сущность
BookPublication
и функциональность загрузки файлов, описанную в разделе Хранилище файлов в Руководстве по разработке приложений.
2.1. Настройка проекта
-
Запустите CUBA Studio, перейдите в окно Open project > Samples и загрузите проект Library.
-
Откройте проект Library в Studio.
-
Откройте окно свойств проекта Project properties → Edit и в списке App components включите компонент fts, затем сохраните изменения. Studio предложит пересоздать скрипты Gradle - согласитесь.
-
Запустите Run → Deploy. На этом этапе будет произведена сборка приложения и оно будет развернуто на сервере Tomcat в подкаталоге
build/tomcat
. -
Создайте базу данных приложения: Run → Create database.
-
Запустите сервер приложения: Run → Start application server.
-
Откройте веб-интерфейс приложения по адресу
http://localhost:8080/app
. Войдите в систему с именемadmin
и паролемadmin
. -
Для того, чтобы включить функциональность полнотекстового поиска, в главном меню приложения откройте Administration → Application properties, найдите и откройте список
fts
в таблице свойств, двойным щелчком откройте атрибут fts.enabled и выберите true в поле флажок Current value.
После выполнения вышеописанных действий функциональность полнотекстового поиска подключена к приложению и готова к работе. Если выйти из системы и снова выполнить логин, в правой части верхней панели главного окна приложения появится поле поиска. Кроме того, полнотекстовый поиск может использоваться в UI-компоненте Filter.
Однако поиск не будет давать результатов, так как никакие данные еще не проиндексированы.
Для однократного запуска индексации текущего состояния базы данных (а точнее, сущностей, описанных в конфигурационном файле FTS по умолчанию), откройте в главном меню Administration → JMX Console, найдите JMX-бин app-core.fts:type=FtsManager
и вызовите последовательно сначала метод reindexAll()
, а затем processQueue()
.
После этого поиск, например, строки "adm" должен выдавать следующие результаты:
2.2. Настройка вызова процесса индексирования
Для периодического вызова процесса индексирования удобно воспользоваться механизмом назначенных заданий платформы (см. Руководство по разработке приложений → Назначенные задания CUBA).
Сначала необходимо активировать весь механизм запуска задач. Добавьте в файл app.properties
модуля core проекта приложения следующее свойство:
cuba.schedulingActive = true
Перезапустите сервер приложения, войдите в систему пользователем admin
, откройте экран JMX Console, найдите и откройте JMX-бин app-core.cuba:type=Scheduling
и убедитесь, что атрибут Active имеет значение true
.
Далее откройте экран Administration → Scheduled Tasks, нажмите Create и задайте следующие значения атрибутов новой задачи:
-
Defined by: Bean
-
Bean name: cuba_FtsManager
-
Method name: processQueue()
-
Singleton: true
-
Period, sec: 30
Сохраните задачу, выделите ее в таблице и нажмите Activate. С этого момента каждые 30 секунд будет вызываться процесс индексирования измененных сущностей.
Warning
|
Автоматическое индексирование не затрагивает сущности, созданные до его запуска. Для постановки таких сущностей в очередь на индексацию воспользуйтесь методами |
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.
Сгенерируйте новые скрипты обновления БД, выполните команду обновления базы данных и перезапустите сервер приложения. При пересоздании базы данных полнотекстовый поиск по умолчанию отключается. Снова включите флажок Value для атрибута Enable в экране JMX Console, выполните индексацию всех файлов, выйдите из системы снова выполните логин.
Так как мы добавили новый атрибут, в таблице публикаций на экране списка сущности BookPublication
теперь появился новый пустой столбец: File. Чтобы его заполнить, откройте экран редактирования строки таблицы, с помощью нового поля File загрузите текстовый файл в систему и нажмите OK. По умолчанию CUBA поддерживает следующие форматы файлов: RTF
, TXT
, DOC
, DOCX
, XLS
, XSLX
, ODT
, ODS
и PDF
.
Новые файлы теперь отображаются в таблице. Внешний вид таблицы можно отредактировать.
Чтобы переиндексировать имеющиеся в базе данных сущности и файлы в соответствии с новой конфигурацией поиска, откройте в экране JMX Console JMX-бин app-core.fts:type=FtsManager
и вызовите последовательно сначала метод reindexAll()
, а затем processQueue()
. Все вновь добавляемые и изменяемые данные будут индексироваться автоматически, с задержкой, определяемой интервалом вызова назначенного задания, т.е. не более 30 секунд.
В результате, полнотекстовый поиск будет выводить все результаты, включая вхождения в содержимом загруженных файлов.
Более подробную информацию о 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
- имя атрибута. Может быть путем (через точку) по ссылочным атрибутам. Тип не проверяется, однако если имя является путем, то возможны два случая:-
Конечный атрибут должен быть сущностью (не embeddable), а не простым типом (атрибут простого типа не имеет здесь смысла, он должен индексироваться в своей сущности).
-
Конечный атрибут должен быть простым типом встраиваемой (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