3
votes

I have a springboot application with apache camel. In it I have a camel-context. I am trying to send json through curl with a key pair value and processing it through a route.

Sending the data:

curl --header "Content-Type: application/json" -X POST -d '{"msgId=EB2C7265-EF68-4F8F-A709-BEE2C52E842B", "ticket":"ERR001"}' http://lcalhost:8888/api/erroradmin

Camel-context.xml:

<?xml version="1.0" encoding="utf-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd         http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
        <camelContext xmlns="http://camel.apache.org/schema/spring" useMDCLogging="true">
            <properties>
                <property key="CamelLogEipName" value="ThisLogger"/>
            </properties>
            <dataFormats>
                <!-- here we define a Json data format with the id jack and that it should use the TestPojo as the class type when
                     doing unmarshal. The unmarshalTypeName is optional, if not provided Camel will use a Map as the type -->
                <json id="jack" library="Jackson" unmarshalTypeName="java.util.HashMap"/>
            </dataFormats>

            <restConfiguration component="jetty" port="8888" bindingMode="json">
                <dataFormatProperty key="prettyPrint" value="true"/>
            </restConfiguration>

            <rest path="/api/erroradmin">
                <get uri="{id}">
                    <to uri="direct:processErrorAdminGet"/>
                </get>
                <post>
                    <to uri="direct:processErrorAdminPost"/>
                </post>
            </rest>

            <route id="processErrorAdminPost">
                <from uri="direct:processErrorAdminPost"/>
                <log message="Route(processErrorAdminPost): ${body}"/>
                <unmarshal>
                    <custom ref="jack"/>
                </unmarshal>
                <log message="Route(processErrorAdminPost): ${body}"/>

            </route>
        </camelContext>
    </beans>

I am getting the following Stacktrace:

org.apache.camel.InvalidPayloadException: No body available of type: java.io.InputStream but has value: {msgId=D507B9EE-176D-4F3C-88E7-9E36CC2B9731, ticket=ERR001} of type: java.util.LinkedHashMap on: HttpMessage@0x28c1a31a. Caused by: No type converter available to convert from type: java.util.LinkedHashMap to the required type: java.io.InputStream with value {msgId=D507B9EE-176D-4F3C-88E7-9E36CC2B9731, ticket=ERR001}. Exchange[09395660-c947-47f1-b00f-d0d3030a39d1]. Caused by: [org.apache.camel.NoTypeConversionAvailableException - No type converter available to convert from type: java.util.LinkedHashMap to the required type: java.io.InputStream with value {msgId=D507B9EE-176D-4F3C-88E7-9E36CC2B9731, ticket=ERR001}]

2

2 Answers

3
votes

Welcome to Stackoverflow! I strongly believe the mention of bindingMode="json" at Line 13, is the root cause of this failure. The manual says

When using binding you must also configure what POJO type to map to. This is mandatory for incoming messages, and optional for outgoing.

I am really scared of the XML DSL but, here is an approximate equivalent rest DSL in Java.

@Component
@Slf4j
public class MySpringBootRouter extends RouteBuilder {

    @Override
    public void configure() {

        restConfiguration()
                .component("undertow")
                .host("127.0.0.1")
                .port(9090)
                //This works only when a POJO mapping is possible - Ref: https://camel.apache.org/manual/latest/rest-dsl.html
                //<quote>When using binding you must also configure what POJO type to map to. This is mandatory for incoming messages, and optional for outgoing.</quote>
                //.bindingMode(RestBindingMode.json)
                .dataFormatProperty("prettyPrint", "true");


        rest("/api")
                .post("/erroradmin")
                .to("direct:postError")

                .get("/erroradmin/{id}").to("direct:getError");

        from("direct:getError")
                .process(exchange -> {
                    exchange.getMessage().setBody(("{'messageID':'" + UUID.randomUUID().toString() + "','ticketID':'1234'}"));
                });

        from("direct:postError")
                .unmarshal()
                .json(JsonLibrary.Jackson)
                .process(exchange -> {
                    log.info("Type of incoming body:{}", exchange.getIn().getBody().getClass().getName());
                    log.info("Incoming body:{}", exchange.getIn().getBody());
                }).transform().constant("{'httpResponse:200':'OK'}");
    }

}

Once it is up, and I send the payload with cURL as below

curl -d '{"msgId":"EB2C7265-EF68-4F8F-A709-BEE2C52E842B", "ticket":"ERR001"}' -H "Content-Type: application/json" -X POST http://localh
ost:9090/api/erroradmin

You'll see something like the below in the logs

2020-02-18 11:44:13.032  INFO 2708 --- [  XNIO-1 task-4] o.a.c.community.so.MySpringBootRouter    : Type of incoming body:java.util.LinkedHashMap
2020-02-18 11:44:13.032  INFO 2708 --- [  XNIO-1 task-4] o.a.c.community.so.MySpringBootRouter    : Incoming body:{msgId=EB2C7265-EF68-4F8F-A709-BEE2C52E842B, ticket=ERR001}

Oh btw, your original JSON payload was malformed. The entire Java project is available here, if you wish to play around


Edit: Additional header processing step

        from("direct:postError")
                .unmarshal()
                .json(JsonLibrary.Jackson)
                .process(exchange -> {
                    LinkedHashMap map = exchange.getIn().getBody(LinkedHashMap.class);
                    map.keySet().stream().forEach( item -> exchange.getIn().setHeader(item.toString(),map.get(item)));
                })
//This step is just to print the Headers. Doesnt do anything useful
                .process( exchange -> {
                    log.info(String.valueOf(exchange.getIn().getHeaders()));
                })
                .transform().constant("{'httpResponse:200':'OK'}");
1
votes

After searching and reading for a while, I have found the solution.

The way to do this is by using JsonPath. Now in Java DSL there are many examples, but in XML DSL, there is not much. I finally found a working example.

My camel context looks now like this:

<camelContext xmlns="http://camel.apache.org/schema/spring" useMDCLogging="true" streamCache="true">
    <properties>
        <property key="CamelLogEipName" value="SomeLogger"/>
    </properties>

     <dataFormats>
            <json id="json" library="Jackson"/>     
     </dataFormats>

    <restConfiguration component="jetty" port="9090" >
        <dataFormatProperty key="prettyPrint" value="true"/>
    </restConfiguration>

    <rest path="/api/erroradmin">
        <post>
            <to uri="direct:error" />
        </post>
    </rest>

    <route id="error">
        <from uri="direct:error"/>
        <log message="Route(error): ${body}"/>

        <setHeader headerName="errMessageId">
            <jsonpath suppressExceptions="true">$[0].msgId</jsonpath>
        </setHeader>
        <setHeader headerName="errTicket">
            <jsonpath suppressExceptions="true">$[0].ticket</jsonpath>
        </setHeader>               
        <setHeader headerName="errHandled">
            <jsonpath suppressExceptions="true">$[0].handled</jsonpath>
        </setHeader>

        <log message="Route(error): Header name: errMessageId -> ${header[errMessageId]}"/>
        <log message="Route(error): Header name: errTicket -> ${header[errTicket]}"/>
        <log message="Route(error): Header name: errHandled -> ${header[errHandled]}"/>
    </route>
</camelContext>

When accessing the key of the corresponding node I get the value in my newly set header.

The JSON is sent in like this: So you send in:

curl -XPOST http://localhost:9090/api/erroradmin -d '[{"var1": 10,"var2": 20}]' --header "Content-Type: application/json"

The dependencies I am using:

    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-jackson</artifactId>
        <version>${camel.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-jsonpath</artifactId>
        <version>${camel.version}</version>
    </dependency>
  </dependencies>

Hope this benefits someone in the future!

EDIT: Make sure to use camel 2.25.0 and higher. Apparently when using camel-json 2.24.1 along with same core version, the camel-json will download an outdated dependency called json-smart, which misses some classes for JsonPath to work correctly.