Skip to main content
Every operation validates the inbound request against a JSON Schema, then transforms the canonical Grand Central payload into the vendor’s shape and the vendor’s response back into the Grand Central shape. Use JOLT for JSON, XSLT for XML, and lift value-to-value substitutions out of the transformation files into properties.

Validate the request schema

Every request-bearing operation gets a JSON schema in src/main/resources/<operation>-schema-validation.json, applied at the very start of the route:
.to("json-validator:{{?resourcesBasePath}}create-entity-schema-validation.json")
If validation fails, kamelet:json-validation-exception-handler catches the resulting JsonValidationException and returns a Grand Central-shaped error response.

Convert an OpenAPI spec to JSON Schema

To generate a JSON Schema file from an OpenAPI spec, follow these steps:
  1. Install the conversion tool:
    npm install -g yaml-to-json-schema
    
  2. Run the following command to generate a JSON Schema for all schemas in the OpenAPI spec:
    npx yaml-to-json-schema {path-to-openapi-spec-doc}.yaml > {json-schema-file-name}.json
    
  3. In the generated schema file, prefix a / at the beginning of all reference paths in $ref values. Example: Change "$ref": "#definitions/Address" to "$ref": "#/definitions/Address".
A typical Draft-07 schema skeleton looks like this:
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "CreateEntity",
  "type": "object",
  "required": ["requiredField"],
  "properties": {
    "requiredField": { "type": "string", "maxLength": 255 },
    "enumField":     { "enum": ["VALUE_A", "VALUE_B"] },
    "numericField":  { "type": "number" }
  }
}

JOLT for REST/JSON connectors

JOLT is a JSON-to-JSON declarative transformation language. Use it to map the Grand Central canonical request and response shape to the vendor shape and back. The most common route call takes a JSON String in and out:
.to("jolt:{{?resourcesBasePath}}create-entity-request-transformation.json"
    + "?transformDsl=Chainr&inputType=JsonString&outputType=JsonString")
When the body is already a Java Map (for example, mid-Splitter), use Hydrated input:
.to("jolt:{{?resourcesBasePath}}secondary-item-transformation.json"
    + "?transformDsl=Chainr&inputType=Hydrated&outputType=JsonString")
Chainr spec structure:
[
  { "operation": "shift",   "spec": { /* ... */ } },
  { "operation": "default", "spec": { /* ... */ } },
  { "operation": "remove",  "spec": { /* ... */ } }
]

XSLT for SOAP/XML connectors

XSLT 3.0 produces the SOAP envelope on the request side and reshapes the SOAP response into the Grand Central canonical JSON-friendly XML on the response side. Route call:
.to("xslt:{{?resourcesBasePath}}get-entity-request-transformation.xslt")

// Dynamic XSLT path: use toD when the file name is computed at runtime
.toD("xslt:{{?resourcesBasePath}}${exchangeProperty.xsltFile}-transformation.xslt")
When the XSLT builds the full request from headers and properties without JSON input:
.setBody(simple(EMPTY_ROOT_XML_TAG))    // "<root></root>"
.to("xslt:{{?resourcesBasePath}}request.xslt")
Access Camel headers from inside an XSLT by declaring them as parameters; Camel injects matching header values:
<xsl:param name="entityId"/>
<xsl:param name="currentDateTime"/>
XSLT tips:
  • Always declare the vendor namespace at the top (xmlns:ns="..."). XPath without the prefix matches nothing.
  • Use omit-xml-declaration="yes" on the root xsl:output so the route can pipe the XML cleanly into direct:xmlToJson.
  • To force a JSON array on a single element, emit a paired empty-and-populated element (for example, <amounts/><amounts>...</amounts>). The RemoveNullOrEmptyJsonSerializer configured on the JSON marshaller drops the empty placeholder, leaving an array.
  • Wrap optional sections in xsl:if to avoid emitting empty objects.

Dynamic field translation through properties

For straight value-to-value substitutions (for example, mapping a vendor code like SAV to a Grand Central value like savings-account), avoid embedding if/else logic inside JOLT or XSLT. The SDK ships two helpers that pull the mappings straight from application.properties. The helpers keep transformation files focused on structure, remove a major source of duplication across operations, and make every value mapping visible in one place. Pick the helper bean call that matches the body type:
// JSON bodies
.bean(GrandCentralUtil.class, "replaceValuesForJson(${exchange})")

// XML bodies
.bean(GrandCentralUtil.class, "replaceValuesForXML(${exchange})")
Both helpers read the fieldsToBeReplaced property and its related entries from the connector’s properties, then apply the substitutions in place. Simple mapping uses one field per row with comma-separated field names:
fieldsToBeReplaced=MajorAccountTypeCode,MinorAccountTypeCode
MajorAccountTypeCode.SAV=kind4
MinorAccountTypeCode.SAV=savings-account
Composite mapping handles cases where a unique target value depends on a combination of fields. Use @ as the delimiter:
fieldsToBeReplaced=MajorAccountTypeCode@MinorAccountTypeCode
MajorAccountTypeCode.SAV@MinorAccountTypeCode.SAV=kind3@savings-account
Override the XML namespace when the field lives outside the default namespace:
namespaceURI.RtxnTypeCode=http://schemas.example.com/types
RtxnTypeCode.DEP=DEPOSIT
RtxnTypeCode.WTH=WITHDRAW
Shared lookups across operations handle cases where different operations expose the same logical field under different element names. Redirect them to a common lookup that defines the mapping table once:
mappingField.MajorAccountType=MajorAccountTypeCode
Minimum BOM versions for this feature:
Camel-K runtimeMinimum grandcentral-bom version
Pre-2.62.2.119
2.62.3.17

JSON and XML marshaling for SOAP

Define two helper routes once and reuse them across every SOAP operation:
JacksonDataFormat jacksonDataFormat = new JacksonDataFormat();
jacksonDataFormat.setObjectMapper(new ObjectMapper().registerModule(
    new SimpleModule().addSerializer(Object.class, new RemoveNullOrEmptyJsonSerializer())));

from("direct:xmlToJson").unmarshal().jacksonXml().marshal(jacksonDataFormat);
from("direct:jsonToXml").unmarshal().json().marshal().jacksonXml();
RemoveNullOrEmptyJsonSerializer strips nulls and empty strings or objects on serialization. The serializer makes the array trick in XSLT work and keeps response payloads tidy. After every direct:xmlToJson, run the type-coercion processor to fix string-to-number/boolean coercion from Jackson’s XML deserializer:
.process(GrandCentralUtil::correctNumericAndBooleanValuesInJsonFromProperties)
It uses the comma-separated typeConversion.skipAttributes property to decide which fields stay strings, typically identifiers.

Input validation with PredicatesValidatingProcessor

Use this for header or format validation that should produce a Grand Central error response on failure.
.process(new PredicatesValidatingProcessor(Map.ofEntries(
    Map.entry(
        header("entityId").regex("^[A-Z0-9]{8,16}$"),
        new ErrorMessage("entityId: must be 8-16 uppercase alphanumeric characters")
    ),
    Map.entry(
        header("entityType").in("TYPE_A", "TYPE_B"),
        new ErrorMessage("entityType must be TYPE_A or TYPE_B")
    )
)))

Common GrandCentralUtil inline helpers

A handful of bean(...) calls come up over and over:
.bean(GrandCentralUtil.class,
    "appendAttribute(${body}, attributeName, ${header.someHeader})")

.bean(GrandCentralUtil.class,
    "appendJsonNode(${body}, 'status', '\"OK\"')")

.bean(GrandCentralUtil.class,
    "appendAttributeToNewObject(${body}, attributeName, ${header.value})")

.bean(GrandCentralUtil.class,
    "appendMultipleAttributes(${body}, ${exchangeProperty.attrMap})")

.bean(GrandCentralUtil.class,
    "convertHeadersToJsonBody(${exchange},'header1:header2:header3')")

// SOAP only: coerce numeric/boolean values after xmlToJson
.process(GrandCentralUtil::correctNumericAndBooleanValuesInJsonFromProperties)

// SOAP only: append XML attributes from headers into a DOM
.bean(GrandCentralUtil.class,
    "appendXMLAttributesFromHeader(${exchange},'header1:header2','parentTagName')")

Next step

For the Camel and HTTP properties that drive these transformations at runtime, see Configure properties.