Building a component in Java

Here is how to build a component in Java.

The elastic.io platform supports Java programming language for building integration components. Please read the JavaDocs of the Java SDK or browse the source code on GitHub.

To help you create a component in Java we have created a simple Petstore component in Java which connects to the Petstore API and demonstrates multiple features of the platform.

Petstore Component

Let’s have a look at the structure of the Petstore component first.

petstore-component-java
├── build.gradle                                (1)
├── component.json                              (2)
├── gradle
│   └── wrapper                                 (3)
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew                                     (4)
├── gradlew.bat                                 (5)
├── logo.png                                    (6)
├── schemas                                     (7)
│   ├── createPet.in.json
│   ├── createPet.out.json
│   └── getPetsByStatus.out.json
└── src
    └── main
        └── java                                (8)

The Java components for the elastic.io platform are built by Gradle and so have a typical structure of a Gradle project. Each component has a build.gradle (1) file used to configure Gradle project, dependencies, plugins, etc. Your build file can be a regular Gradle build file. We only require you to define the following dependency:

compile "io.elastic:sailor-jvm:2.0.1"

Sailor is the Java SDK for the elastic.io platform. It makes your component a citizen of the elastic.io platform by providing you a simple programming model for components and ensuring a smooth communication with the platform.

Java components are always built with Gradle Wrapper in order to make sure that we build your component with the same version of Gradle as you did. That’s why you are required to add Gradle wrapper (3), (4) and (5) to your project and commit it to Git.

If you have a logo for the component, you can place the file called logo.png (6) in the root directory of the component. Typically the logo of the API vendor gets used as component logo. If you did not provide any logo, the component will show a generic logo for your component.

The directory src/main/java (8) is predefined directory Gradle expects your Java sources to be located in and the schemas (7) directory is the location of JSON schemas defining the component’s metatada which we will cover later in this article.

Last but not least the component.json file (2) is the component descriptor interpreted by the platform to gather all the required information to be presented to the user in the platform UI. For example, you can define simple things like component’s title in the component descriptor but also the component’s authentication mechanism. The descriptor is the only place to list the functionality provided by the component, the so called triggers and actions.

Component descriptor

As mentioned above the component.json file is the component descriptor interpreted by the platform to gather all the required information about the component. Let’s explore the descriptor of the Petstore component:

{
  "title": "Petstore API (Java)",                                           (1)
  "description": "Component for the Petstore API",                          (2)
  "credentials": {                                                          (3)
    "fields": {
      "apiKey": {
        "label": "API key",
        "required": true,
        "viewClass": "TextFieldWithNoteView",
      }
    },
    "verifier": "io.elastic.petstore.ApiKeyVerifier"                        (4)
  },
  "triggers": {                                                             (5)
    ...
  },
  "actions": {                                                              (6)
    ...
  }
}

The component descriptor above defines the component title (1) and description (2). It also defines the fields used to ask the user to provide input for authentication (3). In this case a single field is define in which the user will input the API key for the Petstore API so that the component can communicate with the API on user’s behalf. The property verifier (4) is used to define an implementation of the io.elastic.api.CredentialsVerifier interface which will be invoked by the platform when a user credential, such as an API key, needs to be verified before storing it in the platform.

The triggers (5) and actions (6) properties are used to define the component’s triggers and actions.

Now let’s have a closer look on how to define triggers. The example below demonstrates the triggers section from the component.json component descriptor file.

  "triggers": {
    "getPetsByStatus": {                                                (1)
      "main": "io.elastic.petstore.triggers.GetPetsByStatus",           (2)
      "type": "polling",                                                (3)
      "title": "Get Pets By Status (HttpClient)",
      "fields": {                                                       (4)
        "status": {
          "label": "Pet Status",
          "required": true,
          "viewClass": "SelectView",
          "model": {
            "available": "Available",
            "pending": "Pending",
            "sold": "Sold"
          },
          "prompt": "Select Pet Status"
        }
      },
      "metadata": {
        "out": "./schemas/getPetsByStatus.out.json"                     (5)
      }
    }
  }

The example above demonstrates that the trigger with id getPetsByStatus (1) is implemented by the GetPetsByStatus class (2). The trigger is of polling type (3) meaning it will wake up periodically to poll for changes in the Petstore API. The triggers can be configured with some fields (4) and defines out-metadata in the file getPetsByStatus.out.json (5).

Verifying credentials

As mentioned above you can configure a credentials verifying in the component’s descriptor. In the Petstore component the verifier is implemented in the io.elastic.petstore.ApiKeyVerifier class, shown below:

public class ApiKeyVerifier implements CredentialsVerifier {                    (1)

    @Override
    public void verify(final JsonObject configuration)
        throws InvalidCredentialsException {                                    (2)
        try {
            final JsonObject user
                = HttpClientUtils.getSingle("/user/me", configuration);         (3)
        } catch (Exception e) {                                                 (4)
            throw new InvalidCredentialsException("Failed to verify credentials", e);
        }
    }
}

The ApiKeyVerifier class above is an implementation of the io.elastic.api.CredentialsVerifier interface (1) which defines the method verify (2). This method takes a JsonObject which represents the component’s configuration and may throw an InvalidCredentialsException exception. The component’s configuration holds the values user input into the credentials fields defined in component.json (see above). The verification above is implemented by sending a simple request to the Petstore API (3). If the request succeeds, the verify method’s execution completes successfully and the credentials as assumed to be valid. Otherwise an InvalidCredentialsException is thrown to signal the platform that the provided credentials (API key) is invalid. An error will be displayed to the user.

Implementing a trigger

Any integration flow starts with a trigger which is responsible to start the flow’s execution by providing new data to be processed. A trigger might query an API for updates and in case of new changes start the integration flow.

Now let’s have a look at how to implement a trigger defined in the component.json descriptor above. The following listing demonstrates the GetPetsByStatus class which is responsible to retrieve pets from the Petstore API by a status. If new pets can be found, the trigger will start the flow to process the new pets.

public class GetPetsByStatus implements Module {                            (1)

    @Override
    public void execute(ExecutionParameters parameters) {                   (2)
        JsonObject configuration = parameters.getConfiguration();           (3)

        JsonString status = configuration.getJsonString("status");          (4)

        if (status == null) {
            throw new IllegalStateException("status field is required");    (5)
        }

        JsonArray pets = HttpClientUtils.getMany(
                "/pet/findByStatus?status=" + status.getString(),
                configuration);                                             (6)

        JsonObject body = Json.createObjectBuilder()
                .add("pets", pets)
                .build();                                                   (7)

        Message data
                = new Message.Builder().body(body).build();                 (8)

        parameters.getEventEmitter().emitData(data);                        (9)
    }
}

The GetPetsByStatus class is an implementation of the Module interface (1). This interface specifies the execute method (2) to implement the trigger’s logic. This method takes an instance of ExecutionParameters which provides a component with data required for execution. For example, the component may retrieve its configuration (3) from these parameters.

The trigger’s author defined a field named status in component.json to let the integrator enter a status of pets he is interested in. The value of this field is available to the component from the configuration (4). Because status is required, the trigger throws an exception if the value does not exist (5). The value of the status field is encoded into the request url as a query parameter and the request is sent to the Petstore API (6). The retrieved response is an array of pets, returned as JsonArray instance. Because elastic.io platform does not support naked arrays yet, the response is wrapped into an instance of JsonObject (7).

Finally a Message is created (8) and emitted to the platform (9). Please note that you can’t emit pure JSON objects to the platform but always must create a platform message from you payload and emit this message. The emitted message will be passed by the platform to the next step of the integration flow.