Ir para o conteúdo

Implementação de conector usando o Jitterbit Connector SDK

Introdução

Embora cada conector construído com o Jitterbit Connector SDK seja diferente em design e implementação, existem alguns conceitos fundamentais que todos os conectores incluem, abordados em Conceitos do SDK de Conector no Jitterbit Connector SDK.

Esta página expande esses conceitos fundamentais, cobrindo detalhes de implementação para conectores desenvolvidos usando o SDK de Conector.

Se você estiver implementando um conector com a intenção de submetê-lo para certificação pela Jitterbit, o código-fonte submetido para certificação deve seguir o estilo de código Java da Jitterbit. Veja os comentários sobre estilo de código na seção Exemplo completo abaixo para o arquivo de configuração do checkstyle que deve ser usado.

Conector

Nomeação

Ao nomear um conector, não inclua caracteres especiais ou barras ( / ) no nome. Isso pode causar problemas quando o conector é carregado no agente.

Veja Adapter JSON para detalhes sobre como especificar os diferentes nomes usados em um conector.

Implementação

Um conector deve estender a interface BaseJitterbitConnector e fornecer uma fábrica que o Harmony pode chamar para criar instâncias dele. A implementação base inclui os métodos comuns que um conector deve implementar. (Uma alternativa é implementar a interface JitterbitConnector diretamente.)

Para indicar que a classe que implementa esta interface obrigatória é a que deve ser considerada como o JitterbitConnector, anote-a com uma anotação @Connector e forneça a classe que implementa sua fábrica.

(Uma alternativa ao uso de uma anotação é especificar a classe da fábrica no manifesto do arquivo JAR como o valor do atributo Jitterbit-Connector-Factory-Class. Veja Registro de conector para detalhes.)

Essas interfaces e seus métodos devem ser implementados:

  • Interface Connection e seus métodos open() e close()
  • Interface ConnectionFactory e seu método createConnection(Map<String,String> properties)

Aviso

Variáveis estáticas não devem ser usadas em um ambiente multi-threaded, como um conector. Isso pode levar a muitos problemas, incluindo corrupção de dados. Por exemplo, usar e acessar variáveis estáticas públicas em uma classe utilitária pode causar problemas:

public class MyConnectorUtils {
  public static String accessToken;
  public static String host;
  ...
}
Em vez disso, substitua essas variáveis estáticas públicas por variáveis de instância privadas e use métodos get e set para acessá-las:

public class MyConnectorUtils {
  private String accessToken;

  public String getAccessToken() {
    return accessToken;
  }

  public String setAccessToken(String accessToken) {
    this.accessToken = accessToken;
  }
  ...
}

Exemplo de conector

Um exemplo simples de um conector:

/**
 * Example Connector.
 */
@Connector(factory = ExampleConnector.ExampleConnectorFactory.class)
public class ExampleConnector extends BaseJitterbitConnector {

  public static final ExampleConnector INSTANCE = new ExampleConnector();

  static {
    connectionFactory = ExampleConnectionFactory.INSTANCE;
  }

  @Override
  public ConnectionFactory getConnectionFactory() {
    return connectionFactory;
  }

  @Override
  public String getName() {
    return "ExampleConnector";
  }

  private static ConnectionFactory connectionFactory;

  /**
   * ExampleConnectorFactory.
   */
  public static class ExampleConnectorFactory implements
    JitterbitConnector.Factory {
      @Override
      public JitterbitConnector create() {
        return ExampleConnector.INSTANCE;
      }
  }
}

Fábrica de conexão

Conectores são tipicamente usados para estabelecer uma conexão com um endpoint. Para criar essa conexão, facilitar sua configuração e testar a conexão, forneça uma implementação de uma ConnectionFactory. A conexão estará disponível para as atividades do conector através do contexto passado para cada atividade.

Exemplo de fábrica de conexão

Um exemplo simples de uma fábrica de conexão:

/**
* Factory that creates an ExampleConnection instance.
*/
public class ExampleConnectionFactory implements ConnectionFactory {

  public static final ExampleConnectionFactory INSTANCE =
    new ExampleConnectionFactory();

  private ExampleConnectionFactory () {}

  /**
   * Returns a connection to an Example endpoint,
   * created from the specified properties.
   *
   * @param props properties for configuring and
   *              creating an Example connection
   * @return the configured connection
   * @throws RuntimeException if the name or password
   *                          of the specified properties
   *                          are empty or null
   */
  @Override
  public Connection createConnection(Map<String, String> props) {
    String name = props.get("name");
    String password = props.get("password");
    String locale = !props.containsKey("locale") ?
      Locale.getDefault().toString() : "EN_US";
    if (name == null || name.length() == 0) {
      throw new RuntimeException("Name property cannot be empty. " +
        "Specify the name associated with the Example connection.");
    }
    if (password == null || password.length() == 0) {
      throw new RuntimeException("Password cannot be empty. " +
        "Specify the password associated with the Example connection.");
    }
    return new ExampleConnection(name, password, locale);
  }

  /**
   * Returns the pool size configuration.
   *
   * @return the pool size configuration
   */
  @Override
  public PoolSizeConfiguration getPoolSizeConfiguration() {
    return new PoolSizeConfiguration();
  }
}

Conexão

No exemplo anterior, a classe ExampleConnection (não mostrada) realmente criaria a conexão. Seus requisitos são determinados pelo endpoint ou serviço específico ao qual está se conectando, quaisquer bibliotecas sendo usadas para essa conexão (como para um banco de dados ou serviço web) e os detalhes da conexão.

Na interface do usuário do Integration Studio, o método open() da classe que implementa a interface Connection é chamado quando um usuário clica no botão Testar da configuração da conexão. Isso dá ao conector a oportunidade de não apenas criar a conexão, mas também verificar se a conexão funciona. Normalmente, isso pode ser feito chamando o endpoint ou serviço e retornando um pequeno payload ou usando o valor retornado para validar a conexão.

Como esse método é chamado toda vez que se abre uma conexão com um endpoint, caso não esteja atualmente aberta, é uma boa ideia que qualquer teste seja rápido e pequeno para não atrasar o processamento adicional.

Um exemplo disso é mostrado em DropboxConnection.java do conector Dropbox:

  /**
   * Opens a Dropbox version 2 connection.
   */
  public void open() throws ConnectionException {
    if (client != null) {
      return;
    }
    try {
      DbxRequestConfig dbxConfig = new DbxRequestConfig(appKey, locale);
      client = new DbxClientV2(dbxConfig, accessToken);
      ListFolderResult results = client.files().listFolder("");
      System.out.println("Dropbox Connection successful -> app-key: "
        + appKey + ", access-token: " + accessToken);
    } catch (Exception x) {
      x.printStackTrace();
      throw new ConnectionException(Messages.DROPBOX_CODE07,
          Messages.getMessage(Messages.DROPBOX_CODE07_MSG,
            new Object[]{x.getLocalizedMessage()}), x);
    }
  }

Se já houver uma conexão existente, o método retorna imediatamente. Caso contrário, uma nova conexão de cliente é criada dentro de um bloco try-catch. Uma vez que o cliente é criado, ele é testado solicitando a lista de objetos na pasta raiz (""). Os resultados retornados não são realmente verificados, pois uma chamada bem-sucedida é suficiente. Se houver um problema com a chave de acesso que um usuário fornece para criar a conexão, uma exceção será acionada pela API do Dropbox. Isso será capturado e, em seguida, relançado com uma mensagem de erro apropriada para o usuário.

Variações desse padrão de design podem ser usadas dependendo do endpoint ou serviço com o qual o conector está trabalhando.

Atividades

As atividades que um conector expõe e implementa são criadas por classes que implementam JitterbitActivity. Uma atividade Jitterbit é uma unidade de trabalho com dois papéis:

  • Descoberta/configuração: A descoberta de metadados associados a uma atividade e a configuração de seus parâmetros.
  • Execução: Uma unidade de execução que faz parte de uma cadeia de operações.

Embora o processo de descoberta aconteça primeiro na prática, discutiremos o processo de execução primeiro aqui, pois é o que determina os requisitos do processo de descoberta.

As atividades são declaradas no arquivo de manifesto como atributos Jitterbit-Activity-*, com IDs que são atribuídos com base no registro do conector no Harmony. (Veja Registro de conector para detalhes.)

Cada classe de atividade recebe uma anotação @Activity para registrá-la como parte do conector e deve implementar um método execute() que recebe um JitterbitActivity.ExecutionContext.

Durante a execução, o processo da operação invocará a atividade chamando o método execute(ExecutionContext) da atividade.

O contexto de execução contém informações sobre a solicitação (se presente) e a carga útil. A atividade é responsável por definir a carga útil da resposta (implementando o método JitterbitActivity.ExecutionContext.getResponsePayload()), que será então passada para a próxima atividade na cadeia de operações pelo mecanismo de operação do processo.

A partir do contexto passado, a atividade tem sua conexão com o Harmony. Ela pode obter:

  • Os parâmetros, se houver, com os quais a atividade foi configurada pelo usuário final. Por exemplo, chamando o método context.getFunctionParameters().get("folder").
  • Quaisquer conexões estabelecidas na configuração inicial da conexão, se o desenvolvedor as disponibilizar. Por exemplo, chamando o método context.getConnection().
  • Parâmetros do próprio conector, se o desenvolvedor os disponibilizar.
  • A carga útil da solicitação ou resposta, escrita ou obtida da conexão.

Os parâmetros configuráveis são definidos pelo usuário final na interface do Integration Studio e são feitos na configuração do conector e suas atividades. Eles são declarados no arquivo adapter.json incluído no arquivo JAR do conector.

As cargas úteis da solicitação ou resposta de uma atividade são os dados escritos ou obtidos da conexão; elas são determinadas pelos arquivos de esquema XML que definem essas cargas úteis, conforme descrito na próxima seção.

Solicitação e resposta da atividade

A solicitação e a resposta das atividades de um conector são tratadas usando a API Java para Binding XML (JAXB, versão 2+) para gerar classes Java a partir de esquemas XML. Um arquivo de esquema XML separado (.xsd) é usado para cada solicitação ou resposta. O mapeamento entre os arquivos gerados e essas fontes é dado no arquivo de saída sun-jaxb.episode.

As classes Java geradas podem ser importadas pelas classes que implementam o método execute() da atividade.

Por exemplo, na FetchFileActivity do conector Dropbox, os dados estão se movendo do Dropbox para o Harmony. O método execute() utiliza uma DropboxConnection e a API do Dropbox para recuperar dados e metadados; em seguida, define esses valores em um objeto de resposta (uma instância de FetchFileResponse) e, então, marshalling a resposta para o fluxo de saída do payload de resposta.

Por outro lado, na PutFileActivity do conector Dropbox, os dados estão se movendo do Harmony para o Dropbox. O método execute() nessa classe funciona na direção oposta. Ele unmarshals um fluxo de entrada, utiliza a API do Dropbox para fazer upload para o Dropbox e, em seguida, cria um objeto de resposta (neste caso, uma instância de PutFileResponse) completado com valores obtidos da resposta do Dropbox.

Cada atividade é responsável por implementar o método getActivityRequestResponseMetadata() e retornar um ActivityRequestResponseMetaData. Os arquivos de esquema XML são usados para criar o ActivityRequestResponseMetaData.

Para ajudar na criação desse objeto, uma utilidade auxiliar (como mostrado em DropboxUtils.setRequestResponseSchemas de DropboxUtils.java) está disponível para carregar os arquivos de esquema XML e defini-los como a solicitação ou resposta.

Eles aparecerão na interface do usuário do Integration Studio no esquema de dados exibido durante a etapa final da configuração de uma atividade. Se uma resposta ou solicitação não for desejada ou necessária, pode ser desconsiderada e nenhuma árvore de estrutura de dados será construída na interface do usuário do Integration Studio para esse componente. A atividade Fetch File do conector Dropbox é um exemplo disso; ela possui apenas uma resposta e nenhuma estrutura de dados de solicitação.

Se uma solicitação for necessária, isso pode ser especificado no arquivo JSON que define a interface do usuário do Integration Studio para o conector. Declarar inputRequired para uma atividade no adapter.json do conector forçará a interface do usuário do Integration Studio a lançar um erro de validação para a operação pai se não houver uma transformação de origem antes do uso da atividade. Por exemplo, este fragmento de um arquivo JSON mostra a definição da atividade SAP BAPI como exigindo entrada:

"activities": {
    "bapi": {
        "displayName": "BAPI",
        "inputRequired": true,
        "properties": [
            " . . . "
        ]
    }
}

Veja Componentes da UI do SDK do Conector para detalhes sobre como definir o arquivo JSON que especifica a UI do Cloud Studio.

Descoberta e metadados

Como parte do ciclo de vida da configuração de uma atividade de conector, é possível usar um processo de descoberta para obter informações necessárias para completar a configuração.

Um exemplo disso é obter um nome de tabela e, a partir dessa seleção, obter nomes de campos. Para facilitar isso, uma interface no SDK do Conector (org.jitterbit.connector.sdk.Discoverable) está disponível para implementação.

Quando a configuração de uma atividade é chamada na UI do Integration Studio, o método getObjectList() da interface é chamado, permitindo que o conector crie e retorne uma lista de objetos descobertos que podem ser exibidos na UI. Após uma seleção ser feita pelo usuário, essa seleção está disponível no método getActivityRequestResponseMetadata() da interface através do parâmetro activityFunctionParams que é passado.

Veja a ProcessFileActivity do conector Dropbox para um exemplo de como a descoberta pode ser usada na criação de metadados.

Mais exemplos estão incluídos nas descrições dos componentes da UI do Integration Studio que utilizam metadados, conforme descrito na próxima seção.

UI do Integration Studio

O conector e suas atividades são configurados através da UI do Integration Studio. A interface do usuário dessas configurações é especificada no arquivo adapter.json. O nome real do arquivo JSON pode ser alterado a partir desse padrão; ele é especificado no manifesto do arquivo JAR.

O arquivo especifica a UI para o conector e todas as suas atividades. Os detalhes do arquivo JSON são abordados em Componentes da UI do SDK do Conector.

Os componentes são categorizados como componentes básicos ou complexos.

  • Componentes básicos não interagem com o conector. Eles são usados apenas para receber um valor do usuário e retorná-lo ao conector.
  • Componentes complexos são mais sofisticados e envolvem múltiplos métodos e código adicional no conector para implementar seu processo de descoberta e para uso na execução. Eles são destinados a resolver desafios mais difíceis da interface do usuário do conector.

Observe que o name usado no arquivo JSON deve ser o mesmo nome sob o qual o conector está registrado e que está definido no código Java. Veja Registro de conectores para mais detalhes.

Manifest

Esses vários componentes (informações de registro para o conector e cada atividade, classes, classpath de terceiros, nome do arquivo da interface do usuário do conector) estão interligados no MANIFEST.MF que está incluído no arquivo JAR que arquiva o conector. Os detalhes do registro e do manifesto são abordados em Registro de conectores.

Construir o conector

Como de costume para um projeto Java dessa complexidade, um arquivo Maven pom.xml é essencial para vincular corretamente todas as dependências importadas e todos os componentes juntos. Ao executar a construção, você precisará primeiro compilar os arquivos XML Schema antes da compilação e empacotamento do Java. O comando Maven apropriado é:

$ mvn jaxb2:xjc compile install

Instalar

Diferente dos plugins do Jitterbit (que são instalados fazendo o upload para o Harmony e permitindo que a plataforma os instale associando os plugins a um grupo de agentes), os conectores do Harmony construídos com o SDK são instalados colocando manualmente seus arquivos JAR no diretório apropriado de um agente privado. Se o conector for usado em um grupo de agentes com mais de um agente, os arquivos JAR precisam ser copiados para cada agente privado. Se o conector depender de bibliotecas específicas que não estão incluídas em seus arquivos JAR, elas precisam ser instaladas no classpath de cada agente privado para que possam ser acessadas no momento em que o conector for carregado pelo agente.

Quando estiver desenvolvendo um conector, se estiver rodando no Linux ou macOS, recomendamos usar um agente privado Docker, pois ele pode ser configurado para montar como um volume local ao agente o diretório de construção do conector. Para Windows, um agente privado do Windows pode ser utilizado.

O diretório do conector é automaticamente escaneado pelo agente em busca de quaisquer alterações, e quaisquer conectores modificados são recarregados automaticamente, sem a necessidade de reiniciar ou solicitar ao agente. Isso acelera e simplifica o processo de desenvolvimento. Observe que você não deve construir diretamente neste diretório, pois os produtos de construção intermediários podem confundir o agente.

Localizações de Conectores

Agente Caminho do Diretório do Conector (padrão)
Agente privado do Windows C:\Program Files (x86)\Jitterbit Agent\Connectors\
Agente privado do Linux /opt/jitterbit/Connectors/
Agente privado Docker /opt/jitterbit/Connectors/
Este diretório geralmente é mapeado para um diretório externo no comando que inicia a imagem Docker

Sincronização

Conectores públicos (conectores publicados pela Jitterbit) sincronizam automaticamente com um agente Jitterbit conforme necessário. Para evitar a sincronização de conectores públicos, uma variável de ambiente (SKIP_SYNC_CONNECTORS) está disponível para controlar a sincronização. Defina esta variável de ambiente no shell que está executando o agente e reinicie o agente.

Definir SKIP_SYNC_CONNECTORS como um asterisco interromperá a sincronização de todos os conectores públicos:

SKIP_SYNC_CONNECTORS=*

Definir SKIP_SYNC_CONNECTORS como uma lista de conectores separados por vírgula interromperá a sincronização de todos os conectores públicos, exceto aqueles listados:

SKIP_SYNC_CONNECTORS=Box,Magento

Este último exemplo irá parar a sincronização de todos os conectores públicos, exceto pelos conectores Box e Magento, que serão sincronizados.

Exemplo completo

O conector Dropbox é um exemplo completo e funcional desses conceitos. Consulte-o para detalhes adicionais.

Se você deseja personalizar o conector Dropbox em seu próprio conector usando seu próprio pacote e domínio, precisará atualizar—além dos nomes dos pacotes, caminhos, conteúdo do código Java e o registro do seu conector—os seguintes itens:

  • pom.xml: Substitua o uso do Jitterbit pelo seu próprio domínio conforme apropriado; atualize o nome do artefato e a versão
  • MANIFEST.MF: Substitua o uso do Jitterbit pelo seu próprio nome conforme apropriado; atualize as chaves e IDs
  • DropboxConstants.java: Atualize o namespace em conjunto com os arquivos XML Schema XSD; atualize o nome do conector
  • Arquivos XML Schema XSD: Atualize o namespace de destino, em conjunto com o DropboxConstants.java
  • adapter.json e BaseJitterbitConnector: O campo nome do adapter.json e o nome que anota a classe que estende BaseJitterbitConnector são usados para nomear o conector. Se especificado no adapter.json, o framework usará esse nome; caso contrário, o nome fornecido na anotação será utilizado. Consulte DropboxConstants.java e DropboxConnector.java para exemplos de como isso acontece.

Estilo de código

O conector Dropbox foi formatado seguindo o estilo de código Java do Jitterbit. Este estilo deve ser seguido para qualquer código que seja enviado ao Jitterbit como parte da certificação de um conector.

Para implementar este estilo em seu código-fonte:

  • inclua em seu código-fonte o arquivo Jitterbit checkstyle.xml; e
  • inclua em seu arquivo pom.xml uma referência ao maven-checkstyle-plugin e ao arquivo checkstyle.xml. Adicione à seção <plugins> da seção <build> do pom.xml:
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-checkstyle-plugin</artifactId>
      <version>2.17</version>
      <executions>
        <execution>
          <id>validate</id>
          <phase>process-test-classes</phase>
          <configuration>
            <configLocation>checkstyle.xml</configLocation>
            <suppressionsLocation>suppressions.xml</suppressionsLocation>
            <encoding>UTF-8</encoding>
            <consoleOutput>true</consoleOutput>
            <failsOnError>true</failsOnError>
            <includeTestSourceDirectory>true</includeTestSourceDirectory>
          </configuration>
          <goals>
            <goal>check</goal>
          </goals>
        </execution>
      </executions>
      <dependencies>
        <dependency>
          <groupId>com.puppycrawl.tools</groupId>
          <artifactId>checkstyle</artifactId>
          <version>6.19</version>
        </dependency>
      </dependencies>
    </plugin>
    

Consulte o arquivo pom.xml do conector Dropbox para um exemplo de como usar este checkstyle em um projeto.