Telefon Wolakpdx
Telefon Wolakpdx

Reputation: 131

How to elegantly unmarshal newExchange from JSON to POJO in AggregationStrategy

Is there an elegant way of unmarshaling a JSON to POJO in the Apache Camel using custom aggregation strategy?

My simple route takes a message from SQS, the message is JSON. This message is used as input to another service, which in turn based on the message content will let me know what is the URL of a service where the original message should be posted to.

Enrich EIP with AggregationStrategy is perfect for this. However, I cannot not figure out how to unmarshal JSON to POJO elegantly in the route. I am able to do this via ObjectMapper but this just looks ugly. Is there a better way of handling it? Some magical Camel sauce that I haven's seen yet?

public RouteBuilder route() {
    return new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("aws-sqs://" + sqsName + "?amazonSQSClient")
            .setHeader("Content-Type",simple("application/json"))
            .enrich()
            .simple("http4://localhost:8080/getUrl")
            .aggregationStrategy(new AggregationStrategy() {
                @Override
                public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
                    String aPojoStr =  newExchange.getIn().getBody(String.class);
                    ObjectMapper mapper = new ObjectMapper();
                    RestAnswererResponse responosePojo;
                    try {
                        responosePojo = mapper.readValue(aPojoStr, RestAnswererResponse.class);
                    } catch (JsonParseException e) {
                        throw new RuntimeException("Error parsing response to pojo", e);

                    } catch (JsonMappingException e) {
                        throw new RuntimeException("Error parsing response to pojo", e);
                    } catch (IOException e) {
                        throw new RuntimeException("Error parsing response to pojo", e);
                    }
                    oldExchange.getIn().setHeader("URL", responosePojo.getURL());
                    return oldExchange;
                }
            })
            .toD("http4://${header" + "URL + "}/postToAck);
        }
    };
}

EDIT: Just to clarify bit more how my route needs to work:

  1. SQS contains JSON message
  2. Contents of the JSON message needs to be posted to service which, based on the context of the original message, determines final URL (this is context specific discovery service) of where the message should be posted. The discovery service returns only the URL of the final destination.

    {"url":"somehost:port"}
    
  3. Once the final destination is retrieved form service discovery, the original message (as retrieved from SQS) will be posted to that final destination.

Upvotes: 1

Views: 3446

Answers (2)

batwad
batwad

Reputation: 3665

You can use the JSON data format to unmarshal the message, rather than messing around with ObjectMapper.

When using enrich I've found it useful to create a direct route which it calls. I find this easier to understand, mock, test and you can give it different error handling and redelivery policies.

    from("aws-sqs://" + sqsName + "?amazonSQSClient")
        .setHeader("Content-Type",simple("application/json"))
        .enrich("direct:other", strategy)
        .toD("http4://${header" + "URL + "}/postToAck);

    from("direct:other")
        .to("http4://localhost:8080/getUrl")
        .unmarshal().json(JsonLibrary.Jackson, RestAnswererResponse.class);

All the strategy needs to do is extract the URL and set the header as required.

Upvotes: 8

pvpkiran
pvpkiran

Reputation: 27068

You don't need aggregation strategy to achieve what you want. This can be done using jsonpath

 from("aws-sqs://" + sqsName + "?amazonSQSClient")
            .setHeader("Content-Type",simple("application/json"))
            .setHeader("Accept", simple("application/json"))
            .setHeader(Exchange.HTTP_METHOD, constant("GET"))
            .toD("http4://localhost:8080/getUrl")
            .transform().jsonpath("url")
            .toD("http4://${body}/postToAck");

Considering you have a field url in the json response that you get from localhost:8080/getUrl .

Lastly to use jsonpath you need this dependency.

<!-- https://mvnrepository.com/artifact/org.apache.camel/camel-jsonpath -->
  <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-jsonpath</artifactId>
      <version>yourcamelversion</version>
  </dependency>

Update :

from("aws-sqs://" + sqsName + "?amazonSQSClient")
            .setHeader("Content-Type",simple("application/json"))
            .setHeader("Accept", simple("application/json"))
            .setHeader(Exchange.HTTP_METHOD, constant("POST"))
            .toD("http4://localhost:8080/getUrl")
            .convertBodyTo(String.class)
            .unmarshal().json(JsonLibrary.Jackson, RestAnswererResponse.class)
            .setHeader("url", simple("${body.url}"))
            .setBody(simple("${body.originalBody}"))
            .toD("${header.url}" + "/postToAck");

Considering

public class RestAnswererResponse {
  String url;
  String originalBody;
}

In your http4://localhost:8080/getUrl endpoint, you should set the originalBody and then return. Something like this

class SomeController {

 @PostMapping("/getUrl")
  public RestResponse getUrl (@RequestBody String body) {
   // based on the body object decide what is the url
    return new RestResponse("url", body);// set the body it recieves as originalBody.     
}

For json unmarshalling, you would need

 <dependency>
       <groupId>org.apache.camel</groupId>
       <artifactId>camel-jackson</artifactId>
       <version>yourcamelversion</version>
  </dependency>

Upvotes: 0

Related Questions