What is needed to send a POJO with Camel's ProducerTemplate#sendBody() - apache-camel

I am trying to send a POJO using ProducerTemplate#sendBody(), but I get the following error:
org.apache.camel.language.bean.RuntimeBeanExpressionException: Failed to invoke
method: [searchId] on app.FsiRequest due to:
java.lang.IndexOutOfBoundsException: Key: searchId not found in bean:
app.FsiRequest#5c2d65cf of type: app.FsiRequest using OGNL path [[searchId]]
at org.apache.camel.language.bean.BeanExpression.evaluate(BeanExpression.java:119) ~[camel-core-2.22.1.jar:2.22.1]
at org.apache.camel.language.bean.BeanExpression.evaluate(BeanExpression.java:135) ~[camel-core-2.22.1.jar:2.22.1]
at org.apache.camel.model.language.ExpressionDefinition.evaluate(ExpressionDefinition.java:127) ~[camel-core-2.22.1.jar:2.22.1]
at org.apache.camel.model.language.ExpressionDefinition.evaluate(ExpressionDefinition.java:119) ~[camel-core-2.22.1.jar:2.22.1]
at org.apache.camel.builder.ExpressionBuilder$40.evaluate(ExpressionBuilder.java:1004) ~[camel-core-2.22.1.jar:2.22.1]
at org.apache.camel.support.ExpressionAdapter.evaluate(ExpressionAdapter.java:36) ~[camel-core-2.22.1.jar:2.22.1]
at org.apache.camel.builder.SimpleBuilder.evaluate(SimpleBuilder.java:92) ~[camel-core-2.22.1.jar:2.22.1]
The class I'm sending (simplified):
public class FsiRequest {
public String getSearchId() {
return searchId;
}
public void setSearchId(String searchId) {
this.searchId = searchId;
}
private String searchId;
public FsiRequest(Map<String, String> request) {
searchId = request.get("searchId");
}
}
Here's my invocation:
private final ForkJoinPool routeExecutorPool = new ForkJoinPool(1024);
#Override
public void configure() {
from("servlet://" + SEARCH_REQUEST)
.process(exchange -> {
FsiRequest request = createRequestMap(exchange);
sendRequestToAllProviderRoutes(exchange, request);
})
.transform()
.constant("OK");
}
private void sendRequestToAllProviderRoutes(Exchange exchange, FsiRequest request) {
try {
ProducerTemplate tmpl = exchange.getContext().createProducerTemplate();
routeExecutorPool.execute(() -> getRoutes(exchange).parallelStream().forEach(
route -> tmpl.sendBody(route, request)
));
} catch (RejectedExecutionException | RuntimeCamelException e) {
log.error("FSI Servlet failed to send request to provider routes", e);
}
}
getRoutes() fetches relevant routes by filtering exchange.getContext().getRouteDefinitions().
sendBody() works fine when I use a HashMap<String, Object> instead of the FsiRequest class.

This issue was due to a bug on our part. The receiving route had this:
.setHeader(SEARCH_ID_KEY, simple("${body[searchId]}"))
Switching to body.searchId solved the issue.

Related

How to write the unit tests for the apache camel routes

Can someone please help in writing the unit tests for this class.
==============================================================================================
#Component("edi820AdapterRouteBuilder")
public class EDI820AdapterRouteBuilder extends BaseRouteBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(EDI820AdapterRouteBuilder.class);
private static final String DIRECT_Q_RECEIVER = "direct:queueReceiver";
private static final String DIRECT_PROCESS_820 = "direct:process820";
private static final String DIRECT_TO_HIX = "direct:toHIX";
private static final String DIRECT_TO_CMS = "direct:toCMS";
private static final String MDC_TRANSACTIONID = "transactionId";
private static final String REQUEST_ID = "RequestID";
private static final String DIRECT_PUT_MDC = "direct:putMDC";
private static final String DIRECT_REMOVE_MDC = "direct:removeMDC";
#Override
public void configure() throws Exception {
super.configure();
LOGGER.debug("configure called.");
String queueName = appConfig.getInboundQueueName();
LOGGER.debug("inboundQueueName: {}", queueName);
String toHIXendpoint = appConfig.getEndpointWithOptions("toHIXendpoint");
LOGGER.debug("toHIXendpoint: {}", toHIXendpoint);
String toCMSendpoint = appConfig.getEndpointWithOptions("toCMSendpoint");
LOGGER.debug("toCMSendpoint: {}", toCMSendpoint);
String routeDelay = appConfig.getRouteDelay();
LOGGER.debug("routeDelay: {}",routeDelay);
from("timer://runOnce?repeatCount=1&delay="+routeDelay)
.to("bean:edi820AdapterRouteBuilder?method=addRoute")
.end();
from(DIRECT_Q_RECEIVER)
.to(PERSIST_EDI820_XDATA)
.to(EDI820_REQUEST_TRANSFORMER)
.to(DIRECT_PROCESS_820)
.log(LoggingLevel.INFO, LOGGER,"Executed "+DIRECT_Q_RECEIVER)
.end();
from(DIRECT_PROCESS_820)
.choice()
.when(header(TRANSACTION_SOURCE_STR).isEqualTo(HIX_SOURCE_SYSTEM))
.log(LoggingLevel.INFO, LOGGER,"Calling route for: "+HIX_SOURCE_SYSTEM)
.to(DIRECT_TO_HIX)
.when(header(TRANSACTION_SOURCE_STR).isEqualTo(CMS_SOURCE_SYSTEM))
.log(LoggingLevel.INFO, LOGGER,"Calling route for: "+CMS_SOURCE_SYSTEM)
.to(DIRECT_TO_CMS)
.otherwise()
.log(LoggingLevel.INFO, LOGGER,"Invalid "+TRANSACTION_SOURCE_STR+" ${header["+TRANSACTION_SOURCE_STR+"]}")
.end();
from(DIRECT_TO_HIX).routeId("edi820adapter-to-hix-producer-route")
.log(LoggingLevel.INFO, LOGGER,"Executing edi820adapter-to-hix-producer-route")
.marshal().json(JsonLibrary.Jackson)//Convert body to json string
.to(toHIXendpoint)
.log(LoggingLevel.DEBUG, LOGGER, "json body sent to edi820-hix: ${body}")
.log(LoggingLevel.INFO, LOGGER,"Executed edi820adapter-to-hix-producer-route")
.end();
from(DIRECT_TO_CMS).routeId("edi820adapter-to-cms-producer-route")
.log(LoggingLevel.INFO, LOGGER,"Executing edi820adapter-to-cms-producer-route")
.marshal().json(JsonLibrary.Jackson)//Convert body to json string
.to(toCMSendpoint)
.log(LoggingLevel.DEBUG, LOGGER, "json body sent to edi820-cms: ${body}")
.log(LoggingLevel.INFO, LOGGER,"Executed edi820adapter-to-cms-producer-route")
.end();
from(DIRECT_PUT_MDC).process(new Processor() {
public void process(Exchange exchange) throws Exception {
if (exchange.getIn().getHeader(REQUEST_ID) != null) {
MDC.put(MDC_TRANSACTIONID, (String) exchange.getIn().getHeader(REQUEST_ID));
}
}
}).end();
from(DIRECT_REMOVE_MDC).process(new Processor() {
public void process(Exchange exchange) throws Exception {
MDC.remove(MDC_TRANSACTIONID);
}
}).end();
}
public void addRoute(Exchange exchange) {
try {
CamelContext context = exchange.getContext();
ModelCamelContext modelContext = context.adapt(ModelCamelContext.class);
modelContext.addRouteDefinition(buildRouteDefinition());
} catch (Exception e) {
LOGGER.error("Exception in addRoute: {}", e.getMessage());
LOGGER.error(ExceptionUtils.getFullStackTrace(e));
}
}
private RouteDefinition buildRouteDefinition() {
String queueName = appConfig.getInboundQueueName();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition
.from("jms:queue:" + queueName).routeId("edi820-adapter-jms-consumer-route")
.to(DIRECT_PUT_MDC)
.log(LoggingLevel.INFO, LOGGER,"Executing edi820-adapter-jms-consumer-route")
.log(LoggingLevel.DEBUG, LOGGER, "Received Message from queue: "+queueName)
.to(DIRECT_Q_RECEIVER)
.log(LoggingLevel.INFO, LOGGER,"Executed edi820-adapter-jms-consumer-route")
.to(DIRECT_REMOVE_MDC)
.end();
return routeDefinition;
}
}
==============================================================================
Please let me know if any additional information is needed. Thanks for the help in advance.
It is not possible to write entire test cases mentioning those classes. So I'm writing snippet code for understanding purposes.
Sample Example for Understanding:
Below is a snippet of code for the route builder class:
public class CheckRouteBuilder extends RouteBuilder{
#Override
public void configure() throws Exception {
from("timer://runOnce?repeatCount=1&delay="+routeDelay)
.to("bean:edi820AdapterRouteBuilder?method=addRoute")
.end();
}
}
Follow the test cases of the above class:
public class CheckRouteBuilderTest extends CamelTestSupport {
#Test
public void testConfigure() throws Exception {
context.addRoutes(new CheckRouteBuilder ());
RouteDefinition route = context.getRouteDefinitions().get(0);
AdviceWith.adviceWith(route, context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:TestEndpoint"); // timer://runOnce?repeatCount=1&delay="+routeDelay replace with direct:TestEndpoint because during test timer component not support that's why need to replace with direct component.
weaveAddLast().to("mock:endPoint");
}
});
MockEndpoint mockEndpoint = getMockEndpoint("mock:endPoint");
mockEndpoint.expectedMessageCount(1);
template.sendBodyAndHeaders("direct:TestEndpoint", "#", "##");
mockEndpoint.assertIsSatisfied();
}
}
# -> Mention body like String, JSONObject, XML. If body is not required then simply pass the null value.
## -> Mention Header like Content-type:application/json to need to pass in Map<String, Object) format.

How to read a file and send as stream to another endpoint

I need to read a file using apache camel and send to another endpoint as stream.
public class SimpleRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("file:C:/inputFolder?noop=true").to("streamEndPoint");
}
}
Here some simple working example of routes from(file).to(rest-service)
//receiver
from("jetty://http://0.0.0.0:5514/path/getFile")
.process(exchange -> {
if (exchange.getIn().getAttachments() != null) {
for (String key : exchange.getIn().getAttachments().keySet()) {
DataHandler dataHandler = exchange.getIn().getAttachments().get(key);
System.out.println(String.format("Receive attachment:%s size:%s", dataHandler.getName(), dataHandler.getInputStream().available()));
}
}
});
//sender
from("file:/Users/user1/test/?delete=true&delay=5000")
.process(exchange -> {
GenericFile<File> body = exchange.getIn().getBody(GenericFile.class);
exchange.getIn().setHeader("Content-Type", MediaType.MULTIPART_FORM_DATA);
exchange.getIn().setHeader("CamelHttpMethod", "POST");
exchange.getIn().setBody(MultipartEntityBuilder.create()
.addPart(body.getFileName(), new FileBody(body.getFile(), ContentType.MULTIPART_FORM_DATA, body.getFileName()))
.build()
);
})
.to("http4://0.0.0.0:5514/path/getFile?synchronous=true")//camel-http4 component for sending to our rest service
;
Here is whole example that you can run and see how it works.

Send data to an external resource: why content is null?

I have a simple Camel route. I want to extract a file from the queue and pass it by using POST request to an external resource. This route works and the request reaches an external resource:
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:alfresco-queue")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
byte[] bytes = exchange.getIn().getBody(byte[].class);
// All of that not working...
// exchange.getIn().setHeader("content", bytes); gives "java.lang.IllegalAgrumentException: Request header is too large"
// exchange.getIn().setBody(bytes, byte[].class); gives "size of content is -1"
// exchange.getIn().setBody(bytes); gives "size of content is -1"
// ???
// ??? But I can print file content here
for(int i=0; i < bytes.length; i++) {
System.out.print((char) bytes[i]);
}
}
})
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("multipart/form-data"))
.to("http://vm-alfce52-31......com:8080/alfresco/s/someco/queuefileuploader?guest=true")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("The response code is: " + exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE));
}
});
}
}
The question is that the payload of the request is lost:
// somewhere on an external resource
Content content = request.getContent();
long len = content.getSize() // is always == -1.
// the file name is passed successfully
String fileName = request.getHeader("fileName");
How to set and pass the payload of POST request in this route/ processor?
I noticed that ANY data setted by this way is losted too. Only the headers are sent to the remote resource.
By using simple HTML form with <input type="file"> encoded in multipart/form-data I can successfully send all the data to the external resource.
What could be the reason?
Updated.
The following code also gives null-content:
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
// this also gives null-content
//multipartEntityBuilder.addBinaryBody("file", exchange.getIn().getBody(byte[].class));
multipartEntityBuilder.addPart("file", new ByteArrayBody(exchange.getIn().getBody(byte[].class), exchange.getIn().getHeader("fileName", String.class)));
exchange.getOut().setBody(multipartEntityBuilder.build().getContent());
/********** This also gives null-content *********/
StringBody username = new StringBody("username", ContentType.MULTIPART_FORM_DATA);
StringBody password = new StringBody("password", ContentType.MULTIPART_FORM_DATA);
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.addPart("username", username);
multipartEntityBuilder.addPart("password", password);
String filename = (String) exchange.getIn().getHeader("fileName");
File file = new File(filename);
try(RandomAccessFile accessFile = new RandomAccessFile(file, "rw")) {
accessFile.write(bytes);
}
multipartEntityBuilder.addPart("upload", new FileBody(file, ContentType.MULTIPART_FORM_DATA, filename));
exchange.getIn().setBody(multipartEntityBuilder.build().getContent());
One more detail. If I change this:
exchange.getOut().setBody(multipartEntityBuilder.build().getContent());
To this:
exchange.getOut().setBody(multipartEntityBuilder.build());
I get the following exception on FUSE side (I see it through hawtio management console):
Execution of JMS message listener failed.
Caused by: [org.apache.camel.RuntimeCamelException - org.apache.camel.InvalidPayloadException:
No body available of type: java.io.InputStream but has value: org.apache.http.entity.mime.MultipartFormEntity#26ee73 of type:
org.apache.http.entity.mime.MultipartFormEntity on: JmsMessage#0x1cb83b9.
Caused by: No type converter available to convert from type: org.apache.http.entity.mime.MultipartFormEntity to the required type:
java.io.InputStream with value org.apache.http.entity.mime.MultipartFormEntity#26ee73. Exchange[ID-63-DP-TAV-55652-1531889677177-5-1]. Caused by:
[org.apache.camel.NoTypeConversionAvailableException - No type converter available to convert from type:
org.apache.http.entity.mime.MultipartFormEntity to the required type: java.io.InputStream with value org.apache.http.entity.mime.MultipartFormEntity#26ee73]]
I write a small servlet application and get the content in the doPost(...) method from the HttpServletRequest object.
The problem was with the WebScriptRequest object on the external system (Alfresco) side.
#Bedla, thanks for your advices!
On the Alfresco side the problem can be solved as follows:
public class QueueFileUploader extends DeclarativeWebScript {
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status) {
HttpServletRequest httpServletRequest = WebScriptServletRuntime.getHttpServletRequest(req);
// calling methods of httpServletRequest object and retrieving the content
...
The route:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:alfresco-queue")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.addPart("file", new ByteArrayBody(exchange.getIn().getBody(byte[].class),
exchange.getIn().getHeader("fileName", String.class)));
exchange.getIn().setBody(multipartEntityBuilder.build().getContent());
}
})
.setHeader(Exchange.HTTP_METHOD, constant(org.apache.camel.component.http4.HttpMethods.POST))
.to("http4://localhost:8080/alfresco/s/someco/queuefileuploader?guest=true")
// .to("http4://localhost:8080/ServletApp/hello")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("The response code is: " +
exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE));
}
});
}
}

How to browse messages from a queue using Apache Camel?

I need to browse messages from an active mq using Camel route without consuming the messages.
The messages in the JMS queue are to be read(only browsed and not consumed) and moved to a database while ensuring that the original queue remains intact.
public class CamelStarter {
private static CamelContext camelContext;
public static void main(String[] args) throws Exception {
camelContext = new DefaultCamelContext();
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnectionFactory.DEFAULT_BROKER_URL);
camelContext.addComponent("jms", JmsComponent.jmsComponent(connectionFactory));
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("jms:queue:testQueue").to("browse:orderReceived") .to("jms:queue:testQueue1");
}
}
);
camelContext.start();
Thread.sleep(1000);
inspectReceivedOrders();
camelContext.stop();
}
public static void inspectReceivedOrders() {
BrowsableEndpoint browse = camelContext.getEndpoint("browse:orderReceived", BrowsableEndpoint.class);
List<Exchange> exchanges = browse.getExchanges();
System.out.println("Browsing queue: "+ browse.getEndpointUri() + " size: " + exchanges.size());
for (Exchange exchange : exchanges) {
String payload = exchange.getIn().getBody(String.class);
String msgId = exchange.getIn().getHeader("JMSMessageID", String.class);
System.out.println(msgId + "=" +payload);
}
As far as I know, not possible in Camel to read (without consuming !) JMS messages...
The only workaround I found (in a JEE app) was to define a startup EJB with a timer, holding a QueueBrowser, and delegating the msg processing to a Camel route:
#Singleton
#Startup
public class MyQueueBrowser {
private TimerService timerService;
#Resource(mappedName="java:/jms/queue/com.company.myqueue")
private Queue sourceQueue;
#Inject
#JMSConnectionFactory("java:/ConnectionFactory")
private JMSContext jmsContext;
#Inject
#Uri("direct:readMessage")
private ProducerTemplate camelEndpoint;
#PostConstruct
private void init() {
TimerConfig timerConfig = new TimerConfig(null, false);
ScheduleExpression se = new ScheduleExpression().hour("*").minute("*/"+frequencyInMin);
timerService.createCalendarTimer(se, timerConfig);
}
#Timeout
public void scheduledExecution(Timer timer) throws Exception {
QueueBrowser browser = null;
try {
browser = jmsContext.createBrowser(sourceQueue);
Enumeration<Message> msgs = browser.getEnumeration();
while ( msgs.hasMoreElements() ) {
Message jmsMsg = msgs.nextElement();
// + here: read body and/or properties of jmsMsg
camelEndpoint.sendBodyAndHeader(body, myHeaderName, myHeaderValue);
}
} catch (JMSRuntimeException jmsException) {
...
} finally {
browser.close();
}
}
}
Apache camel browse component is exactly designed for that. Check here for the documentation.
Can't say more since you have not provided any other information.
Let's asssume you have a route like this
from("activemq:somequeue).to("bean:someBean")
or
from("activemq:somequeue).process(exchange -> {})
All you got to do it put a browse endpoint in between like this
from("activemq:somequeue).to("browse:someHandler").to("bean:someBean")
Then write a class like this
#Component
public class BrowseQueue {
#Autowired
CamelContext camelContext;
public void inspect() {
BrowsableEndpoint browse = camelContext.getEndpoint("browse:someHandler", BrowsableEndpoint.class);
List<Exchange> exchanges = browse.getExchanges();
for (Exchange exchange : exchanges) {
......
}
}
}

Mock not found in the registry for Camel route test

I am trying to test a Camel route (polling messages from an SQS queue) containing
.bean("messageParserProcessor")
where messageParserProcessor is a Processor.
The test:
public class SomeTest extends CamelTestSupport {
private final String queueName = ...;
private final String producerTemplateUri = "aws-sqs://" + queueName + ...;
private static final String MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT = "mock:messageParserProcessor";
#EndpointInject(uri = MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT)
protected MockEndpoint messageParserProcessor;
#Override
public boolean isUseAdviceWith() {
return true;
}
#Before
public void setUpContext() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
interceptSendToEndpoint("bean:messageParserProcessor")
.skipSendToOriginalEndpoint()
.process(MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT);
}
});
}
#Test
public void testParser() throws Exception {
context.start();
String expectedBody = "test";
messageParserProcessor.expectedBodiesReceived(expectedBody);
ProducerTemplate template = context.createProducerTemplate();
template.sendBody(producerTemplateUri, expectedBody);
messageParserProcessor.assertIsSatisfied();
context.stop();
}
}
When I run the test I get this error:
org.apache.camel.FailedToCreateRouteException:
Failed to create route route1 at:
>>> InterceptSendToEndpoint[bean:messageParserProcessor -> [process[ref:mock:messageParserProcessor]]] <<< in route: Route(route1)[[From[aws-sqs://xxx...
because of No bean could be found in the registry for: mock:messageParserProcessor of type: org.apache.camel.Processor
Same error if I replace interceptSendToEndpoint(...) with mockEndpointsAndSkip("bean:messageParserProcessor")
The test can be executed (but obviously doesn't pass) when I don't use a mock:
interceptSendToEndpoint("bean:messageParserProcessor")
.skipSendToOriginalEndpoint()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {}
});
So the problem is the mock that is not found, what is wrong in the way I create it?
So I found a workaround to retrieve mocks from the registry:
interceptSendToEndpoint("bean:messageParserProcessor")
.skipSendToOriginalEndpoint()
.bean(getMockEndpoint(MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT));
// Instead of
// .process(MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT);
But I still don't understand why using .process("mock:someBean") doesn't work...

Resources