Skip to main content
A connector is a Camel-K integration packaged as a Quarkus app and a Helm chart. The execution pattern for every operation is the same:
direct://<operationId> route -> validate -> transform -> call vendor -> transform -> respond
You implement the body of each direct://<operationId> route. Each operationId is one of the operations declared in the Grand Central OpenAPI spec the connector implements; the OpenAPI plugin auto-wires the inbound REST DSL to those direct:// routes. The route then calls the vendor through a kamelet, almost always gc-http-caller for REST.

The skeleton every connector has

Every connector class extends RouteBuilder, declares its kamelet URIs and property names as constants, registers the header-removal policy and GrandCentralUtil bean, then defines one direct://<operationId> per OpenAPI operation.
import com.backbase.grandcentral.sdk.policy.HeaderRemovalPolicy;
import com.backbase.grandcentral.sdk.util.GrandCentralUtil;
import org.apache.camel.LoggingLevel;
import org.apache.camel.PropertyInject;
import org.apache.camel.builder.RouteBuilder;

public class {Vendor}{Domain}Connector extends RouteBuilder {

    private static final String KAMELET_HTTP_GET =
        "kamelet:gc-http-caller?method=GET&gc-http-caller-retry={{retryFlag}}";
    private static final String KAMELET_HTTP_POST =
        "kamelet:gc-http-caller?method=POST&gc-http-caller-retry={{retryFlag}}";
    private static final String KAMELET_HTTP_PUT =
        "kamelet:gc-http-caller?method=PUT&gc-http-caller-retry={{retryFlag}}";
    private static final String KAMELET_HTTP_DELETE =
        "kamelet:gc-http-caller?method=DELETE&gc-http-caller-retry={{retryFlag}}";

    private static final String PROP_HTTP_URL   = "gc-http-caller-url";
    private static final String PROP_SAVED_BODY = "savedBody";

    @PropertyInject(value = "optional.feature.flag", defaultValue = "false")
    private String optionalFlag;

    @Override
    public void configure() {
        getContext().addRoutePolicyFactory((camelContext, routeId, route) -> {
            String headersToRetain = camelContext.resolvePropertyPlaceholders("{{acceptedHeaders}}");
            return HeaderRemovalPolicy.removeAllExcept(headersToRetain.split(","));
        });

        getContext().getRegistry().bind("grandCentralUtil", new GrandCentralUtil());

        // One direct://<operationId> per OpenAPI operation goes here.
    }
}

REST/JSON operation patterns

These are the canonical shapes of two of the most common HTTP operations. Both assume JSON in and JSON out, and use the gc-http-caller kamelet for the vendor call.

GET with query parameters

from("direct://getEntities")
    .log(LoggingLevel.INFO, "Start getEntities")
    .to("json-validator:{{?resourcesBasePath}}get-entities-schema-validation.json")
    .process(exchange -> {
        Map<String, Object> params = new ObjectMapper()
            .readValue(exchange.getIn().getBody(String.class), Map.class);
        exchange.setProperty("gc-http-caller-queryParams", params);
    })
    .setProperty(PROP_HTTP_URL, simple("{{vendor.base.url}}/entities"))
    .to(KAMELET_HTTP_GET)
    .to("jolt:{{?resourcesBasePath}}get-entities-response-transformation.json"
        + "?transformDsl=Chainr&inputType=JsonString&outputType=JsonString")
    .log(LoggingLevel.INFO, "End getEntities");

POST to create a resource

from("direct://createEntity")
    .log(LoggingLevel.INFO, "Start createEntity")
    .to("json-validator:{{?resourcesBasePath}}create-entity-schema-validation.json")
    .to("jolt:{{?resourcesBasePath}}create-entity-request-transformation.json"
        + "?transformDsl=Chainr&inputType=JsonString&outputType=JsonString")
    .setProperty(PROP_HTTP_URL, simple("{{vendor.base.url}}/entities"))
    .to(KAMELET_HTTP_POST)
    .to("jolt:{{?resourcesBasePath}}create-entity-response-transformation.json"
        + "?transformDsl=Chainr&inputType=JsonString&outputType=JsonString")
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(HttpStatus.SC_CREATED))
    .log(LoggingLevel.INFO, "End createEntity");

SOAP/XML anatomy at a glance

When the vendor exposes a SOAP/WSDL API, the connector marshals JSON to XML, applies an XSLT, calls the vendor through the vendor SDK kamelet, transforms the SOAP response back to JSON, and runs a type-coercion processor.
public static final String DIRECT_XML_TO_JSON = "direct:xmlToJson";
public static final String DIRECT_JSON_TO_XML = "direct:jsonToXml";

private static final String VENDOR_PROPERTIES_KAMELET =
    "kamelet:{vendor}-properties?loggingFeatureEnabled={{?cxf.loggingFeatureEnabled}}";
private static final String VENDOR_API_CALLER_KAMELET =
    "kamelet:{vendor}-api-caller?retry={{retryFlag}}";

// Shared converters (define once, reuse)
JacksonDataFormat jacksonDataFormat = new JacksonDataFormat();
jacksonDataFormat.setObjectMapper(new ObjectMapper().registerModule(
    new SimpleModule().addSerializer(Object.class, new RemoveNullOrEmptyJsonSerializer())));

from(DIRECT_XML_TO_JSON).unmarshal().jacksonXml().marshal(jacksonDataFormat);
from(DIRECT_JSON_TO_XML).unmarshal().json().marshal().jacksonXml();

from("direct://createEntity")
    .to("json-validator:{{?resourcesBasePath}}create-entity-schema-validation.json")
    .to(VENDOR_PROPERTIES_KAMELET)
    .to(DIRECT_JSON_TO_XML)
    .to("xslt:{{?resourcesBasePath}}create-entity-request-transformation.xslt")
    .to(VENDOR_API_CALLER_KAMELET)
    .to("xslt:{{?resourcesBasePath}}create-entity-response-transformation.xslt")
    .to(DIRECT_XML_TO_JSON)
    .process(GrandCentralUtil::correctNumericAndBooleanValuesInJsonFromProperties)
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(HttpStatus.SC_CREATED));
Run correctNumericAndBooleanValuesInJsonFromProperties after xmlToJson because Jackson’s XML deserializer renders every value as a string. The processor coerces numbers and booleans back, honoring typeConversion.skipAttributes.

Event / Generic anatomy at a glance

Use this style to pull from a third-party REST API, transform and validate, and push to the Grand Central ASB producer.
from("direct://postInboundEvent")
    .setProperty("eventReference", jsonpath("$.reference"))
    .setProperty("gc-http-caller-url",
        simple("{{api.thirdparty.url}}?symbol=").append(jsonpath("$.symbol")))
    .to("kamelet:gc-http-caller?method=GET")
    .to("jolt:{{?resourcesBasePath}}inbound-event-transformer.json"
        + "?inputType=JsonString&outputType=JsonString")
    .to("json-validator:{{?resourcesBasePath}}event-schema.json")
    .setProperty("gc-http-caller-url",
        simple("{{asb.producer.apiUri}}/{{asb.event.topic}}/publish"))
    .to("kamelet:gc-http-caller?method=POST")
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(HttpStatus.SC_NO_CONTENT));

Next step

For the kamelet that handles every outbound REST call, see Call REST endpoints.