Apache Camel is likely the most powerful open source integration solution you can find out on the market. It enables you to create very simply "routes" which are literally integration pipelines. However in current version it does not integrate natively with JSON-B (the "javax" JSON-Java binding).

The main reason you will want to integrate with JSON-B , instead of using one of the existing JSON integrations Camel has, is to reuse some existing model developped in JSON-B or just to drop some dependencies you don't want and slim down your deployment.

As a quick reminder, Camel design is based on exchanges which have an in and out payloads (generally the in payload is propagated to the out one and at the next stage the out becomes the in side). Each step of a route will process the exchange to create a new one - or propagate the incoming one. In this context, a particular type of processing is the (un)marshalling. This is why Camel has a particular abstraction for that concern. Thanks to a registry of DataFormats, Camel can serialize and deserialize any payload very easily.

Before showing you a potential implementation, please note that we will also give an alias to our implementation thanks to the API DataFormatName and enable to manage a dedicated JSON-B instance thanks to ServiceSupport which will enable us to release the instance when the CamelContext will be closed. Here is the implementation of the JSON-B DataFormat:

package com.github.rmannibucau.camel.jsonb;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.json.JsonObject;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;

import org.apache.camel.Exchange;
import org.apache.camel.model.dataformat.JsonDataFormat;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.support.ServiceSupport;

public class JsonbDataFormat extends ServiceSupport implements DataFormat, DataFormatName {
    public static final String UNMARSHALLING_TYPE_HEADER = JsonDataFormat.class.getName() + ".type";

    private final Jsonb jsonb;
    private final boolean closeJsonb;
    private final ConcurrentMap<String, Class<?>> models = new ConcurrentHashMap<>();

    // <1>
    private JsonbDataFormat(final Jsonb jsonb, final boolean closeJsonb) {
        this.jsonb = jsonb;
        this.closeJsonb = closeJsonb;
    }

    // <2>
    public JsonbDataFormat(final Jsonb jsonb) {
        this(jsonb, false);
    }

    // <3>
    public JsonbDataFormat() {
        this(JsonbBuilder.create(), true);
    }

    @Override
    protected void doStart() {
        // no-op
    }

    // <4>
    @Override
    protected void doStop() throws Exception {
        if (closeJsonb) {
            jsonb.close();
        }
    }

    // <5>
    @Override
    public String getDataFormatName() {
        return "jsonb";
    }

    // <6>
    @Override
    public void marshal(final Exchange exchange, final Object graph, final OutputStream stream) {
        jsonb.toJson(graph, stream);
    }

    // <6>
    @Override
    public Object unmarshal(final Exchange exchange, final InputStream stream) {
        return jsonb.fromJson(stream, getModel(exchange));
    }

    // <7>
    private Class<?> getModel(final Exchange exchange) {
        final String typeName = exchange.getIn().getHeader(UNMARSHALLING_TYPE_HEADER, String.class);
        if (typeName == null) { // default
            return JsonObject.class;
        }
        return models.computeIfAbsent(typeName, k -> {
            try {
                return Thread.currentThread().getContextClassLoader().loadClass(k);
            } catch (final ClassNotFoundException e) {
                throw new IllegalArgumentException(e);
            }
        });
    }
}
  1. This internal constructor enables us to initialize the JSON-B instance but also to store if we must manage it (close it) or if it is provided by the user and not be closed by the data format instance,
  2. this constructor can be used by the user to inject its own instance of JSON-B, this is common when the deployment also has some rest endpoints using another instance (memory optimization) or to customize the instance with specific type adapters or configuration (null etc),
  3. this is the default constructor used when the default JSON-B modelling can be used, we will see very soon how it is also used by the default camel integration,
  4. we use the ServiceSupport#doStop to close the JSON-B instance when needed,
  5. we name the data format "jsonb" to enable us to use this string instead of the class in our routes,
  6. we map the (un)marshalling of JSON-B in camel data format API,
  7. finally, to unmarshall a model, JSON-B requires a Class and to pass it we use a camel header (UNMARSHALLING_TYPE_HEADER) we defined in our data format which can be set to specify the type to use, if not set we will default on JsonObject.

At that point, the format is usable but is not implicitly usable. To make it usable automatically - with our default constructor - by Camel without requiring to register the instance in the camel registry, we will put its class name in a file called META-INF/services/org/apache/camel/dataformat/jsonb:

class=com.github.rmannibucau.camel.jsonb.JsonbDataFormat

With this, Camel will look this format, as a fallback with "jsonb" format will be requested and not found in the Camel registry.

From now on, we can unmarshall a payload in a route as simply as this:

from("rest:get:/hello")
    .id("unmarshall_route")
    .setHeader(JsonbDataFormat.UNMARSHALLING_TYPE_HEADER, constant(MyEntity.class.getName()))
    .unmarshal("jsonb")
    .to("jpa:" + MyEntity.class.getName());

And do the opposite even simpler:

from("jpa:" + MyEntity.class.getName() + "?consumer.namedQuery=MyEntity.findAll")
    .id("marshall_route")
    .marshall("jsonb")
    .to("rest:post:/send-list");

Simple and enables you to not redevelop an existing mapping/binding :)

 

From the same author:

In the same category: