Criar um conector de conteúdo

Um conector de conteúdo é um programa de software usado para transferir dados no repositório da empresa e preencher uma fonte de dados. O Google oferece as seguintes opções para desenvolvimento de conectores de conteúdo:

  • SDK do Content Connector. Essa é uma boa opção se você estiver programando em Java. O SDK do Content Connector é um wrapper em torno da API REST que permite criar conectores rapidamente. Para criar um conector de conteúdo usando o SDK, consulte Criar um conector de conteúdo usando o SDK do Content Connector.

  • Uma API REST de baixo nível ou bibliotecas de API: use essas opções se você não estiver programando em Java ou se sua codebase funciona melhor com uma API REST ou biblioteca. Para criar um conector de conteúdo usando a API REST, consulte Criar um conector de conteúdo usando a API REST.

Um conector de conteúdo típico desempenha as seguintes tarefas:

  1. Leitura e processamento de parâmetros de configuração.
  2. Extração de blocos distintos de dados indexáveis, chamados de “itens”, do repositório de conteúdo de terceiros.
  3. Combinação de Access Control Lists (ACLs), metadados e dados de conteúdo em itens indexáveis.
  4. Indexação de itens com a fonte de dados do Cloud Search.
  5. (Opcional) Escuta de notificações sobre alterações do repositório de conteúdo de terceiros. As notificações sobre alterações são convertidas em solicitações de indexação para manter a fonte de dados do Cloud Search em sincronia com o repositório de terceiros. O conector desempenhará essa tarefa apenas se o repositório for compatível com a detecção de alterações.

Criar um conector de conteúdo usando o SDK do Content Connector

Nas seções a seguir, você verá explicações sobre como criar um conector de conteúdo usando o SDK do Content Connector.

Configurar dependências

Você precisa incluir determinadas dependências em seu arquivo de compilação para usar o SDK. Clique em uma guia abaixo para ver as dependências do seu ambiente de build:

Maven

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

Gradle

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

Criar a configuração do conector

Cada conector tem um arquivo de configuração que contém os parâmetros usados pelo conector, como o código do repositório. Os parâmetros são definidos como pares de chave-valor, como api.sourceId=1234567890abcdef.

O SDK do Google Cloud Search contém vários parâmetros de configuração fornecidos pelo Google que são usados por todos os conectores. É necessário declarar os parâmetros fornecidos pelo Google a seguir no arquivo de configuração:

  • Para um conector de conteúdo, declare api.sourceId e api.serviceAccountPrivateKeyFile, porque esses parâmetros identificam o local do repositório e a chave privada necessária para acessá-lo.
  • No caso dos conectores de identidade, declare api.identitySourceId, porque esse parâmetro identifica o local da origem de identidade externa. Se você estiver sincronizando usuários, também vai precisar declarar api.customerId como o ID exclusivo da conta do Google Workspace da empresa.

A menos que você queira modificar os valores padrão dos outros parâmetros fornecidos pelo Google, não é necessário declará-los no arquivo de configuração. Para mais informações sobre os parâmetros de configuração fornecidos pelo Google, por exemplo, como gerar determinados IDs e chaves, consulte Parâmetros de configuração fornecidos pelo Google.

Também é possível definir parâmetros específicos do repositório para usá-los no seu arquivo de configuração.

Transmitir o arquivo de configuração para o conector

Defina a propriedade do sistema config para transmitir o arquivo de configuração ao seu conector. É possível definir a propriedade usando o argumento -D ao iniciar o conector. Por exemplo, o comando a seguir inicia o conector com o arquivo de configuração MyConfig.properties:

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

Se esse argumento estiver ausente, o SDK vai tentar acessar um arquivo de configuração padrão chamado connector-config.properties.

Determinar a estratégia de travessia

A principal função do conector de conteúdo é percorrer um repositório e indexar os dados nele. Implemente uma estratégia de travessia com base no tamanho e no layout do volume de dados no seu repositório. Crie sua própria estratégia ou escolha uma das seguintes estratégias implementadas no SDK:

Estratégia de travessia completa

A estratégia de travessia completa verifica o repositório inteiro e indexa cada item às cegas. Essa estratégia é comumente usada quando se tem um repositório pequeno e a sobrecarga de fazer uma travessia completa toda vez que indexar não causa prejuízos.

Essa estratégia de travessia é adequada para repositórios pequenos com dados estáticos e não hierárquicos em sua maioria. Também é possível usar essa estratégia de travessia quando a detecção de alterações é difícil ou está indisponível no repositório.

Estratégia de travessia de listas

A estratégia de travessia de listas verifica o repositório inteiro, incluindo todos os nós filhos, e determina o status de cada item. Depois, o conector realiza uma segunda verificação e indexa apenas os itens novos ou que foram atualizados desde a última indexação. Essa estratégia é comumente usada para realizar atualizações incrementais em um índice atual, em vez de fazer uma travessia completa toda vez o índice é atualizado.

Essa estratégia de travessia é adequada para os casos em que a detecção de alterações é difícil ou está indisponível no repositório, os dados são não hierárquicos ou os conjuntos de dados são muito grandes.

Travessia de gráficos

A estratégia de travessia de grafos verifica o nó pai inteiro e determina o status de cada item. Depois, o conector realiza uma segunda verificação e indexa apenas os itens no nó raiz que são novos ou foram atualizados desde a última indexação. Por fim, o conector transmite todos os códigos filhos e indexa os itens nos nós filhos que são novos ou foram atualizados. O conector continua a percorrer de maneira recorrente todos os nós filhos até que todos os itens tenham sido processados. Normalmente, esse tipo de estratégia de travessia é usada em repositórios hierárquicos, em que não é prático fazer a listagem de todos os códigos.

Essa estratégia é adequada quando se tem dados hierárquicos que precisam ser rastreados, como uma série de diretórios ou páginas da Web.

Cada uma dessas estratégias de travessia é implementada por uma classe de conector de modelo no SDK. Embora seja possível implementar sua própria estratégia de travessia, esses modelos aceleram bastante o desenvolvimento do conector. Para criar um conector usando um modelo, siga para a seção correspondente à sua estratégia de travessia:

Criar um conector de travessia completa usando uma classe de modelo

Nesta seção da documentação, são usados snippets de código do exemplo FullTraversalSample.

Implementar o ponto de entrada do conector

O ponto de entrada de um conector é o método main(). A tarefa principal desse método é criar uma instância da classe Application e invocar o método start() para executar o conector.

Antes de chamar application.start(), use a classe IndexingApplication.Builder para instanciar o modelo FullTraversalConnector. O FullTraversalConnector aceita um objeto Repository com métodos que você implementa. O snippet de código abaixo mostra como implementar o método 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();
}

Em segundo plano, o SDK chama o método initConfig() depois que o método main() do conector chama Application.build. O método initConfig() executa as seguintes tarefas:

  1. Chama o método Configuation.isInitialized() para garantir que o Configuration não tenha sido inicializado.
  2. Inicializa um objeto Configuration com os pares de chave-valor fornecidos pelo Google. Cada par de chave-valor é armazenado em um objeto ConfigValue no objeto Configuration.

Implementar a interface Repository

O único objetivo do objeto Repository é realizar a travessia e a indexação de itens do repositório. Ao usar um modelo, é necessário modificar apenas determinados métodos na interface Repository para criar um conector de conteúdo. A escolha dos métodos que você modificará dependerá do modelo e da estratégia de travessia usados. No caso de FullTraversalConnector, substitua os métodos abaixo:

  • O método init(). Para configurar e inicializar o repositório de dados, modifique o método init().

  • O método getAllDocs(). Para percorrer e indexar todos os itens no repositório de dados, modifique o método getAllDocs(). Esse método é chamado uma vez em cada travessia programada, conforme definido por sua configuração.

  • (opcional) O método getChanges(). Se o repositório for compatível com a detecção de alterações, modifique o método getChanges(). Esse método é chamado uma vez em cada travessia incremental programada, conforme definido por sua configuração, para recuperar os itens modificados e indexá-los.

  • (opcional) O método close(). Se você precisar realizar a limpeza do repositório, modifique o método close(). Esse método é chamado uma vez durante o encerramento do conector.

Cada um dos métodos do objeto Repository retorna algum tipo de objeto ApiOperation. Um objeto ApiOperation realiza uma ação na forma de uma ou talvez várias chamadas IndexingService.indexItem() para realizar a indexação real do repositório.

Receber parâmetros de configuração personalizados

Como parte do processamento da configuração do conector, é necessário receber todos os parâmetros personalizados do objeto Configuration. Essa tarefa geralmente é realizada em um método init() da classe Repository.

A classe Configuration tem vários métodos para receber diferentes tipos de dados de uma configuração. Cada método retorna um objeto ConfigValue. Em seguida, você vai usar o método get() do objeto ConfigValue para recuperar o valor real. O snippet abaixo, de FullTraversalSample, mostra como extrair um único valor inteiro personalizado de um objeto Configuration:

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

Para receber e analisar um parâmetro que contém vários valores, use um dos analisadores de tipo da classe Configuration para analisar os dados em blocos distintos. O snippet a seguir, do conector do tutorial, usa o método getMultiValue para receber uma lista de nomes de repositório do GitHub:

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

Realizar uma travessia completa

Modifique getAllDocs() para realizar uma travessia completa e indexar seu repositório. O método getAllDocs() aceita um checkpoint. O checkpoint é usado para retomar a indexação em um item específico, caso o processo seja interrompido. Para cada item no repositório, realize estas etapas no método getAllDocs():

  1. Defina as permissões.
  2. Defina os metadados para o item que você está indexando.
  3. Combine os metadados e o item em um RepositoryDoc indexável.
  4. Empacote cada item indexável em um iterador retornado pelo método getAllDocs(). Observe que getAllDocs() retorna uma CheckpointCloseableIterable que é uma iteração de objetos ApiOperation. Cada objeto representa uma solicitação de API realizada em um RepositoryDoc, por exemplo, indexando-a.

Se o conjunto de itens for muito grande para ser processado em uma única chamada, inclua um checkpoint e defina hasMore(true) para indicar que mais itens estão disponíveis para indexação.

Definir as permissões para um item

Seu repositório usa uma lista de controle de acesso (ACL, na sigla em inglês) para identificar os usuários ou grupos que têm acesso a um item. Uma ACL é uma lista de códigos de grupos ou usuários que podem acessar o item.

É preciso duplicar a ACL usada pelo repositório para garantir que apenas os usuários com acesso a um item o vejam em um resultado da pesquisa. A ACL de um item precisa ser incluída ao indexar um item para que o Google Cloud Search tenha as informações necessárias para fornecer o nível correto de acesso ao item.

O SDK do Content Connector oferece um conjunto variado de classes e métodos para modelar as ACLs da maioria dos repositórios. Analise a ACL de cada item no seu repositório e crie uma ACL correspondente para o Google Cloud Search ao indexar um item. Se a ACL do seu repositório utiliza conceitos como herança de ACL, a modelagem pode ser complicada. Para mais informações sobre as ACLs do Google Cloud Search, consulte as ACLs do Google Cloud Search.

Observação: a API Cloud Search Indexing aceita ACLs de domínio único. No entanto, ela não é compatível com ACLs de domínios cruzados. Use a classe Acl.Builder para definir o acesso a cada item usando uma ACL. O snippet de código a seguir, retirado da amostra de travessia completa, permite que todos os usuários ou "principais" (getCustomerPrincipal()) sejam "leitores" de todos os itens (.setReaders()) ao realizar uma pesquisa.

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

Você precisa entender as ACLs para modelar corretamente as ACLs para o repositório. Por exemplo, se você estiver indexando arquivos em um sistema de arquivos que usa algum tipo de modelo de herança em que as pastas filho herdam as permissões das pastas pai. Modelar a herança da ACL requer informações adicionais que estão inclusas na página sobre ACLs do Google Cloud Search.

Definir os metadados de um item

Os metadados são armazenados em um objeto Item. Para criar uma Item, você precisa de, no mínimo, um ID de string exclusivo, um tipo de item, uma ACL, um URL e uma versão para o item. O snippet de código a seguir mostra como criar um Item usando a classe auxiliar 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();

Criar o item indexável

Depois de definir os metadados do item, crie o item real indexável usando a classe RepositoryDoc.Builder. O exemplo a seguir mostra como criar um único item indexável.

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

Um RepositoryDoc é um tipo de ApiOperation que executa a solicitação IndexingService.indexItem() real.

Também é possível usar o método setRequestMode() da classe RepositoryDoc.Builder para identificar a solicitação de indexação como ASYNCHRONOUS ou SYNCHRONOUS:

ASYNCHRONOUS
O modo assíncrono resulta em uma latência de indexação para exibição maior e acomoda uma grande cota de capacidade para solicitações de indexação. O modo assíncrono é recomendado para a indexação inicial (preenchimento) do repositório inteiro.
SYNCHRONOUS
O modo síncrono resulta em uma latência de indexação para exibição menor e acomoda uma cota de capacidade limitada. O modo síncrono é recomendado para a indexação de atualizações e alterações no repositório. Se não for especificado, o modo de solicitação será SYNCHRONOUS por padrão.

Empacotar cada item indexável em um iterador

O método getAllDocs() retorna um Iterator, especificamente um CheckpointCloseableIterable, de objetos RepositoryDoc. É possível usar a classe CheckpointClosableIterableImpl.Builder para criar e retornar um iterador. O snippet de código a seguir mostra como construir e retornar um iterador.

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

O SDK executa cada chamada de indexação incluída no iterador.

Próximas etapas

Veja a seguir algumas das próximas etapas:

Criar um conector de travessia de listas usando uma classe de modelo

O Cloud Search Indexing Queue é usado para armazenar códigos e, opcionalmente, valores de hash de cada item no repositório. Um conector de travessia de listas envia códigos de itens ao Google Cloud Search Indexing Queue e os recupera, um de cada vez, para realizar a indexação. O Google Cloud Search mantém filas e compara o conteúdo delas para determinar o status dos itens, como por exemplo, se um item foi excluído do repositório. Para mais informações sobre esse recurso, consulte Filas de indexação do Cloud Search.

Nesta seção da documentação, são usados snippets de código do exemplo ListTraversalSample.

Implementar o ponto de entrada do conector

O ponto de entrada de um conector é o método main(). A tarefa principal desse método é criar uma instância da classe Application e invocar o método start() para executar o conector.

Antes de chamar application.start(), use a classe IndexingApplication.Builder para instanciar o modelo ListingConnector. O ListingConnector aceita um objeto Repository que tem os métodos que você implementa. O snippet a seguir mostra como instanciar o ListingConnector e o Repository associado:

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

Em segundo plano, o SDK chama o método initConfig() depois que o método main() do conector chama Application.build. O método initConfig():

  1. Chama o método Configuation.isInitialized() para garantir que o Configuration não tenha sido inicializado.
  2. Inicializa um objeto Configuration com os pares de chave-valor fornecidos pelo Google. Cada par de chave-valor é armazenado em um objeto ConfigValue no objeto Configuration.

Implementar a interface Repository

O único objetivo do objeto Repository é realizar a travessia e a indexação de itens do repositório. Ao usar um modelo, é necessário modificar apenas determinados métodos na interface Repository para criar um conector de conteúdo. A escolha dos métodos que você modificará dependerá do modelo e da estratégia de travessia usados. Para ListingConnector, substitua os métodos a seguir:

  • O método init(). Para configurar e inicializar o repositório de dados, modifique o método init().

  • O método getIds(). Para recuperar IDs e valores de hash de todos os registros no repositório, modifique o método getIds().

  • O método getDoc(). Para adicionar, atualizar, modificar ou excluir itens do índice, modifique o método getDoc().

  • (opcional) O método getChanges(). Se o repositório for compatível com a detecção de alterações, modifique o método getChanges(). Esse método é chamado uma vez em cada travessia incremental programada, conforme definido por sua configuração, para recuperar os itens modificados e indexá-los.

  • (opcional) O método close(). Se você precisar realizar a limpeza do repositório, modifique o método close(). Esse método é chamado uma vez durante o encerramento do conector.

Cada um dos métodos do objeto Repository retorna algum tipo de objeto ApiOperation. Um objeto ApiOperation realiza uma ação na forma de uma ou talvez várias chamadas IndexingService.indexItem() para realizar a indexação real do repositório.

Receber parâmetros de configuração personalizados

Como parte do processamento da configuração do conector, é necessário receber todos os parâmetros personalizados do objeto Configuration. Essa tarefa geralmente é realizada em um método init() da classe Repository.

A classe Configuration tem vários métodos para receber diferentes tipos de dados de uma configuração. Cada método retorna um objeto ConfigValue. Em seguida, você vai usar o método get() do objeto ConfigValue para recuperar o valor real. O snippet abaixo, de FullTraversalSample, mostra como extrair um único valor inteiro personalizado de um objeto Configuration:

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

Para receber e analisar um parâmetro que contém vários valores, use um dos analisadores de tipo da classe Configuration para analisar os dados em blocos distintos. O snippet a seguir, do conector do tutorial, usa o método getMultiValue para receber uma lista de nomes de repositório do GitHub:

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

Realizar a travessia de listas

Modifique o método getIds() para recuperar IDs e valores de hash de todos os registros no repositório. O método getIds() aceita um checkpoint. O checkpoint é usado para retomar a indexação em um item específico, caso o processo seja interrompido.

Em seguida, modifique o método getDoc() para processar cada item no Cloud Search Indexing Queue.

Enviar IDs de itens e valores de hash

Modifique getIds() para buscar no repositório os IDs dos itens e os valores de hash de conteúdo associados a eles. Os pares de código e valor de hash são empacotados em uma solicitação de operação de push para o Cloud Search Indexing Queue. Geralmente, os códigos raiz ou pai são enviados primeiro, seguidos pelos códigos filhos, até que toda a hierarquia de itens seja processada.

O método getIds() aceita um checkpoint que representa o último item a ser indexado. O checkpoint pode ser usado para retomar a indexação em um item específico, caso o processo seja interrompido. Para cada item no repositório, siga estas etapas no método getIds():

  • Busque no repositório o ID de cada item e o valor de hash associado.
  • Empacote cada par de ID e valor de hash em um PushItems.
  • Combine cada PushItems em um iterador retornado pelo método getIds(). Observe que getIds() retorna uma CheckpointCloseableIterable, que é uma iteração de objetos ApiOperation. Cada objeto representa uma solicitação de API realizada em uma RepositoryDoc, como enviar os itens para a fila.

O snippet de código a seguir mostra como extrair cada ID do item e valor de hash e os inserir em um PushItems. Um PushItems é uma solicitação ApiOperation para enviar um item ao fila de indexação do 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);
}

O snippet de código a seguir mostra como usar a classe PushItems.Builder para empacotar os IDs e os valores de hash em um único push ApiOperation.

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

Os itens são enviados para a fila de indexação do Cloud Search para processamento adicional.

Recuperar e processar os itens

Modifique getDoc() para processar cada item na fila de indexação do Cloud Search. Um item pode ser novo, modificado, inalterado ou não existir mais no repositório de origem. Recupere e indexe cada item novo ou modificado. Remova os itens do índice que não existam mais no repositório de origem.

O método getDoc() aceita um item da fila de indexação do Google Cloud Search. Para cada item na fila, execute estas etapas no método getDoc():

  1. Verifique se o código do item no Cloud Search Indexing Queue existe no repositório. Se não existir, exclua o item do índice.

  2. Pesquise o índice pelo status do item e, se um item estiver inalterado (ACCEPTED), não faça nada.

  3. No caso de índice alterado ou itens novos, siga estas etapas:

    1. Defina as permissões.
    2. Defina os metadados para o item que você está indexando.
    3. Combine os metadados e o item em um RepositoryDoc indexável.
    4. Retorne a RepositoryDoc.

Observação:o modelo ListingConnector não aceita o retorno de null no método getDoc(). Retornar null resulta em uma NullPointerException.

Processar itens excluídos

O snippet de código a seguir mostra como determinar se um item existe no repositório e, se não existir, como excluí-lo.

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);
}

Observe que documents é uma estrutura de dados que representa o repositório. Se documentID não for encontrado em documents, retorne APIOperations.deleteItem(resourceName) para excluir o item do índice.

Processar itens inalterados

O snippet de código a seguir mostra como pesquisar o status dos itens no Cloud Search Indexing Queue e processar um item inalterado.

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

Para determinar se o item não foi modificado, verifique o status do item e outros metadados que possam indicar uma alteração. No exemplo, é usado o hash de metadados para determinar se o item foi alterado.

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);
}

Definir as permissões para um item

Seu repositório usa uma lista de controle de acesso (ACL, na sigla em inglês) para identificar os usuários ou grupos que têm acesso a um item. Uma ACL é uma lista de códigos de grupos ou usuários que podem acessar o item.

É preciso duplicar a ACL usada pelo repositório para garantir que apenas os usuários com acesso a um item o vejam em um resultado da pesquisa. A ACL de um item precisa ser incluída ao indexar um item para que o Google Cloud Search tenha as informações necessárias para fornecer o nível correto de acesso ao item.

O SDK do Content Connector oferece um conjunto variado de classes e métodos para modelar as ACLs da maioria dos repositórios. Analise a ACL de cada item no seu repositório e crie uma ACL correspondente para o Google Cloud Search ao indexar um item. Se a ACL do seu repositório utiliza conceitos como herança de ACL, a modelagem pode ser complicada. Para mais informações sobre as ACLs do Google Cloud Search, consulte as ACLs do Google Cloud Search.

Observação: a API Cloud Search Indexing aceita ACLs de domínio único. No entanto, ela não é compatível com ACLs de domínios cruzados. Use a classe Acl.Builder para definir o acesso a cada item usando uma ACL. O snippet de código a seguir, retirado da amostra de travessia completa, permite que todos os usuários ou "principais" (getCustomerPrincipal()) sejam "leitores" de todos os itens (.setReaders()) ao realizar uma pesquisa.

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

Você precisa entender as ACLs para modelar corretamente as ACLs para o repositório. Por exemplo, se você estiver indexando arquivos em um sistema de arquivos que usa algum tipo de modelo de herança em que as pastas filho herdam as permissões das pastas pai. Modelar a herança da ACL requer informações adicionais que estão inclusas na página sobre ACLs do Google Cloud Search.

Definir os metadados de um item

Os metadados são armazenados em um objeto Item. Para criar uma Item, você precisa de, no mínimo, um ID de string exclusivo, um tipo de item, uma ACL, um URL e uma versão para o item. O snippet de código a seguir mostra como criar um Item usando a classe auxiliar 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();

Criar um item indexável

Depois de definir os metadados do item, crie o item real indexável usando o RepositoryDoc.Builder. O exemplo a seguir mostra como criar um único item indexável.

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

Um RepositoryDoc é um tipo de ApiOperation que executa a solicitação IndexingService.indexItem() real.

Também é possível usar o método setRequestMode() da classe RepositoryDoc.Builder para identificar a solicitação de indexação como ASYNCHRONOUS ou SYNCHRONOUS:

ASYNCHRONOUS
O modo assíncrono resulta em uma latência de indexação para exibição maior e acomoda uma grande cota de capacidade para solicitações de indexação. O modo assíncrono é recomendado para a indexação inicial (preenchimento) do repositório inteiro.
SYNCHRONOUS
O modo síncrono resulta em uma latência de indexação para exibição menor e acomoda uma cota de capacidade limitada. O modo síncrono é recomendado para a indexação de atualizações e alterações no repositório. Se não for especificado, o modo de solicitação será SYNCHRONOUS por padrão.

Próximas etapas

Veja a seguir algumas das próximas etapas:

Criar um conector de travessia de grafos usando uma classe de modelo

O Cloud Search Indexing Queue é usado para armazenar códigos e, opcionalmente, valores de hash de cada item no repositório. Um conector de travessia de grafos envia IDs de itens à fila de indexação do Google Cloud Search e os recupera, um por vez, para indexação. O Google Cloud Search mantém filas e compara o conteúdo delas para determinar o status dos itens, como por exemplo, se um item foi excluído do repositório. Para mais informações sobre esse recurso, consulte Filas de indexação do Google Cloud Search.

Durante a indexação, o conteúdo do item é buscado no repositório de dados e todos os códigos de itens são enviados para a fila. O conector continua a processar os códigos pai e filhos de modo recorrente até que todos os itens sejam verificados.

Nesta seção da documentação, são usados snippets de código do exemplo GraphTraversalSample.

Implementar o ponto de entrada do conector

O ponto de entrada de um conector é o método main(). A tarefa principal desse método é criar uma instância da classe Application e invocar o método start() para executar o conector.

Antes de chamar application.start(), use a classe IndexingApplication.Builder para instanciar o modelo ListingConnector. O ListingConnector aceita um objeto Repository com métodos que você implementa.

O snippet a seguir mostra como instanciar o ListingConnector e o Repository associado:

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

Em segundo plano, o SDK chama o método initConfig() depois que o método main() do conector chama Application.build. O método initConfig():

  1. Chama o método Configuation.isInitialized() para garantir que o Configuration não tenha sido inicializado.
  2. Inicializa um objeto Configuration com os pares de chave-valor fornecidos pelo Google. Cada par de chave-valor é armazenado em um objeto ConfigValue no objeto Configuration.

Implementar a interface Repository

O único objetivo do objeto Repository é realizar a travessia e a indexação de itens do repositório. Ao usar um modelo, é necessário modificar apenas determinados métodos na interface Repository para criar um conector de conteúdo. A escolha dos métodos que você modificará dependerá do modelo e da estratégia de travessia usados. Para o ListingConnector, substitua os seguintes métodos:

  • O método init(). Para configurar e inicializar o repositório de dados, modifique o método init().

  • O método getIds(). Para recuperar IDs e valores de hash de todos os registros no repositório, modifique o método getIds().

  • O método getDoc(). Para adicionar, atualizar, modificar ou excluir itens do índice, modifique o método getDoc().

  • (opcional) O método getChanges(). Se o repositório for compatível com a detecção de alterações, modifique o método getChanges(). Esse método é chamado uma vez em cada travessia incremental programada, conforme definido por sua configuração, para recuperar os itens modificados e indexá-los.

  • (opcional) O método close(). Se você precisar realizar a limpeza do repositório, modifique o método close(). Esse método é chamado uma vez durante o encerramento do conector.

Cada um dos métodos do objeto Repository retorna algum tipo de objeto ApiOperation. Um objeto ApiOperation realiza uma ação na forma de uma ou talvez várias chamadas IndexingService.indexItem() para realizar a indexação real do seu repositório.

Receber parâmetros de configuração personalizados

Como parte do processamento da configuração do conector, é necessário receber todos os parâmetros personalizados do objeto Configuration. Essa tarefa geralmente é realizada em um método init() da classe Repository.

A classe Configuration tem vários métodos para receber diferentes tipos de dados de uma configuração. Cada método retorna um objeto ConfigValue. Em seguida, você vai usar o método get() do objeto ConfigValue para recuperar o valor real. O snippet abaixo, de FullTraversalSample, mostra como extrair um único valor inteiro personalizado de um objeto Configuration:

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

Para receber e analisar um parâmetro que contém vários valores, use um dos analisadores de tipo da classe Configuration para analisar os dados em blocos distintos. O snippet a seguir, do conector do tutorial, usa o método getMultiValue para receber uma lista de nomes de repositório do GitHub:

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

Realizar a travessia de gráficos

Modifique o método getIds() para recuperar IDs e valores de hash de todos os registros no repositório. O método getIds() aceita um checkpoint. O checkpoint é usado para retomar a indexação em um item específico, caso o processo seja interrompido.

Em seguida, modifique o método getDoc() para processar cada item no Cloud Search Indexing Queue.

Enviar IDs de itens e valores de hash

Modifique getIds() para buscar no repositório os IDs dos itens e os valores de hash de conteúdo associados a eles. Os pares de código e valor de hash são empacotados em uma solicitação de operação de push para o Cloud Search Indexing Queue. Geralmente, os códigos raiz ou pai são enviados primeiro, seguidos pelos códigos filhos, até que toda a hierarquia de itens seja processada.

O método getIds() aceita um checkpoint que representa o último item a ser indexado. O checkpoint pode ser usado para retomar a indexação em um item específico, caso o processo seja interrompido. Para cada item no repositório, siga estas etapas no método getIds():

  • Busque no repositório o ID de cada item e o valor de hash associado.
  • Empacote cada par de ID e valor de hash em um PushItems.
  • Combine cada PushItems em um iterador retornado pelo método getIds(). Observe que getIds() retorna uma CheckpointCloseableIterable, que é uma iteração de objetos ApiOperation. Cada objeto representa uma solicitação de API realizada em uma RepositoryDoc, como enviar os itens para a fila.

O snippet de código a seguir mostra como extrair cada ID do item e valor de hash e inserir esses valores em um PushItems. Um PushItems é uma solicitação ApiOperation para enviar um item para a fila de indexação do Cloud Search.

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

O snippet de código a seguir mostra como usar a classe PushItems.Builder para empacotar os IDs e os valores de hash em um único push ApiOperation.

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

Os itens são enviados para a fila de indexação do Cloud Search para processamento adicional.

Recuperar e processar os itens

Modifique getDoc() para processar cada item na fila de indexação do Cloud Search. Um item pode ser novo, modificado, inalterado ou não existir mais no repositório de origem. Recupere e indexe cada item novo ou modificado. Remova os itens do índice que não existam mais no repositório de origem.

O método getDoc() aceita um item da fila de indexação do Cloud Search. Para cada item na fila, execute estas etapas no método getDoc():

  1. Verifique se o código do item no Cloud Search Indexing Queue existe no repositório. Se não existir, exclua o item do índice. Se o item existir, siga para a próxima etapa.

  2. No caso de índice alterado ou itens novos:

    1. Defina as permissões.
    2. Defina os metadados para o item que você está indexando.
    3. Combine os metadados e o item em um RepositoryDoc indexável.
    4. Coloque os IDs filhos na fila de indexação do Cloud Search para processamento adicional.
    5. Retorne a RepositoryDoc.

Processar itens excluídos

O snippet de código a seguir mostra como determinar se um item existe no índice e, se não existir, como excluí-lo.

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

Definir as permissões para um item

Seu repositório usa uma lista de controle de acesso (ACL, na sigla em inglês) para identificar os usuários ou grupos que têm acesso a um item. Uma ACL é uma lista de códigos de grupos ou usuários que podem acessar o item.

É preciso duplicar a ACL usada pelo repositório para garantir que apenas os usuários com acesso a um item o vejam em um resultado da pesquisa. A ACL de um item precisa ser incluída ao indexar um item para que o Google Cloud Search tenha as informações necessárias para fornecer o nível correto de acesso ao item.

O SDK do Content Connector oferece um conjunto variado de classes e métodos para modelar as ACLs da maioria dos repositórios. Analise a ACL de cada item no seu repositório e crie uma ACL correspondente para o Google Cloud Search ao indexar um item. Se a ACL do seu repositório utiliza conceitos como herança de ACL, a modelagem pode ser complicada. Para mais informações sobre as ACLs do Google Cloud Search, consulte as ACLs do Google Cloud Search.

Observação: a API Cloud Search Indexing aceita ACLs de domínio único. No entanto, ela não é compatível com ACLs de domínios cruzados. Use a classe Acl.Builder para definir o acesso a cada item usando uma ACL. O snippet de código a seguir, retirado da amostra de travessia completa, permite que todos os usuários ou "principais" (getCustomerPrincipal()) sejam "leitores" de todos os itens (.setReaders()) ao realizar uma pesquisa.

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

Você precisa entender as ACLs para modelar corretamente as ACLs para o repositório. Por exemplo, se você estiver indexando arquivos em um sistema de arquivos que usa algum tipo de modelo de herança em que as pastas filho herdam as permissões das pastas pai. Modelar a herança da ACL requer informações adicionais que estão inclusas na página sobre ACLs do Google Cloud Search.

Definir os metadados de um item

Os metadados são armazenados em um objeto Item. Para criar uma Item, você precisa de, no mínimo, um ID de string exclusivo, um tipo de item, uma ACL, um URL e uma versão para o item. O snippet de código a seguir mostra como criar um Item usando a classe auxiliar IndexingItemBuilder.

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

Criar o item indexável

Depois de definir os metadados do item, crie o item real indexável usando o RepositoryDoc.Builder. O exemplo a seguir mostra como criar um único item indexável.

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

Um RepositoryDoc é um tipo de ApiOperation que executa a solicitação IndexingService.indexItem() real.

Também é possível usar o método setRequestMode() da classe RepositoryDoc.Builder para identificar a solicitação de indexação como ASYNCHRONOUS ou SYNCHRONOUS:

ASYNCHRONOUS
O modo assíncrono resulta em uma latência de indexação para exibição maior e acomoda uma grande cota de capacidade para solicitações de indexação. O modo assíncrono é recomendado para a indexação inicial (preenchimento) do repositório inteiro.
SYNCHRONOUS
O modo síncrono resulta em uma latência de indexação para exibição menor e acomoda uma cota de capacidade limitada. O modo síncrono é recomendado para a indexação de atualizações e alterações no repositório. Se não for especificado, o modo de solicitação será SYNCHRONOUS por padrão.

Colocar os IDs filhos na fila de indexação do Cloud Search

O snippet de código a seguir mostra como incluir na fila os códigos filhos de um item pai atualmente em processamento. Esses códigos são processados depois que o item pai é indexado.

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

Próximas etapas

Veja a seguir algumas das próximas etapas:

Criar um conector de conteúdo usando a API REST

Nas seções a seguir, você aprenderá como criar um conector de conteúdo usando a API REST.

Determinar a estratégia de travessia

A principal função do conector de conteúdo é percorrer um repositório e indexar os dados nele. Implemente uma estratégia de travessia com base no tamanho e no layout do volume de dados no seu repositório. Estas são a três estratégias de travessia mais comuns:

Estratégia de travessia completa

A estratégia de travessia completa verifica o repositório inteiro e indexa cada item às cegas. Essa estratégia é comumente usada quando se tem um repositório pequeno e a sobrecarga de fazer uma travessia completa toda vez que indexar não causa prejuízos.

Essa estratégia de travessia é adequada para repositórios pequenos com dados estáticos e não hierárquicos em sua maioria. Também é possível usar essa estratégia de travessia quando a detecção de alterações é difícil ou está indisponível no repositório.

Estratégia de travessia de listas

A estratégia de travessia de listas verifica o repositório inteiro, incluindo todos os nós filhos, e determina o status de cada item. Depois, o conector realiza uma segunda verificação e indexa apenas os itens novos ou que foram atualizados desde a última indexação. Essa estratégia é comumente usada para realizar atualizações incrementais em um índice atual, em vez de fazer uma travessia completa toda vez o índice é atualizado.

Essa estratégia de travessia é adequada para os casos em que a detecção de alterações é difícil ou está indisponível no repositório, os dados são não hierárquicos ou os conjuntos de dados são muito grandes.

Travessia de gráficos

A estratégia de travessia de grafos verifica o nó pai inteiro e determina o status de cada item. Depois, o conector realiza uma segunda verificação e indexa apenas os itens no nó raiz que são novos ou foram atualizados desde a última indexação. Por fim, o conector transmite todos os códigos filhos e indexa os itens nos nós filhos que são novos ou foram atualizados. O conector continua a percorrer de maneira recorrente todos os nós filhos até que todos os itens tenham sido processados. Normalmente, esse tipo de estratégia de travessia é usada em repositórios hierárquicos, em que não é prático fazer a listagem de todos os códigos.

Essa estratégia é adequada quando se tem dados hierárquicos que precisam ser rastreados, como uma série de diretórios ou páginas da Web.

Implementar a estratégia de travessia e indexar itens

Todos os elementos indexáveis do Cloud Search são chamados de item na API Cloud Search. Um item pode ser um arquivo, uma pasta, uma linha em um arquivo CSV ou um registro de banco de dados.

Depois de registrar um esquema, é possível preencher o índice das seguintes maneiras:

  1. (opcional) Usando items.upload para fazer upload de arquivos maiores que 100 KiB para indexação. Para arquivos menores, incorpore o conteúdo como inlineContent usando items.index.

  2. (opcional) Usando media.upload para fazer upload de arquivos de mídia para indexação.

  3. Usando items.index para indexar o item. Por exemplo, se o seu esquema usa a definição de objeto no esquema de filme, a solicitação de indexação de um único item seria assim:

    {
      "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. (Opcional) Usando chamadas items.get para verificar se um item foi indexado.

Para realizar uma travessia completa, seria necessário indexar novamente o repositório inteiro periodicamente. Para realizar uma travessia de listas ou gráficos, você precisa implementar o código para processar as mudanças no repositório.

Processar alterações no repositório

É possível coletar e indexar periodicamente cada item do repositório para realizar uma indexação completa. Isso é eficaz para garantir que o índice esteja atualizado, mas uma indexação completa pode ser dispendiosa quando é necessário trabalhar com repositórios grandes ou hierárquicos.

Em vez de usar chamadas de indexação para indexar o repositório de vez em quando, uma alternativa é usar o Google Cloud Indexing Queue como mecanismo de rastreio de alterações e indexar apenas os itens que foram alterados. É possível usar as solicitações items.push para enviar itens para a fila e, depois, pesquisá-los e atualizá-los. Para mais informações sobre esse recurso, consulte Filas de indexação do Google Cloud.

Para mais informações sobre a API Google Cloud Search, consulte API Cloud Search.