1
votes

I have a scenario that I'm using camel-restlet component to receive post requests, I'm forwarding these requests to an external web service, after receiving the response code from the external service, I need to add this response code to my own response to the client asynchronously.

Im trying to save the response object to a hashMap where key is an unique serial number generated based on the request content, once upon receiving the response from external web service, I can retrieve the response object from the hashMap using this unique key. Seems like restlet saves the response to exchange.getOut() message and sends back to the client synchronously which is not something I want. Not setting an out message would give me a nullPointerException.

route Class:

public class ReceiveRoute extends RouteBuilder {

@Override
public void configure() throws Exception {

    from("restlet:http://localhost:8083/api/atmp?restletMethod=post")
        .to("activemq:queue:requestReceiveQueue");  

    from("activemq:queue:requestReceiveQueue")
        .process(new RequestProcessor())
        .to("activemq:queue:requestSendQueue");

    from("activemq:queue:requestSendQueue")
        .setHeader(Exchange.HTTP_METHOD, constant("POST"))
        .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
            .to("jetty:http://localhost:8080/rest_api_demo/api/restService")
            .bean("responseProcessor"); 
    }
}

requestProcessor class:

public class RequestProcessor implements Processor {

@Override
public void process(Exchange exchange) throws Exception {
    Message message = exchange.getIn();
    byte[] bytes = (byte[])message.getBody(); 
    String body = new String(bytes);

    String atmpId = GUIDGenerator.generateAtmpSerialNumber(); 
    String terIndentifier = GUIDGenerator.generateTerminalIdentifier(body);
    MapLookupHelper.insertResponse(atmpId, terIndentifier, exchange);

    Map<String, Object> messageMap = new HashMap<String, Object>();
    messageMap = FormatUtil.parseJson(body); 
    messageMap.put("ATMPId", atmpId);
    exchange.getIn().setBody(messageMap.toString());    
  }
}

responseProcessor class

@Component
public class ResponseProcessor implements Processor {

@Override
public void process(Exchange exchange) throws Exception {
    Message in = exchange.getIn();
    String responseCode = in.getHeader(Exchange.HTTP_RESPONSE_CODE).toString();
    String body = in.getBody().toString(); 
    Map<String, Object> resMap = new HashMap<String, Object>(); 

    resMap = FormatUtil.parseJson(body);
    String atmpId = resMap.get("ATMPId").toString();
    Exchange ex = MapLookupHelper.getOutMessage(atmpId);

    ex.getOut().setHeader("HostResponseCode", responseCode);
    ex.getOut().setBody(resMap.toString());
  }
}

I'm new to Apache Camel and would like to know if restlet is the right way to go, if not, any suggestion on how I can handle async responses to client in Camel? Is AsyncProcessor only solution to such scenario?

1

1 Answers

0
votes

I think it's not issue of restlet. Your exchange pattern is InOut, that's why all jms-endpoint's waiting synchronously result of your .bean("responseProcessor"). Even if you change pattern to InOnly your client will not receive response asynchronously. I think you should make another route's architecture, like below:

from("restlet:http://localhost:8083/api/atmp_asyncRequest?restletMethod=post")
            .process(exchange -> {
                exchange.setProperty("uniqueRequestId", GUIDGenerator.generateAtmpSerialNumber());
            })
            .inOnly("seda:requestReceiveQueue")// here starts async processing of your request
            .process(exchange -> {
                exchange.getProperty("uniqueRequestId");
                // make here response for client with generated request id
            });

    from("seda:requestReceiveQueue")
            .process(exchange -> {
                // prepare\process request if need
            })
            .setHeader(Exchange.HTTP_METHOD, constant("POST"))
            .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
            .to("jetty:http://localhost:8080/rest_api_demo/api/restService")
            .process(exchange -> {
                exchange.getProperty("uniqueRequestId");
                // save somewhere prepared response for client bound to generated request id
            });

    from("restlet:http://localhost:8083/api/atmp_getResponse?restletMethod=post")
            .process(exchange -> {
                String requestId = ;//extract request id from client's request
                Object body =  ;//find response that you saved asynchronously by extracted request id
                // if response not found, then async processing request not ended, so you should send message to client to continue polling
                exchange.getIn().setBody(body);
            });

That will work if you haven't callback server for async responses on client's side.

Also you can use Seda component instead of jms, for queueing tasks between routes.