How to use Apache Camel aggregator - java

I have a very simple use case in which I want to put together a collection of stings and trying to use aggregator EIP for this. However when trying to start up the route it complains it cannot find an Aggregator Strategy:
java.lang.IllegalArgumentException: AggregationStrategy or AggregationStrategyRef must be set on Aggregate
below is how I can reproduce the issue:
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class AggregatorTest extends CamelTestSupport {
private static final List<String> LIST = Arrays.asList(new String[] {"one", "two", "three"});
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.split().body()
.setHeader("cheese", constant("camembert"))
.aggregate(constant("all"))
.to("mock:end");
}
};
}
#Test
public void shouldAggregateStrings() throws Exception {
sendBody("direct:start", LIST);
}
}
Of course a very easy way to fix this would be to create an Aggregation Strategy implementation and configure my route to use it. However I would like to understand why the way is set up now does not work. According to the camel documentation on here :
By default Camel uses DefaultAggregationCollection and
UseLatestAggregationStrategy, so this simple example will just keep
the latest received exchange for the given correlation Expression
I also noticed DefaultAggregationCollection is no longer part of the camel core. So what I am missing here?

That is the old documentation. The correct documentation is at: http://camel.apache.org/aggregator2
eg the list of EIPs has links to the correct documentation: http://camel.apache.org/eip
You can find examples from those links, and as well in this little example: https://github.com/apache/camel/blob/master/examples/camel-example-aggregate/README.md
And the Camel in Action books has an EIP chapter where the aggregator is covered in much more details as well: http://camel.apache.org/books

I made a small video to demonstrate this EIP pattern using SpringBoot and Camel.
Have a look here: https://youtu.be/IdGuGGVv51Q

Related

Adding Apache Camel custom component/endpoint in a Spring application

I'm trying to implement a custom endpoint in a Spring Boot application.
Goal is to use routes as: from("...").process("...").to("my:...");
Now, I have 3 classes: a DefaultConsumer, a DefaultEndpoint, a DefaultComponent:
package com.my.endpoint;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.support.DefaultEndpoint;
public class MyEndpoint extends DefaultEndpoint {
public MyEndpoint(String uri, MyComponent myComponent) {
}
...
}
package com.my.endpoint;
import org.apache.camel.Endpoint;
import org.apache.camel.Processor;
import org.apache.camel.support.DefaultConsumer;
public class MyConsumer extends DefaultConsumer {
public MyConsumer(Endpoint endpoint, Processor processor) {
super(endpoint, processor);
}
}
package com.my.endpoint;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
import java.util.Map;
#Component("my")
public class MyComponent extends DefaultComponent {
public MyComponent(CamelContext camelContext) {
super(camelContext);
}
...
}
Now: how can I register?
In a Spring configuration class, I have:
#Override
public void configure() throws Exception {
camelContext.addComponent("my", new MyComponent(camelContext));
But is not working:
Caused by: org.apache.camel.NoSuchEndpointException: No endpoint could be found for: my, please check your classpath contains the needed Camel component jar.
So, I added the META-INF file in services/org/apache/camel/component/my:
class=com.my.endpoint.MyComponent
But also this, is not working.
There is no complete tutorial on how to implement this.
Any help?
Note: I'm trying to implement an Endpoint because I need to integrate my systems using my data types. I tried using Transformer but failed because of this: Set a custom DataType in Apache Camel Processor
Before, I tried using data type converter, but failed because of this (marked duplicate because people are too lazy to really understand questions): Enforce type conversion on Rest consumer in Apache Camel
I've FULLY read "Apache Camel In Action, Second Edition" but, at the moment, I can't continue with my project because of?
This is because custom component must be annotated by #UriEndpoint annotation.
Another way to solve this problem: Set EndpointUri via Constructor or by implementing createEndpointUri() in MyEndpoint.
So easiest way might be changing your constructor to:
public MyEndpoint(String uri, MyComponent myComponent) {
super(uri, myComponent);
}

JOOQ is not generating classes

I have a problem with JOOQ framework (3.13.4) along with Spring Boot and Java 8.
The problem is that I'm trying to generate domain classes using java code way (instead of using codegen plugin with maven which had some troubles with custom naming strategy provider). So as first let me show You the #Configuration class which contains (at least I believe that it contains) all of the necessary beans:
import com.ormtester.common.base.Measurer;
import com.ormtester.common.utils.enums.OrmType;
import com.ormtester.datasources.config.RouteableDataSource;
import org.jooq.SQLDialect;
import org.jooq.codegen.GenerationTool;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.jooq.util.xml.jaxb.Schema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.jooq.meta.jaxb.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.PostConstruct;
import java.util.Properties;
#Configuration
#EnableTransactionManagement
public class JooqConfigurator {
private Properties moduleProperties;
private RouteableDataSource routeableDataSource;
public JooqConfigurator(RouteableDataSource routeableDataSource) {
this.routeableDataSource = routeableDataSource;
try {
moduleProperties = new Properties();
moduleProperties.load(JooqConfigurator.class.getClassLoader()
.getResourceAsStream("jooq.properties"));
} catch (Exception ignore) {}
}
#Bean
public DataSourceConnectionProvider connectionProvider() {
return new DataSourceConnectionProvider(routeableDataSource);
}
#Bean
public ExceptionTranslator exceptionTransformer() {
return new ExceptionTranslator();
}
#Bean
public DefaultConfiguration configuration() {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(connectionProvider());
jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer()));
jooqConfiguration.set(SQLDialect.DEFAULT);
return jooqConfiguration;
}
#Bean
public DefaultDSLContext dsl() {
return new DefaultDSLContext(configuration());
}
#PostConstruct
public void generateCode() {
try {
GenerationTool.generate(new org.jooq.meta.jaxb.Configuration()
.withJdbc(new Jdbc()
.withDriver("com.mysql.cj.jdbc.Driver")
.withUrl("jdbc:mysql://localhost:3306/ormtester?useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC")
.withUser("root")
.withPassword("root123"))
.withGenerator(new Generator()
.withName("org.jooq.codegen.JavaGenerator")
.withStrategy(new CustomStrategyProvider())
.withDatabase(new Database()
.withName("org.jooq.meta.mysql.MySQLDatabase")
.withIncludes(".*")
.withExcludes("")
.withSchemata(new SchemaMappingType().withInputSchema("ormtester").withOutputSchema("ormtester"))
.withInputCatalog("ormtester")
.withOutputCatalog("ormtester"))
.withTarget(new Target()
.withPackageName("com.ormtester.jooq.domain")
.withDirectory("jooq/src/main/java"))));
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
RouteableDataSource is a type that extends AbstractRoutingDataSource because in this case I need to have a possibility to change datasource at runtime. This thing is working well in the other regions of the project (or in another words with tools like Hibernate or MyBatis).
As You can see there is a #PostConstruct method which is used for generating domain classes and the problem is that this method doesn't generate any error or something but the classes are also not generated. I've tried to run it using PostgreSQL and Oracle database (of course changing the driver, database name etc.) and the situation is looking exactly the same.
One interesting thing is that when I'm running this code and package com.ormtester.jooq.domain is present - during the method execution domain package is getting removed.
I'd also like to mention that JOOQ autoconfiguration is disabled by excluding JooqAutoConfiguration class through the #SpringBootApplication annotation located at the project's main (starter) class.
IDE is running in administrator's mode and - what can be also interesting - if I will set the breakpoint in the getJavaClassName() method in my custom naming strategy provided (CustomStrategyProvider which extends DefaultGeneratorStrategy class, the breakpoint is reached everytime this method is used.
So does anyone faced the same problem and/or simply can tell me if I'm doing something wrong or something is missing in the code snippet that I've provieded here? I have this problem since about 4 days and now I'm running out of the ideas what can be wrong. I went through the tons of topics on many forums and nothing helped me, including the tutorials on the author's page (which in my opinion simply lacks of important informations).
I'll be really grateful for every help - thanks in advance!
Code generation is a build task, not a runtime task. I can't think of a reasonable scenario where generating code only at runtime would make sense.
The problem is that I'm trying to generate domain classes using java code way (instead of using codegen plugin with maven which had some troubles with custom naming strategy provider)
You have to create a separate maven module (or project) where you build the custom naming strategy, and then add that as a dependency to the jOOQ code generation plugin. This works the same way as with the JPADatabase, where entities have to be placed in a separate maven module.

NullPointerException when using multiple stepdefinitions in Cucumber-jvm

I am currently building a framework to test a Rest-API endpoint. As I am planning to write a lot of test cases, I decided to organize the project to allow me to reuse common Step Definition methods.
The structure is as follows;
FunctionalTest
com.example.steps
-- AbstractEndpointSteps.java
-- SimpleSearchSteps.java
com.example.stepdefinitions
-- CommonStepDefinition.java
-- SimpleSearchStepDefinition.java`
However when I try to call SimpleSearchSteps.java methods I get a NullPointerException
CommonStepDefinition Code
package com.example.functionaltest.features.stepdefinitions;
import net.thucydides.core.annotations.Steps;
import com.example.functionaltest.steps.AbstractEndpointSteps;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
public class CommonStepDefinition {
#Steps
private AbstractEndpointSteps endpointSteps;
#Given("^a base uri \"([^\"]*)\" and base path \"([^\"]*)\"$")
public void aBaseUriAndBasePath(String baseURI, String basePath) {
endpointSteps.givenBasepath(baseURI, basePath);
}
#When("^country is \"([^\"]*)\"$")
public void countryIs(String country)
{
endpointSteps.whenCountry(country);
}
#Then("^the status code is (\\d+)$")
public void theStatusCodeIs(int statusCode) {
endpointSteps.executeRequest();
endpointSteps.thenTheStatusCodeIs200(statusCode);
}
}
SimpleSearchStepDefinition.java
package com.example.functionaltest.features.stepdefinitions;
import net.thucydides.core.annotations.Steps;
import com.example.functionaltest.steps.EndpointSteps;
import cucumber.api.java.en.When;
public class SimpleSearchStepDefinition {
#Steps
private SimpleSearchSteps searchSteps;
#When("^what is \"([^\"]*)\"$")
public void whatIs(String what) {
searchSteps.whenWhatIsGiven(what);
}
}
Looks like you are missing holder class for Cucumber annotation, something like this you should have so that cucumber knows and identified that steps and features of yours:
#RunWith(Cucumber.class)
#CucumberOptions(
glue = {"com.example.functionaltest.features.steps"},
features = {"classpath:functionaltest/features"}
)
public class FunctionalTest {
}
Note that, in your src/test/resources you should have functionaltest/features folder with your .feature files according to this sample, you can ofc, change it by your design
Can you take a look at Karate it is exactly what you are trying to build ! Since you are used to Cucumber, here are a few things that Karate provides as enhancements (being based on Cucumber-JVM)
built-in step-definitions, no need to write Java code
re-use *.feature files and call them from other scripts
dynamic data-driven testing
parallel-execution of tests
ability to run some routines only once per feature
Disclaimer: I am the dev.
I solved this issue by using a static instance of RequestSpecBuilder in the AbstractEndpointSteps instead of RequestSpecification.
Therefore, I was able to avoid duplication of StepDefinitions and NPE issues altogether

How to create a new Bundle object?

I'm trying to use Firebase Analytics for an Android application, and in order to log events I've followed https://firebase.google.com/docs/analytics/android/events. That is, in order to send my event, I have to create a new Bundle object (which I create by using the default constructor) and I call the logEvent function of Firebase Analytics. While testing my development with a simple unit test, I realized that there's no content set in the bundle, which makes me wonder if any information is sent at all. Incidentally, it also breaks my test case.
Here's a simplified test case that shows my problem:
import android.os.Bundle;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
public class SimpleTest {
#Test
public void test() {
Bundle params = new Bundle();
params.putString("eventType", "click");
params.putLong("eventId",new Long(5542));
params.putLong("quantity", new Long(5));
params.putString("currency", "USD");
assertEquals("Did not find eventType=click in bundle", "click", params.getString("eventType"));
}
}
This test case fails with the following message:
junit.framework.ComparisonFailure: Did not find eventType=click in bundle
Expected :click
Actual :null
Would someone know where the problem is? That is, how do I create a Bundle object from zero and populate it correctly so that I can use it in a unit test like this?
Please bear with me on this one as I'm discovering the specifics of the Android environment as we speak.
As pointed out by Tanis.7x in a comment to my original question, all Android framework classes need to be mocked as the android.jar used for running unit tests is empty as documented here.
Here's an updated version of my original simplified test case:
import android.os.Bundle;
import org.junit.Test;
import org.mockito.Mockito;
import static junit.framework.Assert.assertEquals;
public class SimpleTest {
#Test
public void test() {
Bundle bundleMock = Mockito.mock(Bundle.class);
Mockito.doReturn("click").when(bundleMock).getString("eventType");
Mockito.doReturn(new Long(5542)).when(bundleMock).getLong("eventId");
Mockito.doReturn(new Long(5)).when(bundleMock).getLong("quantity");
Mockito.doReturn("USD").when(bundleMock).getString("currency");
assertEquals("Did not find eventType=click in bundle", "click", bundleMock.getString("eventType"));
}
}
The main difference is, that the variables I set earlier with simple getters are now set by using the appropriate functions of Mockito. The code is not as easy on the eyes, but it should allow me to obtain the wanted behaviour.
Try using .equals() to compare strings as assertEquals() also uses the .equal() method for its working.

Unzip a file using Apache Camel UnZippedMessageProcessor

Trying to unzip a file using Apache Camel, I tried the example given in http://camel.apache.org/zip-file-dataformat.html but I can't find UnZippedMessageProcessor class. Here's the code:
import java.util.Iterator;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.dataformat.zipfile.ZipFileDataFormat;
public class TestRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
ZipFileDataFormat zipFile = new ZipFileDataFormat();
zipFile.setUsingIterator(true);
from("file:src/test/resources/org/apache/camel/dataformat/zipfile/")
.unmarshal(zipFile).split(body(Iterator.class)).streaming()
.process(new UnZippedMessageProcessor()).end();
}
}
Anyone tried to do this or have another way to unzip a file through a Camel route?
Thank you in advance!
You can also define the route like this, you can find the ZipSplitter inside of camel-zipfile.
from("file:src/test/resources/org/apache/camel/dataformat/zipfile?consumer.delay=1000&noop=true")
.split(new ZipSplitter())
.streaming().convertBodyTo(String.class).to("mock:processZipEntry")
.end()
This would be a lot easier to figure out if the documentation wasn't so sparse. First, like someone else mentioned, the docs assume that you'll write your own Processor implementation. A simple one looks like this:
public class ZipEntryProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getIn().getBody().toString());
}
}
If you debug the process method, you'll see that the body of the input message is of type ZipInputStreamWrapper, which extends the Java class BufferedInputStream. That's useful information because it tells you that you could probably use Camel's built-in data transformations so that you don't have to write your own processor.
So here's how you consume a zip file and extract all of its entries to a directory on the file system:
from("file:src/test/resources/org/apache/camel/dataformat/zipfile/")
.split(new ZipSplitter())
.streaming()
.to("file://C:/Temp/")
.end();
It's actually ridiculously simple. Also, you have to make sure you understand the file component URI syntax properly too. That tripped me up the most. Here's an excellent blog post about that topic.

Categories

Resources