Mock the body of an intermediate route executed in Camel Tests - apache-camel

Available examples of the usage of the Camel Test component show how to test the expectations of a route:
However what I need to do is mock the body (manually setting it) of an intermediate route, e.g.:
<?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">
<bean id="exampleBean" class="xxx.ExampleBean"/>
<routeContext id="routesTest" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:route1" />
<to uri="direct:route2" />
<log message="${body}"/>
</route>
<route>
<from uri="direct:route2"/>
<to uri="bean:exampleBean"/>
<to uri="direct:route3" />
</route>
<route>
<from uri="direct:route3"/>
<log message="${body}"/>
</route>
</routeContext>
</beans>
In this scenario I want to completely avoid the actual execution of bean:exampleBean, mocking the result of its execution.
My test class:
public class MyTests extends CamelSpringTestSupport {
#Produce(uri = "direct:route1")
protected ProducerTemplate inputProducerTemplate;
#EndpointInject(uri = "mock:bean:exampleBean")
protected MockEndpoint mockBeanExampleBean;
#Test
public void testRoute() throws Exception {
CompletableFuture<Object> future = inputProducerTemplate.asyncSendBody("direct:route1", "Some message");
Object o = future.get();
}
#Override
public String isMockEndpoints() {
return "bean:exampleBean";
}
#Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("spring/gesti-test-application-context.xml");
}
}
public class ExampleBean {
public String enhance(String message) {
System.out.println(message);
//Here I would call a REST API
return "MY API RESULT";
}
}
When using mockBeanExampleBean.whenAnyExchangeReceived(exchange -> exchange.getMessage().setBody("My message")); it allows to override the input to exampleBean, but doesn't avoid its execution.

In the context of your unit test, route2 might be a "mock" component instead. A clean way of achieving that is to declare the route(s) in the properties file. The legibility of the routes gets harder, though.
Then, you could:
#EndpointInject("mock://route2")
MockEndpoint mockSecondStep;
mockSecondStep.whenExchangeReceived(1, e -> {
List whatever = new ArrayList<>();
e.getMessage().setBody(whatever);
});

I solved it using an InterceptStrategy:
public class MyTests extends CamelSpringTestSupport {
#Test
public void testRoute() throws Exception {
CompletableFuture<Object> future = template.asyncSendBody("direct:route1", "Some message");
Object o = future.get();
assertEquals("INTERCEPTED!", o);
}
#Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("spring/gesti-test-application-context.xml");
}
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
context.getProcessorDefinition("bean:exampleBean").addInterceptStrategy(
(context, definition, target, nextTarget) -> exchange -> exchange.getOut().setBody("INTERCEPTED!"));
}
};
}
}
public class ExampleBean {
public String enhance(String message) {
System.out.println(message);
//Here I would call a REST API
return "MY API RESULT";
}
}

Related

How to pass parameters to Apache Camel through command line?

I use Apache Camel’s Spring Main to boot my Camel application. I need my application to read the command line arguments to set some parameters. So, I cannot use property files.
At the moment, I can pass arguments via the JVM system properties, and it works well:
Application.java
public class Application extends org.apache.camel.spring.Main {
public static void main(String[] args) throws Exception {
Application app = new Application();
instance = app;
app.run(args);
}
}
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">
<bean id="shutdownBean" class="com.example.ShutdownBean" />
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:{{inputFile}}?noop=true"/>
<to uri="bean:shutdownBean" />
</route>
</camelContext>
</beans>
I run the app with java com.example.Application -DinputFile=C:/absolute/path/to/watch and everything works fine:
…
FileEndpoint INFO Using default memory based idempotent repository with cache max size: 1000
InternalRouteStartupManager INFO Route: route1 started and consuming from: file://C:/absolute/path/to/watch
AbstractCamelContext INFO Total 1 routes, of which 1 are started
…
But I would like to have some input validation and make the app easier to use because -D could be confusing for a non Java user. So I change Application.java:
public class Application extends org.apache.camel.spring.Main {
private File inputFile;
public static void main(String[] args) throws Exception {
Application app = new Application();
instance = app;
app.run(args);
}
public Application() {
addOption(new ParameterOption("i", "inputFile", "The input file", "inputFile") {
#Override
protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
File file = FileUtils.getFile(parameter);
// some business validation
setInputFile(file);
}
});
}
private void setInputFile(File inputFile) {
this.inputFile = inputFile;
}
}
Then, I could use the following command to run the application: java com.example.Application -inputFile C:/absolute/path/to/watch
How can I use my inputFile field into my Camel route?
Call addProperty(String key, String value) in your doProcess method. Then it will be accessible throught {{key}} notation.
MyApplication:
public final class MyApplication extends Main {
private MyApplication() {
super();
addCliOption("g", "greeting", "Greeting");
addCliOption("n", "name", "Who to greet");
}
public static void main(String[] args) throws Exception {
MyApplication app = new MyApplication();
app.configure().addRoutesBuilder(MyRouteBuilder.class);
app.run(args);
}
private void addCliOption(String abbrevation, String parameterName, String description) {
addOption(new ParameterOption(abbrevation, parameterName, description, parameterName) {
protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
addProperty("console." + parameterName, parameter);
}
});
}
}
MyRouteBuilder:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("quartz:foo")
.log("{{console.greeting}} {{console.name}}");
}
}
java org.apache.camel.example.MyApplication -greeting Hello -name Morgan
23:10:25.862 [DefaultQuartzScheduler-MyCoolCamel_Worker-1] INFO route1 - Hello Morgan
23:10:26.832 [DefaultQuartzScheduler-MyCoolCamel_Worker-2] INFO route1 - Hello Morgan
23:10:27.829 [DefaultQuartzScheduler-MyCoolCamel_Worker-3] INFO route1 - Hello Morgan

How to get the file name in a custom processor?

I serialize the files to a byte arrays and send it to the ActiveMQ queue (I using the code from this example: Apache ActiveMQ File Transfer Example):
private void sendFileAsBytesMessage(File file) throws JMSException, IOException {
BytesMessage bytesMessage = session.createBytesMessage();
// Set file name here
bytesMessage.setStringProperty("fileName", file.getName());
// Set file body here
bytesMessage.writeBytes(fileManager.readfileAsBytes(file));
MessageProduce msgProducer =
session.createProducer(session.createQueue(queueName));
MessageProducer msgProducer.send(bytesMessage);
}
Method readfileAsBytes is presented below:
public byte[] readfileAsBytes(File file) throws IOException {
try (RandomAccessFile accessFile = new RandomAccessFile(file, "r")) {
byte[] bytes = new byte[(int) accessFile.length()];
accessFile.readFully(bytes);
return bytes;
}
}
In my OSGi bundle I have a custom processor:
public class Deserializer implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
// Get file body here
byte[] bytes = exchange.getIn().getBody(byte[].class);
}
}
I use it in my Spring DSL route as follows:
<?xml version="1.0"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="brokerURL" value="tcp://localhost:61616" />
<property name="userName" value="admin" />
<property name="password" value="admin" />
</bean>
<bean id="deserializer" class="org.fusesource.example.Deserializer"/>
<camelContext id="blueprintContext" trace="false" xmlns="http://camel.apache.org/schema/blueprint">
<route id="testRoute">
<from uri="activemq:source-queue"></from>
<process ref="deserializer"/>
<to uri="activemq:sink-queue"></to>
</route>
</camelContext>
</blueprint>
I need to get the file name in my processor. How can I do that?
The problem can be solved as follows. For example, I send the README.html file to the alfresco-queue. Then the body of the file and the file name, as well as some additional information can be obtained as follows:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:alfresco-queue?username=admin&password=admin")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
// Get file body
byte[] bytes = exchange.getIn().getBody(byte[].class);
for(int i = 0; i < bytes.length; i++) {
System.out.print((char) bytes[i]);
}
// Get headers
Map<String, Object> headers = exchange.getIn().getHeaders();
Iterator iterator = headers.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry pair = (Map.Entry)iterator.next();
System.out.println(pair.getKey() + " == " + pair.getValue());
}
}
})
// SKIPPED
It gives the following output:
<head>
root<> title>README
</title>
</head>
<body>
Please refer to http://java.com/licensereadme
</body>
</html>
breadcrumbId == ID:63-DP-TAV-59000-1531813754416-1:1:1:1:1
fileName == README.html
JMSCorrelationID == null
JMSCorrelationIDAsBytes == null
JMSDeliveryMode == 2
JMSDestination == queue://alfresco-queue
JMSExpiration == 0
JMSMessageID == ID:63-DP-TAV-59000-1531813754416-1:1:1:1:1
JMSPriority == 4
JMSRedelivered == false
JMSReplyTo == null
JMSTimestamp == 1531813754610
JMSType == null
JMSXGroupID == null
JMSXUserID == null

TestNG #DataProvider with log4j

When I use log4j and TestNG #DataProvider I was able to generate logs for the below code, but when I use #DataProvider the logs are getting overwritten. I tried to use #BeforeTest but it's not working. Any idea on how can I generate for logs for all the my rows?
public class DemoTest extends baseFX {
public static Logger Log = LogManager.getLogger(baseFX.class.getName());
#Test(dataProvider="DataProvider", groups = { "baseFX" })
public void loginPageNav(String Test, String newMemEmail, String newMemPass ) throws Exception {
driver=driverSetup();
Log.info("Driver is initialized");
//Creating object for the Index to the class
IndexPage indexpage = new IndexPage(driver);
Log.info("Navigated to Index Page");
//invoke method
indexpage.clickLogin().click();
Log.info("Clicked on Login");
//Creating an object for the login page and invoke its elements
LoginPage loginpage = new LoginPage(driver);
loginpage.getPassword().sendKeys(password);
loginpage.loginBtn().click();
Log.info("Member successfully logged in");
//driver.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
driver.close();
}
#DataProvider
public Object[][] DataProvider() throws Exception{
Object[][] arrayObject = ReadExcelData.getExcelData("./src/test/java/Autopkg/TestData/Demo.xlsx", "Sheet1");
return arrayObject;
}
//#AfterTest
//public void tearDown(){
//}
}
I use log4j configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="basePath">./logs</Property>
</Properties>
<Appenders>
<RollingFile name="File" fileName="${basePath}/prints.log" filePattern="${basePath}/prints-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<SizeBasedTriggeringPolicy size="1000" />
</RollingFile>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>

Getting null from the seda queue

I have a camel route that looks like this:
<camel:camelContext id="context" xmlns="http://camel.apache.org/schema/spring">
<propertyPlaceholder id="camelContextProperties" location="ref:lefrogen"/>
<contextScan/>
<template id="producerTemplate"/>
<template id="consumerTemplate"/>
<route id="chibzo">
<from uri="activemq:queueName"/>
<to uri="seda:internal?multipleConsumers=true"/>
</route>
<route id="chon">
<from uri="seda:internal?multipleConsumers=true"/>
<bean ref="beanName"/>
<to uri="seda:external?multipleConsumers=true"/>
</route>
</camel:camelContext>
The bean class is like this:
#Component
public class BeanName {
private final static Logger LOGGER = Logger.getLogger(BeanName.class.getName());
public static String message = null;
public static String map(String custom) {
message = custom;
return custom;
}
}
I have written a unit test which is supposed to get the data from the bean and then compare it with the data from seda:external. The problem is that I am getting null when I am fetching data from the seda queue. Here's the unit test:
public class Test{
#EndpointInject
ProducerTemplate producerTemplate;
#EndpointInject
ConsumerTemplate consumerTemplate;
#Autowired
BeanName beanName;
#Test
public void testName() throws Exception {
producerTemplate.sendBody("seda:internal","Good bye");
Thread.sleep(10000); //needs to sleep so that the bean gets executed before accessing
//beanName.message;
String message = beanName.message;
LOGGER.info("Printing" + message); //prints (Good bye)
String body = consumerTemplate.receiveBodyNoWait("seda:external", String.class);
LOGGER.info("Body is" + body); //prints null
}
Any reason why the queue is giving null?
Its due concurrent, and you are using recieveBodyNoWait which does a fast check. Instead use receiveBody and provide may a timeout value to avoid waiting if there is no messages on the queue.
Or add sleep in the code if you want to use receiveBodyNoWait still.

Zipping and password protect file using camel

I have been trying to zip multiple csv file in my route. I have been successfully able to do that.
I am using spring to do the same.
Now the new requirement is to password protect them. Following is the aggregation strategy I have used. How to achieve this?
<route autoStartup="false" routePolicyRef="routeTwoTimer" startupOrder="2" id="zippingFileRoute">
<from uri="{{to.file.processed1}}"/>
<choice id="csvZipFile">
<when>
<simple>$simple{header.CamelFileName} regex '^.*(csv|CSV)$'</simple>
<aggregate strategyRef="zipAggregationStrategy" completionFromBatchConsumer="true" eagerCheckCompletion="true">
<correlationExpression>
<constant>true</constant>
</correlationExpression>
<to uri="{{to.file.processed2}}"/>
</aggregate>
</when>
</choice>
</route>
As pointed in comments, Java API is a bit limited in encrypting ZIP files. Apache Camel ZipAggregationStrategy is using ZipOutputStream, so there is this limitation too. You can implement custom Aggregator using any other library, which allows encryption of Zip files. For example Zip4j
Add Maven dependency
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>1.3.2</version>
</dependency>
Implement custom Aggregator
import net.lingala.zip4j.core.ZipFile;
//next few imports. I have added this only to take correct ZipFile class, not the JDK one
public class PasswordZipAggregationStrategy implements AggregationStrategy {
public static final String ZIP_PASSWORD_HEADER = "PasswordZipAggregationStrategy.ZipPassword";
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange){
try {
if (newExchange == null) {
return oldExchange;
}
return aggregateUnchecked(oldExchange,newExchange);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Exchange aggregateUnchecked(Exchange oldExchange, Exchange newExchange) throws Exception{
ZipFile zipFile;
String password;
if (oldExchange == null) { // first
password = newExchange.getIn().getHeader(ZIP_PASSWORD_HEADER, String.class);
zipFile = new ZipFile(newExchange.getExchangeId()+".zip");
File toDelete = new File(zipFile.getFile().getPath());
newExchange.addOnCompletion(new Synchronization() {
#Override
public void onComplete(Exchange exchange) {
toDelete.delete();
}
#Override
public void onFailure(Exchange exchange) {
}
});
} else {
password = newExchange.getIn().getHeader(ZIP_PASSWORD_HEADER, String.class);
zipFile = new ZipFile(oldExchange.getIn().getBody(File.class));
}
if (password==null){
throw new IllegalStateException("Null password given");
}
ZipParameters zipParameters = new ZipParameters();
zipParameters.setPassword(password);
zipParameters.setEncryptFiles(true);
zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_FAST);
zipParameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
zipFile.addFile(newExchange.getIn().getBody(File.class), zipParameters);
GenericFile genericFile = FileConsumer.asGenericFile(zipFile.getFile().getParent(), zipFile.getFile(), Charset.defaultCharset().toString(), false);
genericFile.bindToExchange(newExchange);
newExchange.getIn().setBody(zipFile.getFile());
newExchange.getIn().setHeader(ZIP_PASSWORD_HEADER, password);
return newExchange;
}
}
Use it
from("file://in")
.to("log:in")
.setHeader(PasswordZipAggregationStrategy.ZIP_PASSWORD_HEADER, constant("testPassword"))
.aggregate().constant(true).completionFromBatchConsumer()
.aggregationStrategy(new PasswordZipAggregationStrategy())
.to("log:out")
.to("file://out");

Resources