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

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

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

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

Создание соединителя содержимого с помощью SDK соединителя содержимого

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

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

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

Мавен

<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 может оказаться сложной задачей. Для получения дополнительной информации о списках управления доступом Google Cloud Search см. ACL Google Cloud Search .

Примечание. API индексирования Cloud Search поддерживает списки управления доступом для одного домена. Он не поддерживает междоменные ACL. Используйте класс 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 требуется дополнительная информация, включенная в ACL Google Cloud Search.

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

Метаданные хранятся в объекте Item . Чтобы создать Item , вам потребуется как минимум уникальный строковый идентификатор, тип элемента, ACL, 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.

Push-идентификаторы элементов и хеш-значения

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

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

  • Получите идентификатор каждого элемента и связанное с ним хеш-значение из репозитория.
  • Упакуйте каждую пару ID и хеш-значения в 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 для упаковки идентификаторов и хэш-значений в одну операцию 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 может оказаться сложной задачей. Для получения дополнительной информации о списках управления доступом Google Cloud Search см. ACL Google Cloud Search .

Примечание. API индексирования Cloud Search поддерживает списки управления доступом для одного домена. Он не поддерживает междоменные ACL. Используйте класс 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 требуется дополнительная информация, включенная в ACL Google Cloud Search.

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

Метаданные хранятся в объекте Item . Чтобы создать Item , вам потребуется как минимум уникальный строковый идентификатор, тип элемента, ACL, 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 поддерживает очереди и сравнивает содержимое очередей, чтобы определить статус элемента, например, был ли элемент удален из репозитория. Для получения дополнительной информации об очереди на индексирование в облачном поиске см. Очередь на индексирование в облачном поиске Google .

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

Этот раздел документации относится к фрагментам кода из примера 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.

Push-идентификаторы элементов и хеш-значения

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

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

  • 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();

Next Steps

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 .