Alex Oliveira
Alex Oliveira

Reputation: 922

Apache Camel REST endpoint not returning the final body

I've declared a REST endpoint, which calls another route using direct. In the end of the second route I'm logging the body but it's not the same body returned to the browser.

Here's a small example reproducing the behavior (I'm using Apache Camel with Spring Boot):

@Component
public class EntidadeRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        restConfiguration().bindingMode(json);

        rest("/entidade")
            .get("/{id}")
                .to("direct:search-by-id");

        from("direct:search-by-id")
            .routeId("search-by-id")
            .setProperty("idEntidade", simple("${header.id}"))
            .pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
            .unmarshal().json(JsonLibrary.Jackson)
            .split(jsonpath("$"))
            .filter(jsonpath("$[?(@.id == ${property.idEntidade})]"))
            .marshal().json(JsonLibrary.Jackson)
            .log("${body}");
    }
}

I'm calling it from the browser, using the URL: http://localhost:8080/camel/entidade/1 .

On the folder files/mock/dados I have a single file, called entidades.json where there's a JSON Array (see below).

I know the split and filter are working because I'm logging the body in that last line of code and this is what appears in the log:

2021-04-28 18:15:15.707 INFO 3905542 --- [nio-8080-exec-1] search-by-id : {"id":"1","tipo":"PF","nome":"João Maria"}

But this is what is returned to the browser (the exact content of the entidades.json file):

[{"id":"1","tipo":"PF","nome":"João Maria"},{"id":"2","tipo":"PF","nome":"Maria João"},{"id":"3","tipo":"PF","nome":"João Silva"},{"id":"4","tipo":"PF","nome":"José Souza"}]

Why the logged body is not the same that shows in the browser and to fix it?

PS: If I remove those marshal and unmarshal calls, I get the following error in the browser:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.apache.camel.component.file.FileBinding and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.apache.camel.component.file.GenericFile["binding"])

Upvotes: 0

Views: 2298

Answers (2)

hc_dev
hc_dev

Reputation: 9418

Without the input-file and knowing the concrete URI you called (including given value for path-variable {id}), I can only suspect some issues as follows:

  • Did you provide an id at the end of the GET-call ?
  • Why did you convert the JSON ?
  • Did you test the correct split ? Did you aggregate again ?
  • Did you want to log each message ?

REST endpoints

You specified the endpoint as GET /entidada/{id}. The {id} is a path-variable.

So let's assume you call GET /entidata/1 with 1 as ID. Then your JSON file is polled (read), unmarshalled to ... ?

JSON unmarshal/marshal

These unmarshal and marshal methods are either used to convert between different data-formats or from data-representation to Java-objects (POJOs) if they are internally used (e.g. to pass to a processor Bean etc.).

I suppose the file dados contains textual data as JSON. So you can simply read this text into a (textual!) message body (like a text-message, compare JMS etc.) and work with it: (a) split by JSON-path, (b) filter by JSON-path, (c) log this JSON result, (d) send it back as HTTP-response to the calling client (e.g. your browser).

What happens on the split?

After this you try to split (assuming you have a JSON-array):

//  incoming:
//  a single enriched message, in body: JSON-array with 4 elements 
.split(jsonpath("$")) // what do you expect as output ?
// here the split usually should be ended using `.end` DSL method
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]")) // each array element of JSON body matched against id, e.g. 1

I filled your HTTP-response (JSON array with the 4 people) into online JSON-path evaluator. Evaluation of $ was not a split, but a single element (inside the result array): exactly the original array (with 4 people).


  • Why wasn't the file splitted into 4 messages, each containing a person-object?
  • Because your JSON-path $ denotes simply the root-element.

Plus: Usually after the .split() there follows an .end() which aggregates them again.

You left that out. I suppose that is an issue, too.


Filter works as expected

Later you filtered on the REST-given id:

.filter(jsonpath("$[?(@.id == ${property.idEntidade})]"))`

This results in the logged element:

{"id":"1","tipo":"PF","nome":"João Maria"}

The filter worked successfully: just leaving a single one with id 1.

Logging in Camel

Log EIP

When adding .log() to the route chain, this means you are using the Log EIP. In its documentation a warning Tip is given:

Logging message body with streamed messages:

If the message body is stream based, then logging the message body, may cause the message body to be empty afterwards. See this FAQ. For streamed messages you can use Stream caching to allow logging the message body and be able to read the message body afterwards again.

So your empty log message may be caused by a side-effect when using this for logging stream-based message bodies.

Difference of log methods

Explained in section Difference between log in the DSL and Log component:

The log DSL is much lighter and meant for logging human logs such as Starting to do …​ etc.

Below example (adjusted to your REST route) illustrates its usage:

rest("/entidade")
    .get("/{id}")
    .log("Processing ${id}")
    .to("bean:foo");

Log component

I would suggest using the standard Log component by simply using .to() DSL passing a URI string based on schema log: together with the required parameter loggerName.

.to("log:filtered-json")

Here the URI-prefix for Log component is log:. Each message of the stream is logged using loggerName filtered-json.

Upvotes: 1

Alex Oliveira
Alex Oliveira

Reputation: 922

The error was I need to pass an AggregationStrategy to the split. I also need to stop logging the body, because it was consuming the InputStream. After that, I could safely remove those marshal and unmarshal calls.

This is the final code:

@Component
public class EntidadeRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        restConfiguration().bindingMode(json);

        rest("/entidade")
            .get("/{id}")
                .to("direct:search-by-id");

        from("direct:search-by-id")
            .routeId("search-by-id")
            .setProperty("idEntidade", simple("${header.id}"))
            .pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
            .split(jsonpath("$"), takeFirst(Exchange.FILTER_MATCHED))
                .filter(jsonpath("$[?(@.id == ${property.idEntidade})]")).stop()
            .end();
    }

    private AggregationStrategy takeFirst(String matchProperty) {
        return ((oldExchange, newExchange) -> {
            if (oldExchange == null && newExchange.getProperty(matchProperty, Boolean.class)) {
                oldExchange = newExchange;
            }
            return oldExchange;
        });
    }
}

Upvotes: 1

Related Questions