3

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}]

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136
DevelopperX
  • 85
  • 1
  • 2
  • 9

2 Answers2

3

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'}");
ShellDragon
  • 1,712
  • 2
  • 12
  • 24
  • Hello @ShellDragon, I have tried to translate this into XML DSL, we work with XML DSL routes, since not all have full Java knowledge. I managed to receive the JSON object and log the body to stdout. And it shows what you the output like yours, which is fine. I want to retrieve the values of the key's and put them in header elements, something [The value of msgId] [The value of ticket] I appreciate your time and help! Thank you. – DevelopperX Feb 18 '20 at 14:30
  • There may not be such a functionality built in, at least not to my knowledge. You may do that transformation in a custom `.process()` step – ShellDragon Feb 18 '20 at 15:18
  • Ive aded additionam code at the end of original answer. – ShellDragon Feb 18 '20 at 15:27
  • I really appreciate all the effort. I wil se if I can use the information. By the way, I tried undertow, but for some reason, of which I didn't find out yet, it didn't work. I have a deadline to make this work. If I can't manage to process the json into headers, I think I will write a simple bean that does it instead. Thank you – DevelopperX Feb 19 '20 at 09:33
  • Undertow is not a hard requirement, [any one of these](https://camel.apache.org/manual/latest/rest-dsl.html#_components_supporting_rest_dsl) should do the job. – ShellDragon Feb 19 '20 at 09:48
  • Additionally, refer to [Camel examples repo](https://github.com/apache/camel-examples/tree/master/examples) as well – ShellDragon Feb 19 '20 at 09:53
2

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.

DevelopperX
  • 85
  • 1
  • 2
  • 9