I'm new to ActiveMQ (version 5.9.1) and Apache Camel. I'm experimenting about a couple of routes. I wrote them in Java, then packed in a .jar and deployed on %ACTIVEMQ_HOME%/lib. In my custom foo-activemq.xml I added a camelContext tag with a package child tag. Here is my relevant .xml
...
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<package>edu.foo.amq.camel</package>
</camelContext>
...
And here are my two routes:
package edu.foo.amq.camel;
import org.apache.camel.builder.RouteBuilder;
public class NumberRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("jms:queue:number.queue")
.marshal("UTF-8")
.choice()
.when(header("readyToGo").isNotEqualTo(true))
.to("jms:queue:big.number.queue");
}
}
and
package edu.foo.amq.camel;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
public class BigNumberRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("jms:queue:big.number.queue")
.marshal("UTF-8")
.split(body().tokenize("\n")).streaming()
.process(new Processor() {
#Override
public void process(Exchange arg0) throws Exception {
arg0.getIn().setHeader("readyToGo", true);
System.out.println(arg0.getIn().getBody(
String.class));
}
})
.to("jms:queue:number.queue");
}
}
When I start my ActiveMQ broker I can see ... Total 0 routes, of wich 0 is started.. If I look at the ActiveMQ web console I can se the queues number.queue and big.number.queue up and running but my java routes don't. What am I missing?
UPDATE:
I remove the camelContext tag from my foo-activemq.xml configuration file and add the import tag instead:
...
<import resource="jetty.xml"/>
<import resource="foo-camel.xml"/>
...
Here is my foo-camle.xml configuration file, I largely use the %ACTIVEMQ_HOME%/examples/conf/camel.xml file. I put this file into %ACTIVEMQ_HOME%/conf:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<package>edu.foo.amq.camel</package>
</camelContext>
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent" >
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://amq-broker?create=false"/>
<property name="userName" value="${activemq.username}"/>
<property name="password" value="${activemq.password}"/>
</bean>
</property>
</bean>
but the result is the same: ActiveMQ says no routes
The Camel configuration XML file must be added to the activemq.xml configuration file:
<import resource="foo-activemq.xml" />
EDIT:
What else?
Rename foo-activemq.xml to the default activemq.xml
Verify again if your jar is really in the lib directory (I am sure you have done that already)
Add a simple test route to foo-camel-xml and check in the log file if this route is loaded, e.g.
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring" >
<package>edu.foo.amq.camel</package>
<route>
<from uri="direct:start" />
<log message="${body}" />
</route>
</camelContext>
Related
I'm trying to migrate from Camel 2.X to 3.X and have run in to a question about logging the routing trace.
Previously I have configured it like this in my application context xml-file:
<bean id="camelTracer" class="org.apache.camel.processor.interceptor.Tracer">
<property name="traceExceptions" value="false" />
<property name="traceInterceptors" value="true" />
<property name="logLevel" value="DEBUG" />
<property name="logName" value="com.mycompany.routing.trace" />
</bean>
<bean id="traceFormatter" class="org.apache.camel.processor.interceptor.DefaultTraceFormatter">
<property name="showBody" value="true" />
<property name="maxChars" value="0" />
</bean>
But that obviously does not work anymore.
From the migration guide on the Camel website:
"A new tracer has been implemented and the old tracer has been removed. The new tracer logs messages at the org.apache.camel.Tracing logger name which is hardcoded. The format of the output is also updated to make it better. The tracer can be customized."
If I set .tracing() at the start of my routes it does log the trace. The name is hardcoded which is fine, but I would like to change the level from INFO to DEBUG among other things.
Does anyone know where to find information on how to configure this "new" tracer (preferrably in an applicationContext.xml file)? Or anywhere else, maybe in the Java DSL route? Or if it is even possible?
Thanks!
Logging level of DefaultTracer cannot be changed by configuration. You need to implement customized Tracer and bind this implementation to registry.
Tracer:
public class TracerCustom extends DefaultTracer {
private static final Logger LOG = LoggerFactory.getLogger("com.stackoverflow.camel.TracerCustom");
#Override
protected void dumpTrace(String out) {
LOG.debug(out);
}
// Customize other methods if needed
}
Spring context:
<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 class="com.stackoverflow.camel.TracerCustom" />
<camelContext id="tracerCamelContext" xmlns="http://camel.apache.org/schema/spring">
<route trace="true">
<from uri="timer:test"/>
<to uri="log:test"/>
</route>
</camelContext>
</beans>
I created a camel route that reads an xml an puts a message in an ActiveMQ-Queue for every element in xml. All is working fine but the error-handling does not work. Here is the route:
#Override
public void configure() throws Exception {
from(fileIn)
.routeId(IN_ROUTE_ID)
.onCompletion()
.to(CommonRoutes.ENDPOINT_DIRECT_LOGGING)
.end()
.onException(UncategorizedJmsException.class)
.maximumRedeliveries(maxRetries)
.backOffMultiplier(multiplier)
.redeliveryDelay(initialDelay)
.end()
.unmarshal(jaxbDataFormat)
.process(fileProcessor)
.split(body()).stopOnException().shareUnitOfWork()
.process(domainWrapProcessor)
.to(ACTIVE_MQ_OUT + ":queue:" + checkNotNull(queueName))
.end()
;
}
And here the configuration of FileEndpoint:
public void init(){
this.setAutoCreate(false);
this.setFile(new File(checkNotNull(csvFolder)));
this.setCamelContext(checkNotNull(context));
this.setAntInclude(ANT_INCLUDE);
this.setMove(doneFolder);
this.setMoveFailed(errorFolder);
}
The intention is that on every exception the file is moved to moveFailed folder and in case of JMSException (ActiveMQ offline) camel should retry and if finally fails also move to moveFailed.
I have created a unit tests for both cases. here is the test for any exception:
#Test
public void testException throws InterruptedException {
amqMmock.setExpectedMessageCount(3);
amqMock.whenAnyExchangeReceived((e) -> {
throw new RuntimeException("");
});
assertMockEndpointsSatisfied();
Thread.sleep(1000);
errorContainsFile(true);
}
This test is passing the file is moved into error folder set. But this is not working if i run this route in real (camel blueprint on fuse):
The problem is that on any (not UncategorizedJmsException) exception the file just keeps locked (.camelLock file created). (thanks to stopOnException the desired behavior for JMSExceptions is working)
So how to get camel to move the file to moveFailed folder on any exception?
To the best of my knowledge, moveFailed will only actually move the file to the error-dir if something goes wrong within the file-component itself (at least what I've experienced). This would explain why your file is not moved to the error-dir upon hitting an exception.
What you could try to do is perhaps something like this:
.onException(UncategorizedJmsException.class)
.maximumRedeliveries(maxRetries)
.backOffMultiplier(multiplier)
.redeliveryDelay(initialDelay)
.to(errorFolder)
.end()
Here is how I would have set up your route using blueprint with XML rather than Java DSL (well, roughly, just to get the gist of it):
<!-- Redelivery Policy -->
<bean id="redeliveryPolicyConfig" class="org.apache.camel.processor.RedeliveryPolicy">
<property name="maximumRedeliveries" value="${redelivery.max.attempts}" />
<property name="redeliveryDelay" value="${redelivery.delay.ms}" />
<property name="logRetryAttempted" value="${redelivery.log.attempts}" />
</bean>
<!-- Error Handler -->
<bean id="errorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
<property name="deadLetterUri" value="direct:error" />
<property name="useOriginalMessage" value="true" />
<property name="redeliveryPolicy" ref="redeliveryPolicyConfig" />
</bean>
<camelContext>
<route id="IN_ROUTE_ID">
<from uri="file://{{fileIn}}?move={{doneFolder}}&moveFailed={{errorFolder}}" />
...
<to uri="activemq:queue:{{queueName}}" />
</route>
<route id="error-route">
<from uri="direct:error" />
<to uri="file://{{errorFolder}}" />
</route>
</camelContext>
So, now I am attempting to import routes from an XML file into the Java DSL.
I've been attempting to start with this link but since it's such a simple example, it doesn't really help me and doesn't point me to a more complicated example.
My problem is that my Camel routes use beans. Beans for the PropertiesComponent and FileIdempotentRepository and others are defined within the XML file for use by the routes in the XML file.
My original Spring configuration looked something like the following:
<?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-4.2.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="bean1" class="class1" />
<bean id="bean2" class="class2" />
<bean id="bean3" class="FileIdempotentRepository"> [...] </bean>
<bean id="properties" class="PropertiesComponent"> [...] </bean>
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="{{someplace}}&filter=#bean1" />
<setHeader headerName="FileRepoKey">
<simple>${file:name}-${file:modified}</simple>
</setHeader>
<idempotentConsumer messageIdRepositoryRef="bean3">
<header>FileRepoKey</header>
<process ref="bean2" />
<to uri="{{otherplace}}"/>
</idempotentConsumer>
</route>
</camelContext>
</beans>
So how do I convert this mess into something usable by the Java DSL to import routes from?
I understand from looking at that link that I need to do something like convert <camelContext> to <routes>. But leaving in the beans gives me an error along the lines of:
Exception in thread "main" javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.springframework.org/schema/beans", local:"beans"). Expected elements are [...]
What do I need to change? Or can I not have beans in the XML file in order for it to be imported by the Java used in the link?
I guess I should've asked this a different way and maybe someone would have thought of this way.
It may give you all nightmares, I'm not sure. Be warned.
So since the concept is "have things potentially run from an XML file alongside Java" the following end result came about:
public static void main(String[] args) throws Exception {
Main main = new Main();
//the XML file has a CamelContext in it.
main.setApplicationContextUri("myRoutes.xml");
main.start();//instantiates the CamelContext so we can use it in Java
List<CamelContext> camelContexts = main.getCamelContexts(); //should only have 1 item in the list
CamelContext context = camelContexts.get(0);
//in order to add a component to the registry the following is needed for set up
// afterwards, should just be able to add anything to the registry with registry.put("name", object)
final SimpleRegistry registry = new SimpleRegistry();
final CompositeRegistry compositeRegistry = new CompositeRegistry();
compositeRegistry.addRegistry(context.getRegistry());
compositeRegistry.addRegistry(registry);
((DefaultCamelContext) context).setRegistry(compositeRegistry);
final FileIdempotentRepository myFileStore = new FileIdempotentRepository();
File myFile = new File("idempotentRepoFiles/myFileStore.txt");
final TimeStampFileFilter<?> myFileFilter = new TimeStampFileFilter<Object>(0L);
registry.put("myFileFilter", myFileFilter);
//512MB
myFileStore.setMaxFileStoreSize(536870912L);
myFileStore.setFileStore(myFile);
myFileStore.setCacheSize(100000);
//add a route to the CamelContext that was initially created in the XML file
context.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
onException(myException.class)
.handled(true);
onException(GenericFileOperationFailedException.class)
.onException(SocketException.class)
.maximumRedeliveries(2)
.redeliveryDelay(5000L)
;
Processor myProcessor = new myProcessor();
from("{{myStart}}&filter=#myFileFilter")
.setHeader("myFileRepoKey", simple("${file:name}-${file:modified}"))
.idempotentConsumer(header("myFileRepoKey"), myFileStore)
.process(myProcessor)
.to("{{myEnd}}")
;
}
});
context.start();
main.run();
}
Basically: create a CamelContext in the Spring XML file, initialize it, grab it, modify it to include routes built in Java.
Route definition in Camel can be XML based (Spring DSL or Blueprint DSL) or Java based (Java DSL). A route definition can be expressed equally in both languages.
In a Spring application you can define your beans in a file and your routes in other files which you import. Routes defined in external files can refer to beans defined in your main file.
spring-main.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-4.2.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="bean1" class="class1" />
<bean id="bean2" class="class2" />
<bean id="bean3" class="FileIdempotentRepository"> [...] </bean>
<bean id="properties" class="PropertiesComponent"> [...] </bean>
<import resource="camel-routes.xml"/>
<camelContext xmlns="http://camel.apache.org/schema/spring">
<routeContextRef ref="ExternalRoutes"/>
</camelContext>
</beans>
camel-routes.xml
<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-4.2.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<routeContext id="ExternalRoutes" xmlns="http://camel.apache.org/schema/spring">
<route id="ARoute">
<from uri="direct:startHere" />
<to uri="bean:bean3" />
</route>
</routeContext>
</beans>
You can import more than one external file, of course. Just name each RouteContext differently.
If you modify one of the RouteContexts you must then restart your application. If you need a more dynamic application, try using an OSGi container to run your Camel routes, so you can easily modularize your application and add/remove features at runtime.
I created a webservice with apache camel using CXF component as bellow :
blueprint.xml:
<bean class="ngtrend.ws.Testws"/>
<!-- Defined the server endpoint to create the cxf-rs consumer -->
<cxf:rsServer id="rsServer" address="http://localhost:9050/route"
serviceClass="ngtrend.ws.Testws" />
<camelContext xmlns="http://camel.apache.org/schema/blueprint">
<route id="timerToLog">
<from uri="cxfrs://bean://rsServer"/>
<to uri="bean:ngtrend.ws.HelloBean?method=test(Exchange)"/>
<log message="${body}"/>
</route>
</camelContext>
Testws.java:
public class Testws {
#GET
#Path("/test/{id}")
#Produces("application/xml")
//#Consumes("text/xml")
public Integer getAssets(#PathParam("id") int id){
return null;
}
}
and I would like to secure it forcing the customer to send ( or enter on a dialog box if using a browser) login and password (BASIC Http authentication). How can i make this configuration ?
In CXF framework, restful services authentication can be done by using the following approach:
<cxf:rsServer id="rsServer"
address="http://localhost:9050/route">
<jaxrs:serviceBeans>
<ref bean="serviceBean"/>
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="authenticationHandler"/>
</jaxrs:providers>
</cxf:server>
<bean id="serviceBean" class="ngtrend.ws.Testws"/>
<bean id="authenticationHandler" class="yourpackage.Class" />
Create your own handler for authenticationHandler that will implement import org.apache.cxf.jaxrs.ext.RequestHandler.
Use the authentication strategy needed in this class , for example authenticate against database etc.. This should allow for basic authentication.
You can write a class which implements ContainerRequestFilter. And then set it in the cxf:providers as below:
<bean id="authenticationHandler" class="a class which implements ContainerRequestFilter" />
<cxf:rsServer id="xxxRsServer"
address="/xxxservice" serviceClass="xxx.XXXService"
loggingFeatureEnabled="true" loggingSizeLimit="20">
<cxf:providers>
<ref component-id="authenticationHandler"/>
</cxf:providers>
</cxf:rsServer>
In this way, you could override below method
public void filter(ContainerRequestContext requestContext)
For example, you could make a simple authentication based on requestContext.getHeaderString("UserPassInfo"). If succeed, do nothing, otherwise call requestContext.abortWith(Response.status(401).header("WWW-Authenticate", "Basic").build());
I'm currently trying the new REST DSL of the Apache Camel 2.14.0 release. And as the title of this post state it, I got a conflict with a String bean. Let's show what's wrong.
Here is a valid XML file reduced to a test case. It only defines a String bean and a Camel context containing a rest endpoint and a single route called by the rest endpoint.
<?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-3.0.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd"
>
<bean id="source-directory" class="java.lang.String">
<constructor-arg type="java.lang.String" value="file:/opt/a/directory/data/audio" />
</bean>
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<dataFormats>
<json id="jack" library="Jackson" unmarshalTypeName="org.apache.camel.component.jackson.TestPojo"/>
</dataFormats>
<restConfiguration bindingMode="json" component="restlet" port="5117" />
<rest path="/rest-api/">
<get uri="/{words}/" consumes="application/json" produces="application/json">
<to uri="direct:words" />
</get>
</rest>
<route>
<from uri="direct:words" />
<transform>
<simple>${headers.words}</simple>
</transform>
</route>
</camelContext>
</beans>
To load and test this Camel context I use the following test case:
import org.apache.camel.CamelContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Test {
#org.junit.Test
public void testT() throws Exception {
final FileSystemXmlApplicationContext bean = new FileSystemXmlApplicationContext("src/test/resources/camelContext.xml");
final CamelContext context = bean.getBean("camelContext", CamelContext.class);
context.start();
Thread.yield();
Thread.sleep(600000);
}
}
It currently lead to the following error :
org.apache.camel.RuntimeCamelException: org.apache.camel.FailedToCreateRouteException: Failed to create route route2 at: >>> RestBinding <<< in route: Route(route2)[[From[rest:get:/rest-api/:/{words}/?produces=a... because of Provider com.sun.xml.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: "file" ne contient pas ObjectFactory.class ou jaxb.index
That is, "file" does not contain ObjectFactory.class or jaxb.index
It seems that removing the source-directory bean declaration OR the rest endpoint declaration solves the problem, so it seems there is an incompatibility between them; but, as a Camel newbie, I'm unable to figure out what the problem is.
Can someone give me clues? Am I doing something wrong?
Thanks in advance,
Arthur.
I believe this issue is occurring due to the mechanism through which you are loading your Spring and Camel contexts for your unit test. Consider instead using CamelTestSupport.
I rewrote this test using this paradigm and everything worked just fine.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "/META-INF/spring/camel-context.xml" })
public class Test extends CamelTestSupport {
#org.junit.Test
public void testT() throws Exception {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(new HttpGet(
"http://localhost:5117/rest-api/hello"));
assertEquals(200, response.getStatusLine().getStatusCode());
assertEquals("\"hello\"", new BufferedReader(new InputStreamReader(response.getEntity()
.getContent(), StandardCharsets.UTF_8)).readLine());
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder()
{
#Override
public void configure() throws Exception {
from("direct:words").transform().simple("${headers.words}");
}
};
}
}
Thanks to a teammate, I finally understood the error.
Problem come from the definition of a "java.lang.String" bean in the camel context.
When putting
<bean id="source" class="java.lang.String">
<constructor-arg type="java.lang.String" value="file:/opt/a/directory/data/audio" />
</bean>
in the camel context I got the following log :
[main] JaxbDataFormat INFO Creating JAXBContext with contextPath: file:/opt/a/directory/data/audio and ApplicationContextClassLoader: sun.misc.Launcher$AppClassLoader#3485def8
My understanding of the situation is that if any java.lang.String object is defined it is then used by the JAXBContext as a factory identifier. They probably seek for an #Autowired String attribute that is normally always null.
Replacing "source" bean by a bean that encapsulate my sting in an attribute solve the problem.
Unexpected side effect discovered !