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 origem 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 para quem programa em Java. O SDK do Content Connector é um wrapper da API REST que permite criar conectores rapidamente. Para criar um conector de conteúdo usando o SDK, consulte esta página.

  • 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 esta página.

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 listas de controle de acesso (ACLs, na sigla em inglês), metadados e dados de conteúdo em itens indexáveis.
  4. Indexação de itens com a origem 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 origem 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

É necessário incluir determinadas dependências no arquivo de criação para usar o SDK. Clique na guia abaixo para ver as dependências do ambiente de criação:

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 ID 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 seguintes parâmetros fornecidos pelo Google no arquivo de configuração:

  • No caso dos conectores de conteúdo, declare api.sourceId e api.serviceAccountPrivateKeyFile porque esses parâmetros identificam o local do seu 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 sua origem de identidade externa. Se você estiver sincronizando usuários, também é necessário declarar api.customerId como o ID exclusivo da conta do G Suite da sua 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.

Observação: não há nenhum requisito de nomenclatura estrito para o arquivo de propriedades do conector, mas recomendamos salvar o arquivo usando uma extensão .properties ou .config.

Transmitir o arquivo de configuração para o conector

Defina o config de propriedade do sistema para transmitir o arquivo de configuração para o 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 tentará acessar um arquivo de configuração padrão denominado 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. Implemente uma estratégia de travessia com base no tamanho e no layout dos 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 em sua maioria estáticos e não hierárquicos. Também é possível usar essa estratégia de travessia quando a detecção de alterações é difícil ou não é compatível com o 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 que o índice é atualizado.

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

Estratégia de travessia de grafos

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

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 modelo no SDK. É possível implementar sua própria estratégia de travessia, mas 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 (em inglês).

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 que tem os métodos que você implementa. O snippet de código a seguir 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() realiza as seguintes tarefas:

  1. Chama o método Configuation.isInitialized() para garantir que Configuration não tenha sido inicializada.
  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

A única função do objeto Repository é realizar a travessia e fazer a indexação de itens de repositório. Ao usar um modelo, você precisa 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, modifique 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 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 seu 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 em uma ou em várias chamadas IndexingService.indexItem() para fazer a indexação real do repositório.

Receber parâmetros de configuração personalizados

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

A classe Configuration tem vários métodos para receber tipos de dados diferentes de uma configuração. Cada método retorna um objeto ConfigValue. Use o método get() do objeto ConfigValue para recuperar o valor real. O seguinte snippet, de FullTraversalSample (em inglês), mostra como recuperar 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, que é 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(), na verdade, retorna um CheckpointCloseableIterable que é uma iteração de objetos ApiOperation, cada objeto representando uma solicitação de API executada em um RepositoryDoc, como uma indexação.

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 IDs de grupos ou usuários que podem acessar o item.

É necessário duplicar essa ACL na origem de dados para garantir que apenas os usuários com acesso a um item possam vê-lo em um resultado de pesquisa. Assim, é necessário que a ACL do item seja incluída ao indexá-lo 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 esta página.

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

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

Você precisará entender as ACLs para modelá-las corretamente para a origem de dados. 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 de ACL requer informações extras 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 um Item, no mínimo, é necessário ter um ID de string exclusivo, um tipo do item, uma ACL, um URL e uma versão do 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 para o item, crie um 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 é adequado para grandes cotas de capacidade de 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 é adequado para cotas de capacidade limitadas. 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 fazer isso.

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

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

A seguir

Veja a seguir algumas das próximas etapas:

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

A fila de indexação do Cloud Search é usada para armazenar IDs e, opcionalmente, valores de hash de cada item no repositório. Um conector de travessia de listas envia IDs de itens às listas de indexação do Google Cloud Search 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 Google Cloud Search.

Nesta seção da documentação, são usados snippets de código do exemplo ListTraversalSample (em inglês).

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() faz o seguinte:

  1. Chama o método Configuation.isInitialized() para garantir que Configuration não tenha sido inicializada.
  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

A única função do objeto Repository é realizar a travessia e fazer a indexação de itens de repositório. Ao usar um modelo, você precisa 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 ListingConnector, modifique 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 seu 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 em uma ou em várias chamadas IndexingService.indexItem() para fazer a indexação real do repositório.

Receber parâmetros de configuração personalizados

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

A classe Configuration tem vários métodos para receber tipos de dados diferentes de uma configuração. Cada método retorna um objeto ConfigValue. Use o método get() do objeto ConfigValue para recuperar o valor real. O seguinte snippet, de FullTraversalSample (em inglês), mostra como recuperar 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 os IDs e os valores de hash de todos os registros no repositório. O método getIds() aceita um checkpoint, que é usado para retomar a indexação em um item específico, caso o processo seja interrompido.

Em seguida, modifique o método getDoc() para manipular cada item na fila de indexação do Cloud Search.

Enviar IDs de itens e valores de hash

Modifique getIds() para buscar, no repositório, os IDs de itens e os valores de hash de conteúdo associados a eles. Os pares de ID e valor de hash são empacotados em uma solicitação de operação de envio para a fila de indexação do Cloud Search. Geralmente, os IDs raiz ou pai são enviados primeiro, seguidos pelos IDs 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, realize 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(), na verdade, retorna um CheckpointCloseableIterable que é uma iteração de objetos ApiOperation, cada objeto representando uma solicitação de API executada em um RepositoryDoc, como o envio dos itens para a fila.

O snippet de código a seguir mostra como buscar cada ID de item e valor de hash e inseri-los em um PushItems. Um PushItems é uma solicitação ApiOperation que serve para enviar um item para a 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 envio único de 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 manipular 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 do índice os itens 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 ID do item na fila de indexação do Cloud Search existe no repositório. Se não existir, exclua o item do índice.

  2. Pesquise o índice para saber o 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 o RepositoryDoc.

Observação: o modelo ListingConnector não aceita o retorno de null no método getDoc(). Retornar null resultará em 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 de itens em filas de indexação do Cloud Search 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 está modificado, verifique o status dele e outros metadados que possam indicar uma alteração. No exemplo, o hash de metadados é usado 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 IDs de grupos ou usuários que podem acessar o item.

É necessário duplicar essa ACL na origem de dados para garantir que apenas os usuários com acesso a um item possam vê-lo em um resultado de pesquisa. Assim, é necessário que a ACL do item seja incluída ao indexá-lo 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 esta página.

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

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

Você precisará entender as ACLs para modelá-las corretamente para a origem de dados. 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 de ACL requer informações extras 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 um Item, no mínimo, é necessário ter um ID de string exclusivo, um tipo do item, uma ACL, um URL e uma versão do 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 é adequado para grandes cotas de capacidade de 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 é adequado para cotas de capacidade limitadas. 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.

A seguir

Veja a seguir algumas das próximas etapas:

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

As filas de indexação do Cloud Search são usadas para armazenar IDs e, opcionalmente, valores de hash de cada item no repositório. Um conector de travessia de grafos envia IDs de itens às filas de indexação do Google Cloud Search 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 Google Cloud Search.

Durante a indexação, o conteúdo do item é buscado no repositório de dados e todos os IDs de itens filhos são enviados para a fila. O conector continua a processar os IDs 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 que tem os 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() faz o seguinte:

  1. Chama o método Configuation.isInitialized() para garantir que Configuration não tenha sido inicializada.
  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

A única função do objeto Repository é realizar a travessia e fazer a indexação de itens do repositório. Ao usar um modelo, você precisa 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 ListingConnector, modifique 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 seu 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 em uma única chamada IndexingService.indexItem(), ou em várias, para fazer a indexação real do repositório.

Receber parâmetros de configuração personalizados

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

A classe Configuration tem vários métodos para receber tipos de dados diferentes de uma configuração. Cada método retorna um objeto ConfigValue. Use o método get() do objeto ConfigValue para recuperar o valor real. O seguinte snippet, de FullTraversalSample (em inglês), mostra como recuperar 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 os IDs e os valores de hash de todos os registros no repositório. O método getIds() aceita um checkpoint, que é usado para retomar a indexação em um item específico, caso o processo seja interrompido.

Em seguida, modifique o método getDoc() para manipular cada item na fila de indexação do Cloud Search.

Enviar IDs de itens e valores de hash

Modifique getIds() para buscar, no repositório, os IDs de itens e os valores de hash de conteúdo associados a eles. Os pares de ID e valor de hash são empacotados em uma solicitação de operação de envio para a fila de indexação do Cloud Search. Geralmente, os IDs raiz ou pai são enviados primeiro, seguidos pelos IDs 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, realize 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(), na verdade, retorna um CheckpointCloseableIterable que é uma iteração de objetos ApiOperation, cada objeto representando uma solicitação de API executada em um RepositoryDoc, como o envio dos itens para a fila.

O snippet de código a seguir mostra como buscar cada ID de item e valor de hash e inseri-los em um PushItems. Uma PushItems é uma solicitação ApiOperation que serve 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 envio único de 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 manipular 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 do índice os itens 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 ID do item na fila de indexação do Cloud Search 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, 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. Coloque os IDs filhos na fila de indexação do Cloud Search para processamento adicional.
    5. Retorne o 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 IDs de grupos ou usuários que podem acessar o item.

É necessário duplicar essa ACL na origem de dados para garantir que apenas os usuários com acesso a um item possam vê-lo em um resultado de pesquisa. Assim, é necessário que a ACL do item seja incluída ao indexá-lo 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 esta página.

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

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

Você precisará entender as ACLs para modelá-las corretamente para a origem de dados. 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 de ACL requer informações extras 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 um Item, no mínimo, é necessário ter um ID de string exclusivo, um tipo do item, uma ACL, um URL e uma versão do 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 é adequado para grandes cotas de capacidade de 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 é adequado para cotas de capacidade limitadas. 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 IDs filhos de um item pai atualmente em processamento. Esses IDs 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();

A seguir

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. Implemente uma estratégia de travessia com base no tamanho e no layout dos 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 em sua maioria estáticos e não hierárquicos. Também é possível usar essa estratégia de travessia quando a detecção de alterações é difícil ou não é compatível com o 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 que o índice é atualizado.

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

Estratégia de travessia de grafos

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

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 Google 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. No caso de 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 a indexação.

  3. Usando items.index para indexar o item. Por exemplo, se 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 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 alterações 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 as filas de indexação do Google Cloud Search 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 posteriormente pesquisá-los e atualizá-los. Para mais informações sobre esse recurso, consulte Filas de indexação do Google Cloud Search.

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