Saltar al contenido

Implementación del conector utilizando el SDK de Conectores de Jitterbit

Introducción

Aunque cada conector construido con el SDK de Conectores de Jitterbit será diferente en diseño e implementación, hay algunos conceptos básicos que todos los conectores incluyen, cubiertos en Conceptos del SDK de Conectores en SDK de Conectores de Jitterbit.

Esta página amplía esos conceptos básicos, cubriendo detalles de implementación para conectores desarrollados utilizando el SDK de Conectores.

Si se está implementando un conector con la intención de enviarlo para certificación por Jitterbit, el código fuente enviado para certificación debe seguir el estilo de código Java de Jitterbit. Consulte los comentarios sobre estilo de código en la sección Ejemplo completo a continuación para el archivo de configuración de checkstyle que debe utilizarse.

Conector

Nomenclatura

Al nombrar un conector, no incluya caracteres especiales ni barras diagonales (/) en el nombre. Estos pueden causar problemas cuando el conector se carga en el agente.

Consulte JSON del adaptador para obtener detalles sobre cómo especificar los diferentes nombres utilizados en un conector.

Implementación

Un conector debe extender la interfaz BaseJitterbitConnector y proporcionar una fábrica que Harmony pueda llamar para crear instancias de él. La implementación base incluye los métodos comunes que un conector debe implementar. (Una alternativa es implementar la interfaz JitterbitConnector directamente.)

Para indicar que la clase que implementa esta interfaz requerida es la que debe considerarse como el JitterbitConnector, anótela con una anotación @Connector y proporcione la clase que implementa su fábrica.

(Una alternativa a usar una anotación es especificar la clase de fábrica en el manifiesto del archivo JAR como el valor del atributo Jitterbit-Connector-Factory-Class. Consulte Registro de conectores para obtener detalles.)

Estas interfaces y sus métodos deben ser implementados:

  • Interfaz Connection y sus métodos open() y close()
  • Interfaz ConnectionFactory y su método createConnection(Map<String,String> properties)

Advertencia

Las variables estáticas no deben ser utilizadas en un entorno multi-hilo como un conector. Esto puede llevar a muchos problemas, incluyendo la corrupción de datos. Por ejemplo, usar y acceder a variables estáticas públicas en una clase de utilidad puede causar problemas:

public class MyConnectorUtils {
  public static String accessToken;
  public static String host;
  ...
}
En su lugar, reemplace estas variables estáticas públicas con variables de instancia privadas y use métodos get y set para acceder a ellas:

public class MyConnectorUtils {
  private String accessToken;

  public String getAccessToken() {
    return accessToken;
  }

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

Example connector

Un ejemplo simple de un 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;
      }
  }
}

Connection factory

Los conectores se utilizan típicamente para establecer una conexión con un punto final. Para crear esa conexión, facilitar su configuración y probar la conexión, proporcione una implementación de ConnectionFactory. La conexión estará disponible para las actividades del conector a través del contexto pasado a cada actividad.

Example connection factory

Un ejemplo simple de una fábrica de conexiones:

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

Connection

En el ejemplo anterior, la clase ExampleConnection (no mostrada) realmente crearía la conexión. Sus requisitos son determinados por el punto final o servicio particular al que se está conectando, cualquier biblioteca que se esté utilizando para esa conexión (como a una base de datos o servicio web), y los detalles de la conexión.

En la interfaz de usuario de Integration Studio, el método open() de la clase que implementa la interfaz Connection se llama cuando un usuario hace clic en el botón Test de la configuración de la conexión. Esto le da al conector la oportunidad de no solo crear la conexión, sino también de verificar que la conexión funcione. Típicamente, esto se puede hacer llamando al punto final o servicio y devolviendo un pequeño payload o utilizando el valor devuelto para validar la conexión.

Como este método se llama cada vez que se abre una conexión a un endpoint si no está actualmente abierta, es una buena idea que cualquier prueba sea rápida y pequeña para no retrasar ningún procesamiento adicional.

Un ejemplo de esto se muestra en el DropboxConnection.java del conector de 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);
    }
  }

Si hay una conexión existente, el método devuelve inmediatamente. De lo contrario, se crea una nueva conexión de cliente dentro de un bloque try-catch. Una vez que se crea el cliente, se prueba solicitando la lista de objetos en la carpeta raíz (""). Los resultados devueltos no se verifican realmente, ya que una llamada exitosa es suficiente. Si hay un problema con la clave de acceso que un usuario proporciona para crear la conexión, la API de Dropbox generará una excepción. Esta será capturada y luego lanzada nuevamente con un mensaje de error apropiado para el usuario.

Se pueden utilizar variaciones de este patrón de diseño dependiendo del endpoint o servicio con el que esté trabajando el conector.

Actividades

Las actividades que un conector expone e implementa son creadas por clases que implementan JitterbitActivity. Una actividad de Jitterbit es una unidad de trabajo con dos roles:

  • Descubrimiento/configuración: El descubrimiento de metadatos asociados con una actividad y la configuración de sus parámetros.
  • Ejecución: Una unidad de ejecución que es parte de una cadena de operaciones.

Aunque el proceso de descubrimiento ocurre primero en la práctica, discutiremos el proceso de ejecución primero aquí, ya que es lo que determina los requisitos del proceso de descubrimiento.

Las actividades se declaran en el archivo de manifiesto como atributos Jitterbit-Activity-*, con IDs que se asignan en función del registro del conector con Harmony. (Consulte Registro de conectores para más detalles.)

Cada clase de actividad recibe una anotación @Activity para registrarla como parte del conector y debe implementar un método execute() que recibe un JitterbitActivity.ExecutionContext.

Durante el tiempo de ejecución, el proceso de operación invocará la actividad llamando al método execute(ExecutionContext) de la actividad.

El contexto de ejecución contiene información sobre la solicitud (si está presente) y la carga útil. La actividad es responsable de establecer la carga útil de respuesta (implementando el método JitterbitActivity.ExecutionContext.getResponsePayload()), que luego será entregada a la siguiente actividad dentro de la cadena de operaciones por el motor de operaciones del proceso.

Desde el contexto pasado, la actividad tiene su conexión a Harmony. Puede obtener:

  • Los parámetros, si los hay, con los que la actividad fue configurada por el usuario final. Por ejemplo, llamando al método context.getFunctionParameters().get("folder").
  • Cualquier conexión establecida en la configuración inicial de la conexión, si el desarrollador las hace disponibles. Por ejemplo, llamando al método context.getConnection().
  • Parámetros del conector en sí, si el desarrollador los hace disponibles.
  • La carga útil de solicitud o respuesta, escrita o obtenida de la conexión.

Los parámetros configurables son establecidos por el usuario final en la interfaz de usuario de Integration Studio y se realizan en la configuración del conector y sus actividades. Se declaran en el archivo adapter.json incluido en el archivo JAR del conector.

Las cargas útiles de solicitud o respuesta de una actividad son los datos escritos o obtenidos de la conexión; se determinan por los archivos de esquema XML que definen esas cargas útiles, como se describe en la siguiente sección.

Solicitud y respuesta de actividad

La solicitud y respuesta de las actividades de un conector se manejan utilizando la API de Java para el enlace XML (JAXB, versión 2+) para generar clases de Java a partir de esquemas XML. Se utiliza un archivo de esquema XML separado (.xsd) para cada solicitud o respuesta. La correspondencia entre los archivos generados y estas fuentes se da en el archivo de salida sun-jaxb.episode.

Las clases de Java generadas pueden ser importadas por las clases que implementan el método execute() de la actividad.

Por ejemplo, en la FetchFileActivity del conector de Dropbox, los datos se mueven de Dropbox a Harmony. Ese método execute() utiliza una DropboxConnection y la API de Dropbox para recuperar datos y metadatos; luego establece esos valores en un objeto de respuesta (una instancia de FetchFileResponse), y luego envía la respuesta al flujo de salida de carga útil de respuesta.

Por el contrario, en la PutFileActivity del conector de Dropbox, los datos se mueven de Harmony a Dropbox. El método execute() en esa clase trabaja en la dirección opuesta. Deserializa un flujo de entrada, utiliza la API de Dropbox para cargar en Dropbox, y luego crea un objeto de respuesta (en este caso, una instancia de PutFileResponse) completado con valores obtenidos de la respuesta de Dropbox.

Cada actividad es responsable de implementar el método getActivityRequestResponseMetadata() y devolver un ActivityRequestResponseMetaData. Los archivos de esquema XML se utilizan para crear el ActivityRequestResponseMetaData.

Para ayudar en la creación de ese objeto, hay una utilidad auxiliar (como se muestra en DropboxUtils.setRequestResponseSchemas de DropboxUtils.java) disponible para cargar los archivos de esquema XML y establecerlos como la solicitud o respuesta.

Luego aparecerán en la interfaz de usuario de Integration Studio en el esquema de datos mostrado durante el paso final de la configuración de una actividad. Si no se desea o se requiere una respuesta o solicitud, se puede ignorar y no se construirá un árbol de estructura de datos en la interfaz de usuario de Integration Studio para ese componente. La actividad Fetch File del conector de Dropbox es un ejemplo de esto; solo tiene una respuesta y no una estructura de datos de solicitud.

Si se requiere una solicitud, esto se puede especificar en el archivo JSON que define la interfaz de usuario de Integration Studio para el conector. Al declarar inputRequired para una actividad en el adapter.json del conector, se forzará a la interfaz de usuario de Integration Studio a lanzar un error de validación para la operación principal si no hay una transformación de origen antes del uso de la actividad. Por ejemplo, este fragmento de un archivo JSON muestra la definición de la actividad SAP BAPI como que requiere entrada:

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

Consulte Componentes de la interfaz de usuario del SDK de Conectores para obtener detalles sobre cómo definir el archivo JSON que especifica la interfaz de usuario de Cloud Studio.

Descubrimiento y metadatos

Como parte del ciclo de vida de la configuración de una actividad de conector, se puede utilizar un proceso de descubrimiento para obtener información que se requiere para completar la configuración.

Un ejemplo de esto es obtener un nombre de tabla y, a partir de esta selección, obtener nombres de campos. Para facilitar esto, hay una interfaz en el SDK de Conectores (org.jitterbit.connector.sdk.Discoverable) disponible para implementación.

Cuando se llama a la configuración de una actividad en la interfaz de usuario de Integration Studio, se llama al método getObjectList() de la interfaz, lo que permite al conector crear y devolver una lista de objetos descubiertos que se pueden mostrar en la interfaz de usuario. Después de que el usuario realiza una selección, esa selección está disponible en el método getActivityRequestResponseMetadata() de la interfaz a través del parámetro activityFunctionParams que se pasa.

Consulte la ProcessFileActivity del conector de Dropbox para un ejemplo de cómo se puede utilizar el descubrimiento en la creación de metadatos.

Se incluyen más ejemplos en las descripciones de los componentes de la interfaz de usuario de Integration Studio que utilizan metadatos, como se describe en la siguiente sección.

Interfaz de usuario de Integration Studio

El conector y sus actividades se configuran a través de la interfaz de usuario de Integration Studio. La interfaz de usuario de esas configuraciones se especifica en el archivo adapter.json. El nombre real del archivo JSON se puede cambiar de ese valor predeterminado; se especifica en el manifiesto del archivo JAR.

El archivo especifica la interfaz de usuario para el conector y todas sus actividades. Los detalles del archivo JSON se cubren en Componentes de la interfaz de usuario del SDK de Conectores.

Los componentes se clasifican como componentes básicos o complejos.

  • Componentes básicos no interactúan con el conector. Se utilizan simplemente para recibir un valor del usuario y devolverlo al conector.
  • Componentes complejos son más sofisticados e involucran múltiples métodos y código adicional en el conector para implementar su proceso de descubrimiento y para usarse en la ejecución. Están destinados a resolver desafíos más difíciles en la interfaz de usuario del conector.

Tenga en cuenta que el name utilizado en el archivo JSON debe ser el mismo nombre bajo el cual está registrado el conector y que está definido en el código Java. Consulte Registro de conectores para más detalles.

Manifiesto

Estos diversos componentes (información de registro para el conector y cada actividad, clases, classpath de terceros, nombre de archivo de la interfaz de usuario del conector) están vinculados en el MANIFEST.MF que se incluye en el archivo JAR que archiva el conector. Los detalles del registro y el manifiesto se cubren en Registro de conectores.

Construir el conector

Como es habitual en un proyecto de Java de esta complejidad, un archivo Maven pom.xml es esencial para vincular correctamente todas las dependencias importadas y todos los componentes juntos. Al ejecutar la construcción, primero deberá compilar los archivos XML Schema antes de la compilación y empaquetado de Java. El comando Maven apropiado es:

$ mvn jaxb2:xjc compile install

Instalar

A diferencia de los complementos de Jitterbit (que se instalan subiéndolos a Harmony y permitiendo que la plataforma los instale asociándolos con un grupo de agentes), los conectores de Harmony construidos con el SDK se instalan colocando manualmente sus archivos JAR en el directorio apropiado de un agente privado. Si el conector se va a utilizar en un grupo de agentes con más de un agente, los archivos JAR deben copiarse a cada agente privado. Si el conector depende de bibliotecas específicas que no están incluidas en sus archivos JAR, deben instalarse en el classpath de cada agente privado para que puedan ser accedidas en el momento en que el conector sea cargado por el agente.

Cuando se desarrolla un conector, si se está ejecutando en Linux o macOS, se recomienda utilizar un agente privado de Docker, ya que se puede configurar para montar como un volumen local al agente el directorio de construcción del conector. Para Windows, se puede utilizar un agente privado de Windows.

El directorio del conector es escaneado automáticamente por el agente en busca de cambios, y cualquier conector modificado se recarga automáticamente, sin necesidad de reiniciar o solicitar al agente. Esto acelera y simplifica el proceso de desarrollo. Tenga en cuenta que no debe construir directamente en este directorio, ya que los productos de construcción intermedios pueden confundir al agente.

Ubicaciones de conectores

Agente Ruta del directorio del conector (predeterminado)
Agente privado de Windows C:\Program Files (x86)\Jitterbit Agent\Connectors\
Agente privado de Linux /opt/jitterbit/Connectors/
Agente privado de Docker /opt/jitterbit/Connectors/
Este directorio generalmente está mapeado a un directorio externo en el comando que inicia la imagen de Docker

Sincronización

Los conectores públicos (conectores publicados por Jitterbit) se sincronizan automáticamente con un agente de Jitterbit según sea necesario. Para evitar la sincronización de conectores públicos, hay una variable de entorno (SKIP_SYNC_CONNECTORS) disponible que controla la sincronización. Establezca esta variable de entorno en el shell que está ejecutando el agente y reinicie el agente.

Establecer SKIP_SYNC_CONNECTORS a un asterisco detendrá la sincronización de todos los conectores públicos:

SKIP_SYNC_CONNECTORS=*

Establecer SKIP_SYNC_CONNECTORS a una lista de conectores separada por comas detendrá la sincronización de todos los conectores públicos, excepto los que están listados:

SKIP_SYNC_CONNECTORS=Box,Magento

Este último ejemplo detendrá la sincronización de todos los conectores públicos, excepto para los conectores de Box y Magento, que serán sincronizados.

Ejemplo completo

El conector de Dropbox es un ejemplo completo de trabajo de estos conceptos. Consúltalo para obtener detalles adicionales.

Si deseas personalizar el conector de Dropbox en tu propio conector utilizando tu propio paquete y dominio, necesitarías actualizar, además de los nombres de los paquetes, las rutas, el contenido del código Java y el registro de tu conector, estos elementos:

  • pom.xml: Reemplaza el uso de Jitterbit con tu propio dominio según corresponda; actualiza el nombre del artefacto y la versión.
  • MANIFEST.MF: Reemplaza el uso de Jitterbit con tu propio nombre según corresponda; actualiza las claves y los IDs.
  • DropboxConstants.java: Actualiza el espacio de nombres en conjunto con los archivos XML Schema XSD; actualiza el nombre del conector.
  • Archivos XML Schema XSD: Actualiza el espacio de nombres objetivo, en conjunto con el DropboxConstants.java.
  • adapter.json y BaseJitterbitConnector: El campo de nombre del adapter.json y el nombre que anota la clase que extiende BaseJitterbitConnector se utilizan para nombrar el conector. Si se especifica en el adapter.json, el marco utilizará ese nombre; de lo contrario, se utilizará el nombre proporcionado en la anotación. Consulta DropboxConstants.java y DropboxConnector.java para ejemplos de cómo sucede esto.

Estilo de código

El conector de Dropbox ha sido formateado siguiendo el estilo de código Java de Jitterbit. Este estilo debe ser seguido para cualquier código que se envíe a Jitterbit como parte de la certificación de un conector.

Para implementar este estilo en tu código fuente:

  • Incluye en tu código fuente el archivo Jitterbit checkstyle.xml; y

  • Incluye en tu archivo pom.xml una referencia al maven-checkstyle-plugin y ese archivo checkstyle.xml. Agrega a los <plugins> de la sección <build> del 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>
    

Refiérase al archivo pom.xml del conector de Dropbox para un ejemplo de cómo usar este checkstyle en un proyecto.