Write an Output component

In this tutorial we will create a complete working output component for hazelcast. This will include :

  1. The component configuration and the UI layout

  2. The output that is responsible for connecting and writing data to the data source.

The component configuration

How to create component configuration has already been described in "Create an input component" tutorial. For now we will use the same component configuration. We will only add a couple of fields required for our output component to already described configuration.

@Option
private String keyAttribute;

@Option
private String valueAttribute;

We will need those fields to determine key and value attributes for our Hazelcast map.

The Output

As our output component needs to work in distributed environments it should implement Serializable interface.

Let’s take a look at the skeleton of our output component.

@Version (1)
@Icon(custom = "hazelcastOutput", value = CUSTOM) (2)
@Processor(name = "Output") (3)
public class HazelcastOutput implements Serializable {
    private final HazelcastConfiguration configuration;
    private final JsonBuilderFactory jsonFactory;
    private final Jsonb jsonb;
    private final HazelcastService service;


    public HazelcastOutput(@Option("configuration") final HazelcastConfiguration configuration,
                           final JsonBuilderFactory jsonFactory,
                           final Jsonb jsonb,
                           final HazelcastService service) {} (4)

    @PostConstruct
    public void init() {} (5)

    @PreDestroy
    public void release() {} (6)

    @ElementListener
    public void onElement(final JsonObject defaultInput) {} (7)

}
1 @Version annotation indicate the version of the component. it will be used to migrate the component configuration if needed.
2 @Icon annotation indicate the icon of the component. here we have defined a custom icon that need to be bundled in the component jar under resources/icons.
3 @Processor annotation indicate that this class is the processor(output) and give it’s name.
4 This constructor of the processor is responsible of injecting the component configuration and services. Configuration parameter are annotated by @Option. and other parameters are considered as services and will be injected by the component framework. The service may be local services (class annotated with @Service) or some services provided by the component framework.
5 The method annotated with @PostConstruct is executed once by instance and can be used to do some initialization. Here we will get the hazelcast instance according to the provided configuration.
6 The method annotated with @PreDestroy is used to clean resource at the end of the execution of the output. here we will shutdown the hazelcast instance loaded in the post Construct method.
7 Data is passed to the method annotated with @ElementListener. That method is responsible for data output. You can put all the related logic in this method.
in real implementation you can desire to bulk write the updates accordingly to groups, see Processor description

Let’s implement all methods required for our output.

The constructor

public HazelcastOutput(@Option("configuration") final HazelcastConfiguration configuration,
                       final JsonBuilderFactory jsonFactory,
                       final Jsonb jsonb,
                       final HazelcastService service) {
    this.configuration = configuration;
    this.jsonFactory = jsonFactory;
    this.jsonb = jsonb;
    this.service = service;
}

The PostConstruct method

private transient HazelcastInstance instance;
private transient IMap<Object, Object> map;

@PostConstruct
public void init() {
    instance = service.findInstance(configuration.newConfig()); (1)
    map = instance.getMap(configuration.getMapName()); (2)
}

We will need Hazelcast instance and Hazelcast map. We add those as attributes to the output. <1> Here we create an instance of hazelcast according to the provided configuration. Here you can notice that we use the injected HazelcastService instance to perform that. This service is implemented in the project. See the implementation in "Create an input component" tutorial. <2> We get the Hazelcast map according to the map name from configuration. We use Hazelcast instance for that purpose.

in production you will not want to create one instance per thread/worker but we will cover that in another coming tutorial

The PreDestroy method

@PreDestroy
public void close() {
    instance.getLifecycleService().shutdown();
    map = null;
}

We shutdown the instance that we have created in the PostConstruct and we free the Hazelcast map reference.

The ElementListener method

@ElementListener
public void onElement(final JsonObject defaultInput) { (1)
    final Object key = toValue(defaultInput.get(configuration.getKeyAttribute()));
    final Object value = toValue(defaultInput.get(configuration.getValueAttribute()));
    map.put(key, value);
}

private Object toValue(final JsonValue jsonValue) { (2)
    if (jsonValue == null) {
        return null;
    }
    if (jsonValue.getValueType() == STRING) {
        return JsonString.class.cast(jsonValue).getString();
    }
    if (jsonValue.getValueType() == NUMBER) {
        return JsonNumber.class.cast(jsonValue).doubleValue();
    }
    return jsonValue.asJsonObject();
}
1 This method will be used to pass the incoming data to our output. Every object passed should be a JsonObject instance. This method can include any logic required to write data to the data source. In our implementation we will put data to Hazelcast map.
2 This is our inner method which is used to transform incoming values in format required to put data to Hazelcast map.

The full implementation of the Output

Here is the full code source for the output to have a global view of it. Read more about output…​

@Version (1)
@Icon(custom = "hazelcastOutput", value = CUSTOM) (2)
@Processor(name = "Output") (3)
public class HazelcastOutput implements Serializable {
    private final HazelcastConfiguration configuration;
    private final JsonBuilderFactory jsonFactory;
    private final Jsonb jsonb;
    private final HazelcastService service;

    private transient HazelcastInstance instance;
    private transient IMap<Object, Object> map;

    public HazelcastOutput(@Option("configuration") final HazelcastConfiguration configuration,
                           final JsonBuilderFactory jsonFactory,
                           final Jsonb jsonb,
                           final HazelcastService service) {
        this.configuration = configuration;
        this.jsonFactory = jsonFactory;
        this.jsonb = jsonb;
        this.service = service;
    }

    @PostConstruct
    public void init() {
        instance = service.findInstance(configuration.newConfig());
        map = instance.getMap(configuration.getMapName());
    }

    @ElementListener
    public void onElement(final JsonObject defaultInput) {
        final Object key = toValue(defaultInput.get(configuration.getKeyAttribute()));
        final Object value = toValue(defaultInput.get(configuration.getValueAttribute()));
        map.put(key, value);
    }

    @PreDestroy
    public void release() {
        instance.getLifecycleService().shutdown();
        map = null;
    }

    private Object toValue(final JsonValue jsonValue) {
        if (jsonValue == null) {
            return null;
        }
        if (jsonValue.getValueType() == STRING) {
            return JsonString.class.cast(jsonValue).getString();
        }
        if (jsonValue.getValueType() == NUMBER) {
            return JsonNumber.class.cast(jsonValue).doubleValue();
        }
        return jsonValue.asJsonObject();
    }
}

We have seen how to create a complete working output in this tutorial. Later we will explain how to create some unit tests for it.

Scroll to top