I'm trying to remove some boilerplate from routes in Camel.
For example, let's consider the two routes, which are similar and most of their inner stuff could be generated. I've created a component "template", which creates TemplateEndpoint, and modifyed an XML config to use the template component.
A custom method TemplateEndpoint.generateRoute (adding route definitions) is being called from StartupListener (defined in TemplateEndpoint.setSuffix).
So while Camel context starting, route definitions appear in the context, but the framework doesn't create route services for them and hence they don't get started.
How to start routes added in StartupListener?
Probably I'm facing an XY problem, and you can advice me a better approach to do the trick (i.e. adding and starting routes on the fly).
Similar two routes
<route id="route_A">
<from uri="restlet:/another1?restletMethods=GET" />
<to uri="first:run_A" />
<to uri="second:jump_A" />
<to uri="third:fly_A" />
</route>
<route id="route_B">
<from uri="restlet:/another2?restletMethods=GET" />
<to uri="first:run_B" />
<to uri="second:jump_B" />
<to uri="third:fly_B" />
</route>
Modified XML
<route id="route_A">
<from uri="restlet:/another1?restletMethods=GET" />
<to uri="template://?suffix=A" />
</route>
<route id="route_B">
<from uri="restlet:/another2?restletMethods=GET" />
<to uri="template://?suffix=B" />
</route>
Endpoint
public class TemplateEndpoint extends DefaultEndpoint {
/* some methods omitted */
// this endpoint creates a DefaultProducer, which does nothing
public void setSuffix(final String suffix) throws Exception {
this.getCamelContext().addStartupListener(new StartupListener() {
#Override
public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception {
generateRoute(suffix)
.addRoutesToCamelContext(context);
}
});
}
private RouteBuilder generateRoute(final String suffix){
final Endpoint endpoint = this;
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from(endpoint)
.to("first:run_" + suffix)
.to("second:jump_" + suffix)
.to("third:fly_" + suffix);
}
}
}
I would create a dynamic route builder e.g.
public class MyRouteBuilder extends RouteBuilder {
private String another;
private String letter;
public MyRouteBuilder(CamelContext context,String another, String letter) {
super(context);
this.another=another;
this.letter=letter;
}
#Override
public void configure() throws Exception {
super.configure();
from("restlet:/"+another+"?restletMethods=GET")
.to("first:run_"+letter)
.to("second:jump_"+letter)
.to("third:fly_"+letter);
}
}
and add it on some event e.g
public class ApplicationContextProvider implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
camelContext=(CamelContext)context.getBean("mainCamelContext");
camelContext.addRoutes(new MyRouteBuilder(camelContext, "another1","A"));
camelContext.addRoutes(new MyRouteBuilder(camelContext, "another2","B"));
..
Related
Java 8 and Apache Camel 2.19.5 here. I have the following bean processors:
#Component("foobarResolver")
public class FoobarResolver {
public List<Foobar> resolve(Fizzbuzz fizzbuzz) {
List<Foobar> foobars = new ArrayList<Foobar>();
// Use some logic in here to make the fizzbuzz populate the foobars list.
return foobars;
}
}
#Component("fizzbuzzProcessor")
public class FizzbuzzProcessor {
public FizzbuzzOutput process(Fizzbuzz fizzbuzz) {
// Blah whatever
}
}
And the following Camel route:
<route id="fizzbuzzHandler">
<!-- XML '<fizzbuzz>' messages get sent here by an upstream process -->
<from uri="activemq:fizzbuzzes"/>
<!-- Use XStream to deserialize the XML into a 'Fizzbuzz' POJO instance -->
<unmarshal ref="xs"/>
<split>
<method ref="bean:foobarResolver"/>
<to uri="activemq:analyze"/>
</split>
<!-- Now assuming our body is once again the Fizzbuzz we can just continue as normal... -->
<!-- Process the fizzbuzz -->
<to uri="bean:fizzbuzzProcessor"/>
<!-- Send fizzbuzzProcessor's output to 'output' queue -->
<to uri="activemq:output"/>
</route>
So as you can see, the deserialized Fizzbuzz instance gets sent to the FoobarResolver bean processor, which turns that instance into a List<Foobar> and then sends each Foobar off to the analyze queue, one by one. At least thats the intention of my design anyways!
What I'm curious is: after the split, what does the exchange body become? Does it "revert" back to the Fizzbuzz (which is what I want), or is the exchange body now the List<Foobar> produced by the FoobarResolver (which is NOT what I want)? If the body is now the List<Foobar>, how could I reconfigure things so that the FizzbuzzProcessor receives a Fizzbuzz instead?
It appears to revert to the pre-split body:
#SpringBootApplication
public class SocamelApplication extends RouteBuilder implements ApplicationRunner {
#Autowired
private FooProcessor fooProcessor;
public static void main(String[] args) {
SpringApplication.run(SocamelApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
Thread.sleep(5000);
}
#Override
public void configure() throws Exception {
from("timer://foo?period=100&repeatCount=1").setBody()
.constant(Arrays.asList("Hello", "World"))
.log("1 >>> ${body} ")
.split(body())
.log("2 >>> ${body}")
.bean(fooProcessor)
.log("3 >>> ${body}")
.end()
.log("4 >>> ${body}");
}
#Bean
public FooProcessor fooProcessor() {
return new FooProcessor();
}
}
class FooProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
String reverseMe = exchange.getIn()
.getBody(String.class);
String reversed = new StringBuilder(reverseMe).reverse()
.toString();
exchange.getOut()
.setBody(reversed);
}
}
Yields:
1 >>> Hello,World
2 >>> Hello
3 >>> olleH
2 >>> World
3 >>> dlroW
4 >>> Hello,World
I have multiple provider classes (Provider1 and Provider2), how do I decide what bean I use depending on the input parameter in the Processor class?
public class Processor{
private Provider provider;
public void process(String providerName) throws Exception {
// What should I do here to invoke either provider1 or provider2 depending on the providerName?
provider.doOperation();
}
}
public class Provider1 {
public void doOperation(Exchange exchange) throws Exception {
//Code
}
}
public class Provider2 {
public void doOperation(Exchange exchange) throws Exception {
//Code
}
}
This is the case of Factory pattern. You can create a (ProviderFactory) class, register all the providers and get provider based on value, e.g.:
class ProviderFactory(){
private List<Provider> providers = new ArrayList<>();
public Provider getProvider(String input){
if(input.equals("test1")){
//Find based on criteria
return provider1;
}else if(input.equals("test2")){
//Find based on criteria
return provider2;
}
}
public void registerProvider(Provider provider){
providers.add(provider);
}
}
You can call registerProvider method on application startup and add as many providers as you want. Once that is initialised, you can call getProvider method and return appropriate instance based on some criteria.
Please note that providers doesn't necessarily need to be a list, it can be any data structure. It depends on which structure suits your criteria the best.
Here's documentation/more examples for Factory pattern.
What about somthing like this?
1# into your processor class :
public class Processor{
private Map<Provider> providers;
public void process(String providerName) throws Exception {
Provider provider = providers.get(providerName);
provider.doOperation();
}
}
2# in your spring config:
<bean id="provider1" class="xx.yy.zz.Provider1"/>
<bean id="provider2" class="xx.yy.zz.Provider2"/>
<bean id="processor" class="xx.yy.zz.Processor">
<property name="providers">
<map>
<entry key="provider1" value-ref="provider1" />
<entry key="provider2" value-ref="provider2" />
</map>
</property>
</bean>
now for example if you call processor.process("provider1") it will call provider1.doOperation()
I'm writing an application using camel for deployment (eventually) in a fuse container. The nature of the project requires that I mix and match Java and XML DSL.
I'm having trouble getting the mock framework to work properly with blueprint.
Here's my unit test, based completely on the example here.
public class MockNotWorking extends CamelBlueprintTestSupport {
#Test
public void testAdvisedMockEndpointsWithPattern() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
mockEndpoints("log*");
}
});
getMockEndpoint("mock:log:foo").expectedBodiesReceived("Bye World");
getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
template.sendBody("direct:start", "Hello World");
// additional test to ensure correct endpoints in registry
assertNotNull(context.hasEndpoint("direct:start"));
assertNotNull(context.hasEndpoint("log:foo"));
assertNotNull(context.hasEndpoint("mock:result"));
// only the log:foo endpoint was mocked
assertNotNull(context.hasEndpoint("mock:log:foo"));
assertNull(context.hasEndpoint("mock:direct:start"));
assertNull(context.hasEndpoint("mock:direct:foo"));
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start").to("direct:foo").to("log:foo").to("mock:result");
from("direct:foo").transform(constant("Bye World"));
}
};
}
protected String getBlueprintDescriptor() {
return "OSGI-INF/blueprint/blueprint.xml";
}
}
I have copied verbatim the example here, and modified it very slightly so we extend CamelBlueprintTestSupport instead of CamelTestSupport. This requires over-riding getBlueprintDescriptor to point to my blueprint xml, in which I have defined one very basic (and completely irrelevant to the test) route:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<camelContext id="validationRoute" xmlns="http://camel.apache.org/schema/blueprint" >
<route id="validation">
<from uri="direct:validation" />
<log message="validating..." />
</route>
</camelContext>
</blueprint>
The test fails with:
java.lang.AssertionError: mock://log:foo Received message count. Expected: <1> but was: <0>
So this means the message did not reach the mock endpoint. Change CamelBlueprintTestSupport for CamelTestSupport and it works.
So how do I get mocks like this working correctly with blueprint?
When you use blueprint, it imports all the routes you have defined in the blueprint xml file(s), and adds them to the CamelContext.
The reason this breaks things is that the context.getRouteDefinitions().get(0) no longer refers to the only route - there are now more than one. So when you add the AdviceWithRouteBuilder, it could be added to the wrong route.
The following code fixes the problem (and will work for non-blueprint tests too):
List<RouteDefinition> routes = context.getRouteDefinitions();
for (int i=0; i<routes.size(); i++) {
context.getRouteDefinitions().get(i).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock all endpoints
mockEndpoints("log*");
}
});
}
======update======
A better way of doing this is rather than mocking all routes, find our specific route by id and then apply the advice. This means first setting the route id:
from("direct:start").routeId("start").to("direct:foo").to("log:foo").to("mock:result");
And then looking up the route by id and calling adviceWith as before:
context.getRouteDefinition("start").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock log endpoints
mockEndpoints("log*");
}
});
Actually it depends on what version of Camel you are using, some methods doesn't work as expected. Its improved a lot better after 2.12 if I remember right. (2.15 is way better)
After all my encounters with Camel unit testing, I ended up documenting all stuffs here:
http://bushorn.com/unit-testing-apache-camel/
http://bushorn.com/camel-unit-testing-using-mock-endpoint/
By the way, instead of this "mockEndpoints("log*");", I would do this:
weaveByToString(".direct:foo.").after().to("mock:log"); (This sounds weird, I know ;) )
or if you can set an endpoint id
weaveById("endpoint-id-of-direct-foo").after().to("mock:log");
or
weaveAddLast().to("mock:log");
So far I have implemented Spring XD processors, e.g. like this:
#MessageEndpoint
public class MyTransformer
{
#Transformer( inputChannel = "input", outputChannel = "output" )
public String transform( String payload )
{
...
}
};
However, I am stuck at implementing a custom sink now. The current documentation is not very helpful, since it simply configures something "magically" via XML:
<beans ...>
<int:channel id="input" />
<int-redis:store-outbound-channel-adapter
id="redisListAdapter" collection-type="LIST" channel="input" key="${collection}" auto-startup="false"/>
<beans:bean id="redisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<beans:property name="hostName" value="${host}" />
<beans:property name="port" value="${port}" />
</beans:bean>
</beans>
This will use the redis store-outbound-channel-adapter as a sink. However, the documentation does not tell me how to create a simple, generic sink that simply has one input channel and consumes a message.
So can anyone provide me with a minimal working example?
A sink is just like a processor but without an output channel; use a #ServiceActivator to invoke your code (which should have a void return).
#MessageEndpoint
public class MyService
{
#ServiceActivator( inputChannel = "input")
public void handle( String payload )
{
...
}
};
EDIT
For sources, there are two types:
Polled (messages are pulled from the source):
#InboundChannelAdapter(value = "output",
poller = #Poller(fixedDelay = "5000", maxMessagesPerPoll = "1"))
public String next() {
return "foo";
}
Message-driven (where the source pushes messages):
#Bean
public MySource source() {
// return my subclass of MessageProducer that has outputChannel injected
// and calls sendMessage
// or use a simple POJO that uses MessagingTemplate.convertAndSend(foo)
}
In my Architecture Activemq getting messages from Sql Server Database whenever DBchanges.
We written 3 consumer files name are FirstConsumer.java,SecondConsumer.java and ThirdConsumer.java for processing messages using Spring framework.
so far, we written *MiddileWare.java files for every consumer files.in the following way.
My Business code is written in MessageProcessing.java.for your understanding I posted firstConsumer.java coressponding files codes.
FirstConsumer.java :
import org.apache.camel.CamelContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstConsumer {
public static void main(String[] args) {
try {
ApplicationContext contextObject=new ClassPathXmlApplicationContext("bean.xml");
//Forwarding cursor to ConsumersMiddileWare class using CamelContext
CamelContext camelObject=contextObject.getBean("activeContext1", CamelContext.class);
} catch (Exception e) {
// TODO: handle exception
}
}
}
FirstMiddileWare.java :
import org.apache.camel.spring.SpringRouteBuilder;
public class ConsumersMiddileWare2 extends SpringRouteBuilder {
#Override
public void configure() throws Exception {
VariablesDeclarations vd=lookup("amqURL",VariablesDeclarations.class);
from(vd.getAmqLink()).transacted().to("bean:msgPro1?Method=Processor1");
}
}
MessageProcessing.java :
import org.apache.camel.Exchange;
public class MessageProcessing{
public void MessageProcessing(Exchange exe,String exeFilepath) {
//Business Code Here
}
public void Processor1(Exchange exe) {
MessageProcessing processorObject=new MessageProcessing();
processorObject.MessageProcessing(exe,"Rod1");
}
public void Processor2(Exchange exe) {
MessageProcessing processorObject=new MessageProcessing();
processorObject.MessageProcessing(exe,"Rod2");
}
}
Spring configuration xml file name as bean.xml with all consumer configurations.
bean.xml :
<!--FirstConsumer-->
<camelContext id="activeContext1" xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="activeMQRouter1" />
</camelContext>
<!--SecondConsumer-->
<camelContext id="activeContext2" xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="activeMQRouter2" />
</camelContext>
<!--ThirdConsumer-->
<camelContext id="activeContext3" xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="activeMQRouter3" />
</camelContext>
<!--FirstConsumer-->
<bean id="activeMQRouter1" class="ActivemqPackage.FirstMiddileWare"/>
<!--SecondConsumer-->
<bean id="activeMQRouter2" class="ActivemqPackage.SecondMiddileWare"/>
<!--ThirdConsumer-->
<bean id="activeMQRouter3" class="ActivemqPackage.ThirdMiddileWare"/>
so far we followed in above way. It's working fine. Now I am trying to Implement following way.
Following code was differnce code between First,Second,Third Middileware files.
SecondMiddileWare.java
from(vd.getAmqLink()).transacted().to("bean:msgPro1?Method=Processor2");
ThirdMiddileWare.java :
from(vd.getAmqLink()).transacted().to("bean:msgPro1?Method=Processor3");
I stuck in making of ConsumerMiddileWare.java file for all Consumer files.
If you not understand, let me know I will edit my question.
My Idea:
If we send any String format value from Consumer(FirstConsumer.java,...) file to MiddileWare(FirstMiddileWare.java,...).
Based on that value, I will call that corresponding process(Process1,..)method.
Thanks
couple of things...
just can you a content based router to route messages to the correct processor/method based on the 'String' value
you don't need separate CamelContexts for each RouteBuilder or even separate RouteBuilders for each route...