Создание соединителя содержимого

Коннектор контента — это программа, используемая для просмотра данных в корпоративном репозитории и заполнения источника данных. Google предлагает следующие варианты разработки коннекторов контента:

  • Content Connector SDK. Это хороший вариант для тех, кто программирует на Java. Content Connector SDK — это оболочка для REST API, позволяющая быстро создавать коннекторы. Чтобы создать коннектор контента с помощью этого SDK, см. статью Создание коннектора контента с помощью Content Connector SDK .

  • Низкоуровневый REST API или библиотеки API. Используйте эти варианты, если вы не программируете на Java или если ваша кодовая база лучше подходит для REST API или библиотеки. Чтобы создать коннектор контента с помощью REST API, см. статью Создание коннектора контента с помощью REST API .

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

  1. Считывает и обрабатывает параметры конфигурации.
  2. Извлекает отдельные фрагменты индексируемых данных, называемые « элементами », из стороннего репозитория контента.
  3. Объединяет списки контроля доступа, метаданные и данные контента в индексируемые элементы.
  4. Индексирует элементы в источнике данных Cloud Search.
  5. (необязательно) Принимает уведомления об изменениях из стороннего репозитория контента. Уведомления об изменениях преобразуются в запросы на индексацию для синхронизации источника данных Cloud Search со сторонним репозиторием. Коннектор выполняет эту задачу только в том случае, если репозиторий поддерживает обнаружение изменений.

Создайте коннектор контента с помощью Content Connector SDK

В следующих разделах объясняется, как создать коннектор контента с помощью Content Connector SDK.

Настройка зависимостей

Для использования SDK необходимо включить определённые зависимости в файл сборки. Нажмите на вкладку ниже, чтобы просмотреть зависимости для вашей среды сборки:

Maven

<dependency>
<groupId>com.google.enterprise.cloudsearch</groupId>
<artifactId>google-cloudsearch-indexing-connector-sdk</artifactId>
<version>v1-0.0.3</version>
</dependency>

Грейдл

compile group: 'com.google.enterprise.cloudsearch',
        name: 'google-cloudsearch-indexing-connector-sdk',
        version: 'v1-0.0.3'

Создайте конфигурацию вашего коннектора

У каждого коннектора есть файл конфигурации, содержащий используемые им параметры, такие как идентификатор вашего репозитория. Параметры определяются парами «ключ-значение» , например, api.sourceId= 1234567890abcdef .

Google Cloud Search SDK содержит несколько параметров конфигурации, предоставляемых Google, используемых всеми коннекторами. В файле конфигурации необходимо указать следующие параметры, предоставляемые Google:

  • Для коннектора контента необходимо объявить api.sourceId и api.serviceAccountPrivateKeyFile , поскольку эти параметры определяют местоположение вашего репозитория и закрытый ключ, необходимый для доступа к репозиторию.
  • Для коннектора удостоверений необходимо объявить api.identitySourceId , поскольку этот параметр определяет расположение вашего внешнего источника удостоверений. При синхронизации пользователей необходимо также объявить api.customerId как уникальный идентификатор вашей корпоративной учётной записи Google Workspace.

Если вы не хотите переопределять значения по умолчанию других параметров, предоставляемых Google, вам не нужно объявлять их в файле конфигурации. Дополнительную информацию о параметрах конфигурации, предоставляемых Google, например, о том, как генерировать определённые идентификаторы и ключи, см. в разделе «Параметры конфигурации, предоставляемые Google» .

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

Передать файл конфигурации в коннектор

Настройте config системных свойств для передачи файла конфигурации в коннектор. Вы можете задать это свойство, используя аргумент -D при запуске коннектора. Например, следующая команда запускает коннектор с файлом конфигурации MyConfig.properties :

java -classpath myconnector.jar;... -Dconfig=MyConfig.properties MyConnector

Если этот аргумент отсутствует, SDK попытается получить доступ к файлу конфигурации по умолчанию с именем connector-config.properties .

Определите свою стратегию обхода

Основная функция коннектора контента — обход репозитория и индексация его данных. Необходимо реализовать стратегию обхода, основанную на размере и структуре данных в репозитории. Вы можете разработать собственную стратегию или выбрать одну из следующих стратегий, реализованных в SDK:

Стратегия полного обхода

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

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

Стратегия обхода списка

Стратегия обхода списка сканирует весь репозиторий, включая все дочерние узлы, определяя статус каждого элемента. Затем коннектор выполняет второй проход и индексирует только новые или обновлённые с момента последнего индексирования элементы. Эта стратегия обычно используется для выполнения инкрементных обновлений существующего индекса (вместо того, чтобы выполнять полный обход при каждом обновлении индекса).

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

Обход графа

Стратегия обхода графа сканирует весь родительский узел, определяя статус каждого элемента. Затем коннектор выполняет второй проход и индексирует только те элементы в корневом узле, которые являются новыми или обновленными с момента последней индексации. Наконец, коннектор передает все дочерние идентификаторы, а затем индексирует элементы в дочерних узлах, которые являются новыми или обновленными. Коннектор продолжает рекурсивно проходить по всем дочерним узлам, пока не будут обработаны все элементы. Такой обход обычно используется для иерархических репозиториев, где перечисление всех идентификаторов нецелесообразно.

Эта стратегия подходит, если у вас есть иерархические данные, которые необходимо сканировать, например, ряд каталогов или веб-страниц.

Каждая из этих стратегий обхода реализуется классом-шаблоном коннектора в SDK. Вы можете реализовать собственную стратегию обхода, но эти шаблоны значительно ускоряют разработку коннектора. Чтобы создать коннектор с использованием шаблона, перейдите к разделу, соответствующему вашей стратегии обхода:

Создайте полный обходной соединитель, используя шаблонный класс

В этом разделе документации содержатся ссылки на фрагменты кода из примера FullTraversalSample .

Реализуйте точку входа коннектора

Точкой входа в коннектор является метод main() . Его основная задача — создать экземпляр класса Application и вызвать его метод start() для запуска коннектора.

Перед вызовом application.start() используйте класс IndexingApplication.Builder для создания экземпляра шаблона FullTraversalConnector . FullTraversalConnector принимает объект Repository , методы которого вы реализуете. Следующий фрагмент кода показывает, как реализовать метод main() :

FullTraversalSample.java
/**
 * This sample connector uses the Cloud Search SDK template class for a full
 * traversal connector.
 *
 * @param args program command line arguments
 * @throws InterruptedException thrown if an abort is issued during initialization
 */
public static void main(String[] args) throws InterruptedException {
  Repository repository = new SampleRepository();
  IndexingConnector connector = new FullTraversalConnector(repository);
  IndexingApplication application = new IndexingApplication.Builder(connector, args).build();
  application.start();
}

В фоновом режиме SDK вызывает метод initConfig() после того, как метод main() вашего коннектора вызывает Application.build . Метод initConfig() выполняет следующие задачи:

  1. Вызывает метод Configuation.isInitialized() чтобы убедиться, что Configuration не инициализирована.
  2. Инициализирует объект Configuration с помощью пар «ключ-значение», предоставленных Google. Каждая пара «ключ-значение» хранится в объекте ConfigValue внутри объекта Configuration .

Реализовать интерфейс Repository

Единственная цель объекта Repository — обход и индексация элементов репозитория. При использовании шаблона для создания коннектора контента достаточно переопределить лишь некоторые методы в интерфейсе Repository . Переопределяемые методы зависят от используемого шаблона и стратегии обхода. Для FullTraversalConnector переопределите следующие методы:

  • Метод init() . Для настройки и инициализации репозитория данных переопределите метод init() .

  • Метод getAllDocs() . Чтобы обойти и индексировать все элементы в репозитории данных, переопределите метод getAllDocs() . Этот метод вызывается один раз для каждого запланированного обхода (согласно конфигурации).

  • (необязательно) Метод getChanges() . Если ваш репозиторий поддерживает обнаружение изменений, переопределите метод getChanges() . Этот метод вызывается один раз для каждого запланированного инкрементного обхода (согласно вашей конфигурации) для извлечения изменённых элементов и их индексации.

  • (необязательно) Метод close() . Если вам нужно очистить репозиторий, переопределите метод close() . Этот метод вызывается один раз при завершении работы коннектора.

Каждый из методов объекта Repository возвращает объект ApiOperation определённого типа. Объект ApiOperation выполняет действие в виде одного или нескольких вызовов IndexingService.indexItem() для фактического индексирования вашего репозитория.

Получить пользовательские параметры конфигурации

В рамках настройки коннектора вам потребуется получить все необходимые параметры из объекта Configuration . Эта задача обычно выполняется в методе init() класса Repository .

Класс Configuration содержит несколько методов для получения различных типов данных из конфигурации. Каждый метод возвращает объект ConfigValue . Затем вы используете метод get() объекта ConfigValue для получения фактического значения. Следующий фрагмент кода из FullTraversalSample показывает, как получить одно пользовательское целочисленное значение из объекта Configuration :

FullTraversalSample.java
@Override
public void init(RepositoryContext context) {
  log.info("Initializing repository");
  numberOfDocuments = Configuration.getInteger("sample.documentCount", 10).get();
}

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

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
    "github.repos",
    Collections.emptyList(),
    Configuration.STRING_PARSER);

Выполнить полный обход

Переопределите метод getAllDocs() для полного обхода и индексации репозитория. Метод getAllDocs() принимает контрольную точку. Контрольная точка используется для возобновления индексации с определённого элемента в случае прерывания процесса. Для каждого элемента в репозитории выполните следующие действия в методе getAllDocs() :

  1. Установите разрешения.
  2. Задайте метаданные для индексируемого элемента.
  3. Объедините метаданные и элемент в один индексируемый RepositoryDoc .
  4. Упаковываем каждый индексируемый элемент в итератор, возвращаемый методом getAllDocs() . Обратите внимание, что getAllDocs() фактически возвращает CheckpointCloseableIterable , который представляет собой итерацию объектов ApiOperation , каждый из которых представляет собой API-запрос, выполненный к RepositoryDoc , например, его индексацию.

Если набор элементов слишком велик для обработки за один вызов, включите контрольную точку и установите hasMore(true) чтобы указать, что для индексации доступно больше элементов.

Установить разрешения для элемента

Ваш репозиторий использует список контроля доступа (ACL) для определения пользователей или групп, имеющих доступ к элементу. ACL — это список идентификаторов групп или пользователей, которые могут получить доступ к элементу.

Необходимо продублировать список контроля доступа (ACL), используемый вашим репозиторием, чтобы гарантировать, что только пользователи, имеющие доступ к элементу, смогут увидеть его в результатах поиска. Список контроля доступа (ACL) для элемента должен быть включен при индексировании элемента, чтобы Google Cloud Search имел необходимую информацию для предоставления корректного уровня доступа к элементу.

Content Connector SDK предоставляет богатый набор классов и методов ACL для моделирования списков ACL большинства репозиториев. Вам необходимо проанализировать ACL для каждого элемента в репозитории и создать соответствующий ACL для Google Cloud Search при индексации элемента. Если в ACL вашего репозитория используются такие концепции, как наследование ACL, моделирование такого ACL может быть сложным. Для получения дополнительной информации о списках ACL Google Cloud Search см. статью Google Cloud Search ACL .

Примечание: API индексирования Cloud Search поддерживает однодоменные списки контроля доступа (ACL). Он не поддерживает кроссдоменные списки контроля доступа (CCL). Используйте класс Acl.Builder для настройки доступа к каждому элементу с помощью ACL. Следующий фрагмент кода, взятый из полного примера обхода, позволяет всем пользователям или «принципалам» ( getCustomerPrincipal() ) быть «читателями» всех элементов ( .setReaders() ) при выполнении поиска.

FullTraversalSample.java
// Make the document publicly readable within the domain
Acl acl = new Acl.Builder()
    .setReaders(Collections.singletonList(Acl.getCustomerPrincipal()))
    .build();

Вам необходимо понимать принципы работы списков контроля доступа (ACL), чтобы правильно моделировать их для репозитория. Например, вы можете индексировать файлы в файловой системе, использующей модель наследования, при которой дочерние папки наследуют разрешения от родительских. Для моделирования наследования списков контроля доступа (ACL) требуется дополнительная информация, изложенная в статье «Списки контроля доступа (ACL) Google Cloud Search».

Установить метаданные для элемента

Метаданные хранятся в объекте Item . Для создания Item необходимы как минимум уникальный строковый идентификатор, тип элемента, список контроля доступа, URL-адрес и версия. В следующем фрагменте кода показано, как создать Item с помощью вспомогательного класса IndexingItemBuilder .

FullTraversalSample.java
// Url is required. Use google.com as a placeholder for this sample.
String viewUrl = "https://www.google.com";

// Version is required, set to current timestamp.
byte[] version = Longs.toByteArray(System.currentTimeMillis());

// Using the SDK item builder class to create the document with appropriate attributes
// (this can be expanded to include metadata fields etc.)
Item item = IndexingItemBuilder.fromConfiguration(Integer.toString(id))
    .setItemType(IndexingItemBuilder.ItemType.CONTENT_ITEM)
    .setAcl(acl)
    .setSourceRepositoryUrl(IndexingItemBuilder.FieldOrValue.withValue(viewUrl))
    .setVersion(version)
    .build();

Создать индексируемый элемент

После настройки метаданных для элемента вы можете создать индексируемый элемент, используя класс RepositoryDoc.Builder . В следующем примере показано, как создать один индексируемый элемент.

FullTraversalSample.java
// For this sample, content is just plain text
String content = String.format("Hello world from sample doc %d", id);
ByteArrayContent byteContent = ByteArrayContent.fromString("text/plain", content);

// Create the fully formed document
RepositoryDoc doc = new RepositoryDoc.Builder()
    .setItem(item)
    .setContent(byteContent, IndexingService.ContentFormat.TEXT)
    .build();

RepositoryDoc — это тип ApiOperation , который выполняет реальный запрос IndexingService.indexItem() .

Вы также можете использовать метод setRequestMode() класса RepositoryDoc.Builder для определения запроса на индексацию как ASYNCHRONOUS или SYNCHRONOUS :

ASYNCHRONOUS
Асинхронный режим приводит к увеличению задержки между индексацией и обслуживанием и обеспечивает большую квоту пропускной способности для запросов на индексацию. Асинхронный режим рекомендуется для первоначальной индексации (обратной заливки) всего репозитория.
SYNCHRONOUS
Синхронный режим обеспечивает более короткую задержку от индексации до обслуживания и позволяет использовать ограниченную квоту пропускной способности. Синхронный режим рекомендуется для индексации обновлений и изменений в репозитории. Если режим запроса не указан, по умолчанию используется SYNCHRONOUS .

Упаковать каждый индексируемый элемент в итератор

Метод getAllDocs() возвращает Iterator , а именно CheckpointCloseableIterable , объектов RepositoryDoc . Для создания и возврата итератора можно использовать класс CheckpointClosableIterableImpl.Builder . В следующем фрагменте кода показано, как создать и вернуть итератор.

FullTraversalSample.java
CheckpointCloseableIterable<ApiOperation> iterator =
  new CheckpointCloseableIterableImpl.Builder<>(allDocs).build();

SDK выполняет каждый вызов индексации, заключенный в итераторе.

Следующие шаги

Вот несколько следующих шагов, которые вы можете предпринять:

Создайте соединитель обхода списка, используя шаблонный класс

Очередь индексации Cloud Search используется для хранения идентификаторов и необязательных хэш-значений для каждого элемента в репозитории. Коннектор обхода списка отправляет идентификаторы элементов в очередь индексации Google Cloud Search и извлекает их по одному для индексации. Google Cloud Search поддерживает очереди и сравнивает их содержимое для определения статуса элемента, например, был ли элемент удалён из репозитория. Подробнее об очереди индексации Cloud Search см. в статье «Очередь индексации Cloud Search» .

В этом разделе документации содержатся ссылки на фрагменты кода из примера ListTraversalSample .

Реализуйте точку входа коннектора

Точкой входа в коннектор является метод main() . Его основная задача — создать экземпляр класса Application и вызвать его метод start() для запуска коннектора.

Перед вызовом application.start() используйте класс IndexingApplication.Builder для создания экземпляра шаблона ListingConnector . ListingConnector принимает объект Repository , методы которого вы реализуете. В следующем фрагменте кода показано, как создать экземпляр ListingConnector и связанный с ним Repository :

ListTraversalSample.java
/**
 * This sample connector uses the Cloud Search SDK template class for a
 * list traversal connector.
 *
 * @param args program command line arguments
 * @throws InterruptedException thrown if an abort is issued during initialization
 */
public static void main(String[] args) throws InterruptedException {
  Repository repository = new SampleRepository();
  IndexingConnector connector = new ListingConnector(repository);
  IndexingApplication application = new IndexingApplication.Builder(connector, args).build();
  application.start();
}

В фоновом режиме SDK вызывает метод initConfig() после того, как метод main() вашего коннектора вызывает Application.build . Метод initConfig() :

  1. Вызывает метод Configuation.isInitialized() чтобы убедиться, что Configuration не инициализирована.
  2. Инициализирует объект Configuration с помощью пар «ключ-значение», предоставленных Google. Каждая пара «ключ-значение» хранится в объекте ConfigValue внутри объекта Configuration .

Реализовать интерфейс Repository

Единственная цель объекта Repository — обход и индексация элементов репозитория. При использовании шаблона для создания коннектора контента достаточно переопределить лишь некоторые методы в интерфейсе Repository . Переопределяемые методы зависят от используемого шаблона и стратегии обхода. Для ListingConnector переопределите следующие методы:

  • Метод init() . Для настройки и инициализации репозитория данных переопределите метод init() .

  • Метод getIds() . Чтобы получить идентификаторы и хэш-значения всех записей в репозитории, переопределите метод getIds() .

  • Метод getDoc() . Чтобы добавить новые, обновить, изменить или удалить элементы индекса, переопределите метод getDoc() .

  • (необязательно) Метод getChanges() . Если ваш репозиторий поддерживает обнаружение изменений, переопределите метод getChanges() . Этот метод вызывается один раз для каждого запланированного инкрементного обхода (согласно вашей конфигурации) для извлечения изменённых элементов и их индексации.

  • (необязательно) Метод close() . Если вам нужно очистить репозиторий, переопределите метод close() . Этот метод вызывается один раз при завершении работы коннектора.

Каждый из методов объекта Repository возвращает объект ApiOperation определённого типа. Объект ApiOperation выполняет действие в виде одного или нескольких вызовов IndexingService.indexItem() для фактического индексирования вашего репозитория.

Получить пользовательские параметры конфигурации

В рамках настройки коннектора вам потребуется получить все необходимые параметры из объекта Configuration . Эта задача обычно выполняется в методе init() класса Repository .

Класс Configuration содержит несколько методов для получения различных типов данных из конфигурации. Каждый метод возвращает объект ConfigValue . Затем вы используете метод get() объекта ConfigValue для получения фактического значения. Следующий фрагмент кода из FullTraversalSample показывает, как получить одно пользовательское целочисленное значение из объекта Configuration :

FullTraversalSample.java
@Override
public void init(RepositoryContext context) {
  log.info("Initializing repository");
  numberOfDocuments = Configuration.getInteger("sample.documentCount", 10).get();
}

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

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
    "github.repos",
    Collections.emptyList(),
    Configuration.STRING_PARSER);

Выполнить обход списка

Переопределите метод getIds() для получения идентификаторов и хэш-значений всех записей в репозитории. Метод getIds() принимает контрольную точку. Контрольная точка используется для возобновления индексации с определённого элемента в случае прерывания процесса.

Затем переопределите метод getDoc() для обработки каждого элемента в очереди индексации Cloud Search.

Отправка идентификаторов элементов и хэш-значений

Переопределите getIds() для извлечения идентификаторов элементов и связанных с ними хэш-значений контента из репозитория. Пары идентификаторов и хэш-значений затем упаковываются в запрос на отправку в очередь индексации Cloud Search. Обычно сначала отправляются корневые или родительские идентификаторы, а затем дочерние, пока не будет обработана вся иерархия элементов.

Метод getIds() принимает контрольную точку, представляющую последний индексируемый элемент. Контрольную точку можно использовать для возобновления индексации с определённого элемента в случае прерывания процесса. Для каждого элемента в репозитории выполните следующие действия в методе getIds() :

  • Получите каждый идентификатор элемента и соответствующее ему хэш-значение из репозитория.
  • Упакуйте каждую пару идентификатора и хэш-значения в PushItems .
  • Объедините каждый PushItems в итератор, возвращаемый методом getIds() . Обратите внимание, что getIds() фактически возвращает CheckpointCloseableIterable , который представляет собой итерацию объектов ApiOperation , каждый из которых представляет собой запрос API, выполненный к RepositoryDoc , например, для помещения элементов в очередь.

В следующем фрагменте кода показано, как получить идентификатор каждого элемента и его хэш-значение и вставить их в PushItems . PushItems — это запрос ApiOperation на добавление элемента в очередь индексации Cloud Search.

ListTraversalSample.java
PushItems.Builder allIds = new PushItems.Builder();
for (Map.Entry<Integer, Long> entry : this.documents.entrySet()) {
  String documentId = Integer.toString(entry.getKey());
  String hash = this.calculateMetadataHash(entry.getKey());
  PushItem item = new PushItem().setMetadataHash(hash);
  log.info("Pushing " + documentId);
  allIds.addPushItem(documentId, item);
}

В следующем фрагменте кода показано, как использовать класс PushItems.Builder для упаковки идентификаторов и хэш-значений в одну ApiOperation push ApiOperation.

ListTraversalSample.java
ApiOperation pushOperation = allIds.build();
CheckpointCloseableIterable<ApiOperation> iterator =
  new CheckpointCloseableIterableImpl.Builder<>(
      Collections.singletonList(pushOperation))
  .build();
return iterator;

Элементы помещаются в очередь индексации Cloud Search для дальнейшей обработки.

Извлечь и обработать каждый предмет

Переопределите getDoc() для обработки каждого элемента в очереди индексации Cloud Search. Элемент может быть новым, изменённым, неизменённым или может отсутствовать в исходном репозитории. Извлеките и проиндексируйте каждый новый или изменённый элемент. Удалите из индекса элементы, которых больше нет в исходном репозитории.

Метод getDoc() принимает элемент из очереди индексации Google Cloud Search. Для каждого элемента в очереди выполните следующие действия в методе getDoc() :

  1. Проверьте, существует ли идентификатор элемента в очереди индексации Cloud Search в репозитории. Если нет, удалите элемент из индекса.

  2. Опросить индекс на предмет статуса элемента и, если элемент не изменился ( ACCEPTED ), ничего не делать.

  3. Изменённый индекс или новые элементы:

    1. Установите разрешения.
    2. Задайте метаданные для индексируемого элемента.
    3. Объедините метаданные и элемент в один индексируемый RepositoryDoc .
    4. Верните RepositoryDoc .

Примечание: Шаблон ListingConnector не поддерживает возврат значения null в методе getDoc() . Возврат значения null приводит к исключению NullPointerException.

Обработка удаленных элементов

В следующем фрагменте кода показано, как определить, существует ли элемент в репозитории, и, если нет, удалить его.

ListTraversalSample.java
String resourceName = item.getName();
int documentId = Integer.parseInt(resourceName);

if (!documents.containsKey(documentId)) {
  // Document no longer exists -- delete it
  log.info(() -> String.format("Deleting document %s", item.getName()));
  return ApiOperations.deleteItem(resourceName);
}

Обратите внимание, что documents — это структура данных, представляющая репозиторий. Если documentID не найден в documents , верните APIOperations.deleteItem(resourceName) , чтобы удалить элемент из индекса.

Обрабатывать неизмененные элементы

В следующем фрагменте кода показано, как опрашивать статус элемента в очереди индексации Cloud Search и обрабатывать неизмененный элемент.

ListTraversalSample.java
String currentHash = this.calculateMetadataHash(documentId);
if (this.canSkipIndexing(item, currentHash)) {
  // Document neither modified nor deleted, ack the push
  log.info(() -> String.format("Document %s not modified", item.getName()));
  PushItem pushItem = new PushItem().setType("NOT_MODIFIED");
  return new PushItems.Builder().addPushItem(resourceName, pushItem).build();
}

Чтобы определить, не был ли элемент изменён, проверьте его статус, а также другие метаданные, которые могут указывать на изменение. В этом примере хеш метаданных используется для определения того, был ли элемент изменён.

ListTraversalSample.java
/**
 * Checks to see if an item is already up to date
 *
 * @param previousItem Polled item
 * @param currentHash  Metadata hash of the current github object
 * @return PushItem operation
 */
private boolean canSkipIndexing(Item previousItem, String currentHash) {
  if (previousItem.getStatus() == null || previousItem.getMetadata() == null) {
    return false;
  }
  String status = previousItem.getStatus().getCode();
  String previousHash = previousItem.getMetadata().getHash();
  return "ACCEPTED".equals(status)
      && previousHash != null
      && previousHash.equals(currentHash);
}

Установить разрешения для элемента

Ваш репозиторий использует список контроля доступа (ACL) для определения пользователей или групп, имеющих доступ к элементу. ACL — это список идентификаторов групп или пользователей, которые могут получить доступ к элементу.

Необходимо продублировать список контроля доступа (ACL), используемый вашим репозиторием, чтобы гарантировать, что только пользователи, имеющие доступ к элементу, смогут увидеть его в результатах поиска. Список контроля доступа (ACL) для элемента должен быть включен при индексировании элемента, чтобы Google Cloud Search имел необходимую информацию для предоставления корректного уровня доступа к элементу.

Content Connector SDK предоставляет богатый набор классов и методов ACL для моделирования списков ACL большинства репозиториев. Вам необходимо проанализировать ACL для каждого элемента в репозитории и создать соответствующий ACL для Google Cloud Search при индексации элемента. Если в ACL вашего репозитория используются такие концепции, как наследование ACL, моделирование такого ACL может быть сложным. Для получения дополнительной информации о списках ACL Google Cloud Search см. статью Google Cloud Search ACL .

Примечание: API индексирования Cloud Search поддерживает однодоменные списки контроля доступа (ACL). Он не поддерживает кроссдоменные списки контроля доступа (CCL). Используйте класс Acl.Builder для настройки доступа к каждому элементу с помощью ACL. Следующий фрагмент кода, взятый из полного примера обхода, позволяет всем пользователям или «принципалам» ( getCustomerPrincipal() ) быть «читателями» всех элементов ( .setReaders() ) при выполнении поиска.

FullTraversalSample.java
// Make the document publicly readable within the domain
Acl acl = new Acl.Builder()
    .setReaders(Collections.singletonList(Acl.getCustomerPrincipal()))
    .build();

Вам необходимо понимать принципы работы списков контроля доступа (ACL), чтобы правильно моделировать их для репозитория. Например, вы можете индексировать файлы в файловой системе, использующей модель наследования, при которой дочерние папки наследуют разрешения от родительских. Для моделирования наследования списков контроля доступа (ACL) требуется дополнительная информация, изложенная в статье «Списки контроля доступа (ACL) Google Cloud Search».

Установить метаданные для элемента

Метаданные хранятся в объекте Item . Для создания Item необходимы как минимум уникальный строковый идентификатор, тип элемента, список контроля доступа, URL-адрес и версия. В следующем фрагменте кода показано, как создать Item с помощью вспомогательного класса IndexingItemBuilder .

ListTraversalSample.java
// Url is required. Use google.com as a placeholder for this sample.
String viewUrl = "https://www.google.com";

// Version is required, set to current timestamp.
byte[] version = Longs.toByteArray(System.currentTimeMillis());

// Set metadata hash so queue can detect changes
String metadataHash = this.calculateMetadataHash(documentId);

// Using the SDK item builder class to create the document with
// appropriate attributes. This can be expanded to include metadata
// fields etc.
Item item = IndexingItemBuilder.fromConfiguration(Integer.toString(documentId))
    .setItemType(IndexingItemBuilder.ItemType.CONTENT_ITEM)
    .setAcl(acl)
    .setSourceRepositoryUrl(IndexingItemBuilder.FieldOrValue.withValue(viewUrl))
    .setVersion(version)
    .setHash(metadataHash)
    .build();

Создать индексируемый элемент

После настройки метаданных для элемента вы можете создать индексируемый элемент с помощью RepositoryDoc.Builder . В следующем примере показано, как создать один индексируемый элемент.

ListTraversalSample.java
// For this sample, content is just plain text
String content = String.format("Hello world from sample doc %d", documentId);
ByteArrayContent byteContent = ByteArrayContent.fromString("text/plain", content);

// Create the fully formed document
RepositoryDoc doc = new RepositoryDoc.Builder()
    .setItem(item)
    .setContent(byteContent, IndexingService.ContentFormat.TEXT)
    .build();

RepositoryDoc — это тип ApiOperation , который выполняет реальный запрос IndexingService.indexItem() .

Вы также можете использовать метод setRequestMode() класса RepositoryDoc.Builder для определения запроса на индексацию как ASYNCHRONOUS или SYNCHRONOUS :

ASYNCHRONOUS
Асинхронный режим приводит к увеличению задержки между индексацией и обслуживанием и обеспечивает большую квоту пропускной способности для запросов на индексацию. Асинхронный режим рекомендуется для первоначальной индексации (обратной заливки) всего репозитория.
SYNCHRONOUS
Синхронный режим обеспечивает более короткую задержку от индексации до обслуживания и позволяет использовать ограниченную квоту пропускной способности. Синхронный режим рекомендуется для индексации обновлений и изменений в репозитории. Если режим запроса не указан, по умолчанию используется SYNCHRONOUS .

Следующие шаги

Вот несколько следующих шагов, которые вы можете предпринять:

Создайте соединитель обхода графа, используя шаблонный класс

Очередь индексации Cloud Search используется для хранения идентификаторов и необязательных хэш-значений для каждого элемента в репозитории. Коннектор обхода графа отправляет идентификаторы элементов в очередь индексации Google Cloud Search и извлекает их по одному для индексации. Google Cloud Search поддерживает очереди и сравнивает их содержимое для определения статуса элемента, например, был ли элемент удалён из репозитория. Дополнительную информацию об очереди индексации Cloud Search см. в статье «Очередь индексации Google Cloud Search» .

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

В этом разделе документации содержатся ссылки на фрагменты кода из примера GraphTraversalSample .

Реализуйте точку входа коннектора

Точкой входа в коннектор является метод main() . Его основная задача — создать экземпляр класса Application и вызвать его метод start() для запуска коннектора.

Перед вызовом application.start() используйте класс IndexingApplication.Builder для создания экземпляра шаблона ListingConnector . ListingConnector принимает объект Repository , методы которого вы реализуете.

В следующем фрагменте показано, как создать экземпляр ListingConnector и связанный с ним Repository :

GraphTraversalSample.java
/**
 * This sample connector uses the Cloud Search SDK template class for a graph
 * traversal connector.
 *
 * @param args program command line arguments
 * @throws InterruptedException thrown if an abort is issued during initialization
 */
public static void main(String[] args) throws InterruptedException {
  Repository repository = new SampleRepository();
  IndexingConnector connector = new ListingConnector(repository);
  IndexingApplication application = new IndexingApplication.Builder(connector, args).build();
  application.start();
}

В фоновом режиме SDK вызывает метод initConfig() после того, как метод main() вашего коннектора вызывает Application.build . Метод initConfig() :

  1. Вызывает метод Configuation.isInitialized() чтобы убедиться, что Configuration не инициализирована.
  2. Инициализирует объект Configuration с помощью пар «ключ-значение», предоставленных Google. Каждая пара «ключ-значение» хранится в объекте ConfigValue внутри объекта Configuration .

Реализовать интерфейс Repository

Единственная цель объекта Repository — обход и индексация элементов репозитория. При использовании шаблона для создания коннектора контента достаточно переопределить лишь некоторые методы в интерфейсе Repository . Переопределяемые методы зависят от используемого шаблона и стратегии обхода. Для ListingConnector переопределяются следующие методы:

  • Метод init() . Для настройки и инициализации репозитория данных переопределите метод init() .

  • Метод getIds() . Чтобы получить идентификаторы и хэш-значения всех записей в репозитории, переопределите метод getIds() .

  • Метод getDoc() . Чтобы добавить новые, обновить, изменить или удалить элементы индекса, переопределите метод getDoc() .

  • (необязательно) Метод getChanges() . Если ваш репозиторий поддерживает обнаружение изменений, переопределите метод getChanges() . Этот метод вызывается один раз для каждого запланированного инкрементного обхода (согласно вашей конфигурации) для извлечения изменённых элементов и их индексации.

  • (необязательно) Метод close() . Если вам нужно очистить репозиторий, переопределите метод close() . Этот метод вызывается один раз при завершении работы коннектора.

Каждый из методов объекта Repository возвращает объект ApiOperation определённого типа. Объект ApiOperation выполняет действие в виде одного или нескольких вызовов IndexingService.indexItem() для фактического индексирования вашего репозитория.

Получить пользовательские параметры конфигурации

В рамках настройки коннектора вам потребуется получить все необходимые параметры из объекта Configuration . Эта задача обычно выполняется в методе init() класса Repository .

Класс Configuration содержит несколько методов для получения различных типов данных из конфигурации. Каждый метод возвращает объект ConfigValue . Затем вы используете метод get() объекта ConfigValue для получения фактического значения. Следующий фрагмент кода из FullTraversalSample показывает, как получить одно пользовательское целочисленное значение из объекта Configuration :

FullTraversalSample.java
@Override
public void init(RepositoryContext context) {
  log.info("Initializing repository");
  numberOfDocuments = Configuration.getInteger("sample.documentCount", 10).get();
}

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

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
    "github.repos",
    Collections.emptyList(),
    Configuration.STRING_PARSER);

Выполнить обход графа

Переопределите метод getIds() для получения идентификаторов и хэш-значений всех записей в репозитории. Метод getIds() принимает контрольную точку. Контрольная точка используется для возобновления индексации с определённого элемента в случае прерывания процесса.

Затем переопределите метод getDoc() для обработки каждого элемента в очереди индексации Cloud Search.

Отправка идентификаторов элементов и хэш-значений

Переопределите getIds() для извлечения идентификаторов элементов и связанных с ними хэш-значений контента из репозитория. Пары идентификаторов и хэш-значений затем упаковываются в запрос на отправку в очередь индексации Cloud Search. Обычно сначала отправляются корневые или родительские идентификаторы, а затем дочерние, пока не будет обработана вся иерархия элементов.

The getIds() method accepts a checkpoint representing the last item to be indexed. The checkpoint can be used to resume indexing at a specific item should the process be interrupted. For each item in your repository, perform these steps in the getIds() method:

  • Get each item ID and associated hash value from the repository.
  • Package each ID and hash value pair into a PushItems .
  • Combine each PushItems into an iterator returned by the getIds() method. Note that getIds() actually returns a CheckpointCloseableIterable which is an iteration of ApiOperation objects, each object representing an API request performed on a RepositoryDoc , such as push the items to the queue.

The following code snippet shows how to get each item ID and hash value and insert them into a PushItems . A PushItems is an ApiOperation request to push an item to the Cloud Search Indexing Queue.

GraphTraversalSample.java
PushItems.Builder allIds = new PushItems.Builder();
PushItem item = new PushItem();
allIds.addPushItem("root", item);

The following code snippet shows how to use the PushItems.Builder class to package the IDs and hash values into a single push ApiOperation .

GraphTraversalSample.java
ApiOperation pushOperation = allIds.build();
CheckpointCloseableIterable<ApiOperation> iterator =
  new CheckpointCloseableIterableImpl.Builder<>(
      Collections.singletonList(pushOperation))
  .build();

Items are pushed to the Cloud Search Indexing Queue for further processing.

Retrieve and handle each item

Override getDoc() to handle each item in the Cloud Search Indexing Queue. An item can be new, modified, unchanged, or can no longer exist in the source repository. Retrieve and index each item that is new or modified. Remove items from the index that no longer exist in the source repository.

The getDoc() method accepts an Item from the Cloud Search Indexing Queue. For each item in the queue, perform these steps in the getDoc() method:

  1. Check if the item's ID, within the Cloud Search Indexing Queue, exists in the repository. If not, delete the item from the index. If the item does exist, continue with the next step.

  2. Index changed or new items:

    1. Set the permissions.
    2. Set the metadata for the item that you are indexing.
    3. Combine the metadata and item into one indexable RepositoryDoc .
    4. Place the child IDs in the Cloud Search Indexing Queue for further processing.
    5. Return the RepositoryDoc .

Handle deleted items

The following code snippet shows how to determine if an item exists in the index and, it not, delete it.

GraphTraversalSample.java
String resourceName = item.getName();
if (documentExists(resourceName)) {
  return buildDocumentAndChildren(resourceName);
}
// Document doesn't exist, delete it
log.info(() -> String.format("Deleting document %s", resourceName));
return ApiOperations.deleteItem(resourceName);

Set the permissions for an item

Your repository uses an Access Control List (ACL) to identify the users or groups that have access to an item. An ACL is a list of IDs for groups or users who can access the item.

You must duplicate the ACL used by your repository to ensure only those users with access to an item can see that item within a search result. The ACL for an item must be included when indexing an item so that Google Cloud Search has the information it needs to provide the correct level of access to the item.

The Content Connector SDK provides a rich set of ACL classes and methods to model the ACLs of most repositories. You must analyze the ACL for each item in your repository and create a corresponding ACL for Google Cloud Search when you index an item. If your repository's ACL employs concepts such as ACL inheritance, modeling that ACL can be tricky. For further information on Google Cloud Search ACLs, refer to Google Cloud Search ACLs .

Note: The Cloud Search Indexing API supports single-domain ACLs. It does not support cross-domain ACLs. Use the Acl.Builder class to set access to each item using an ACL. The following code snippet, taken from the full traversal sample, allows all users or “principals” ( getCustomerPrincipal() ) to be “readers” of all items ( .setReaders() ) when performing a search.

FullTraversalSample.java
// Make the document publicly readable within the domain
Acl acl = new Acl.Builder()
    .setReaders(Collections.singletonList(Acl.getCustomerPrincipal()))
    .build();

You need to understand ACLs to properly model ACLs for the repository. For example, you might be indexing files within a file system that uses some sort of inheritance model whereby child folders inherit permissions from parent folders. Modeling ACL inheritance requires additional information covered in Google Cloud Search ACLs

Set the metadata for an item

Metadata is stored in an Item object. To create an Item , you need a minimum of a unique string ID, item type, ACL, URL, and version for the item. The following code snippet shows how to build an Item using the IndexingItemBuilder helper class.

GraphTraversalSample.java
// Url is required. Use google.com as a placeholder for this sample.
String viewUrl = "https://www.google.com";

// Version is required, set to current timestamp.
byte[] version = Longs.toByteArray(System.currentTimeMillis());

// Using the SDK item builder class to create the document with
// appropriate attributes. This can be expanded to include metadata
// fields etc.
Item item = IndexingItemBuilder.fromConfiguration(documentId)
    .setItemType(IndexingItemBuilder.ItemType.CONTENT_ITEM)
    .setAcl(acl)
    .setSourceRepositoryUrl(IndexingItemBuilder.FieldOrValue.withValue(viewUrl))
    .setVersion(version)
    .build();

Create the indexable item

Once you have set the metadata for the item, you can create the actual indexable item using the RepositoryDoc.Builder . The following example shows how to create a single indexable item.

GraphTraversalSample.java
// For this sample, content is just plain text
String content = String.format("Hello world from sample doc %s", documentId);
ByteArrayContent byteContent = ByteArrayContent.fromString("text/plain", content);

RepositoryDoc.Builder docBuilder = new RepositoryDoc.Builder()
    .setItem(item)
    .setContent(byteContent, IndexingService.ContentFormat.TEXT);

A RepositoryDoc is a type of ApiOperation that performs the actual IndexingService.indexItem() request.

You can also use the setRequestMode() method of the RepositoryDoc.Builder class to identify the indexing request as ASYNCHRONOUS or SYNCHRONOUS :

ASYNCHRONOUS
Asynchronous mode results in longer indexing-to-serving latency and accommodates large throughput quota for indexing requests. Asynchronous mode is recommended for initial indexing (backfill) of the entire repository.
SYNCHRONOUS
Synchronous mode results in shorter indexing-to-serving latency and accommodates limited throughput quota. Synchronous mode is recommended for indexing of updates and changes to the repository. If unspecified, the request mode defaults to SYNCHRONOUS .

Place the child IDs in the Cloud Search Indexing Queue

The following code snippet shows how to include the child IDs, for the currently processing parent item, into the queue for processing. These IDs are processed after the parent item is indexed.

GraphTraversalSample.java
// Queue the child nodes to visit after indexing this document
Set<String> childIds = getChildItemNames(documentId);
for (String id : childIds) {
  log.info(() -> String.format("Pushing child node %s", id));
  PushItem pushItem = new PushItem();
  docBuilder.addChildId(id, pushItem);
}

RepositoryDoc doc = docBuilder.build();

Следующие шаги

Here are a few next steps you might take:

Create a content connector using the REST API

The following sections explain how to create a content connector using the REST API.

Determine your traversal strategy

The primary function of a content connector is to traverse a repository and index its data. You must implement a traversal strategy based on the size and layout of data in your repository. Following are three common traversal strategies:

Full traversal strategy

A full traversal strategy scans the entire repository and blindly indexes every item. This strategy is commonly used when you have a small repository and can afford the overhead of doing a full traversal every time you index.

This traversal strategy is suitable for small repositories with mostly static, non-hierarchical, data. You might also use this traversal strategy when change detection is difficult or not supported by the repository.

List traversal strategy

A list traversal strategy scans the entire repository, including all child nodes, determining the status of each item. Then, the connector takes a second pass and only indexes items that are new or have been updated since the last indexing. This strategy is commonly used to perform incremental updates to an existing index (instead of having to do a full traversal every time you update the index).

This traversal strategy is suitable when change detection is difficult or not supported by the repository, you have non-hierarchical data, and you are working with very large data sets.

Graph traversal

A graph traversal strategy scans the entire parent node determining the status of each item. Then, the connector takes a second pass and only indexes items in the root node are new or have been updated since the last indexing. Finally, the connector passes any child IDs then indexes items in the child nodes that are new or have been updated. The connector continues recursively through all child nodes until all items have been addressed. Such traversal is typically used for hierarchical repositories where listing of all IDs isn't practical.

This strategy is suitable if you have hierarchical data that needs to be crawled, such as a series directories or web pages.

Implement your traversal strategy and index items

Every indexable element for Cloud Search is referred to as an item in the Cloud Search API. An item might be a file, folder, a line in a CSV file, or a database record.

Once your schema is registered, you can populate the index by:

  1. (optional) Using items.upload to upload files larger than 100KiB for indexing. For smaller files, embed the content as inlineContent using items.index .

  2. (optional) Using media.upload to upload media files for indexing.

  3. Using items.index to index the item. For example, if your schema uses the object definition in the movie schema , an indexing request for a single item would look like this:

    {
      "name": "datasource/<data_source_id>/items/titanic",
      "acl": {
        "readers": [
          {
            "gsuitePrincipal": {
              "gsuiteDomain": true
            }
          }
        ]
      },
      "metadata": {
        "title": "Titanic",
        "viewUrl": "http://www.imdb.com/title/tt2234155/?ref_=nv_sr_1",
        "objectType": "movie"
      },
      "structuredData": {
        "object": {
          "properties": [
            {
              "name": "movieTitle",
              "textValues": {
                "values": [
                  "Titanic"
                ]
              }
            },
            {
              "name": "releaseDate",
              "dateValues": {
                "values": [
                  {
                    "year": 1997,
                    "month": 12,
                    "day": 19
                  }
                ]
              }
            },
            {
              "name": "actorName",
              "textValues": {
                "values": [
                  "Leonardo DiCaprio",
                  "Kate Winslet",
                  "Billy Zane"
                ]
              }
            },
            {
              "name": "genre",
              "enumValues": {
                "values": [
                  "Drama",
                  "Action"
                ]
              }
            },
            {
              "name": "userRating",
              "integerValues": {
                "values": [
                  8
                ]
              }
            },
            {
              "name": "mpaaRating",
              "textValues": {
                "values": [
                  "PG-13"
                ]
              }
            },
            {
              "name": "duration",
              "textValues": {
                "values": [
                  "3 h 14 min"
                ]
              }
            }
          ]
        }
      },
      "content": {
        "inlineContent": "A seventeen-year-old aristocrat falls in love with a kind but poor artist aboard the luxurious, ill-fated R.M.S. Titanic.",
        "contentFormat": "TEXT"
      },
      "version": "01",
      "itemType": "CONTENT_ITEM"
    }
    
  4. (Optional) Using items.get calls to verify an item has been indexed.

To perform a full traversal, you would periodically reindex the entire repository. To perform a list or graph traversal, you need to implement code to handle repository changes .

Handle repository changes

You can periodically gather and index each item from a repository to perform a full indexing. While effective at ensuring your index is up-to-date, a full indexing can be costly when dealing with larger or hierarchical repositories.

Instead of using index calls to index an entire repository every so often, you can also use the Google Cloud Indexing Queue as a mechanism for tracking changes and only indexing those items that have changed. You can use the items.push requests to push items into the queue for later polling and updating. For more information on the Google Cloud Indexing Queue, refer to Google Cloud Indexing Queue .

For further information on the Google Cloud Search API, refer to Cloud Search API .