Migrating from CamelTestSupport to AbstractCamelTestNGSpringContextTests - apache-camel

I've been using Apache Camel since 3-4 months on Spring 4.0.7.RELEASE
I have several Camel 2.14.0 TestNG tests based on extending CamelTestSupport, in which I use some MockEndpoints.
I configured my routes by overriding the createRouteBuilder() method.
Now I would need also to inject some Spring beans in one of them, by #Autowired annotation.
By reading what is said at http://camel.apache.org/spring-testing.html, I understood that I've to extend AbstractCamelTestNGSpringContextTests now, which supports #Autowired, #DirtiesContext, and #ContextConfiguration.
While I understood that all MockEndpoints are no more accessible by getMockEndpoint() method, but by using #EndpointInject annotation, it is not clear to me is how I can express my routes, because createRouteBuilder() is not more available.
I saw that is possible to define producers and consumers by using annotations, but I cannot manage to understand how routes can be designed.
Many thanks to the community.

Alternatively to the solution given here, you may use the TestNG helper CamelSpringTestSupport in combination with AnnotationConfigApplicationContextif you want to initialize an annotated based Spring configuration context without the need of an additional XML Spring configuration file.
Camel configuration bean class using Spring annotations:
#Configuration
public class MyConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:test").to("mock:direct:end");
}
};
}
}
The TestNG test class extends CamelSpringTestSupport and the Spring configuration MyConfig is initialized with AnnotationConfigApplicationContext:
public class TestNGTest extends org.apache.camel.testng.CamelSpringTestSupport {
#EndpointInject(uri = "mock:direct:end")
protected MockEndpoint errorEndpoint;
#Produce(uri = "direct:test")
protected ProducerTemplate testProducer;
#Override
protected AbstractApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext(MyConfig.class);
}
#DirtiesContext
#Test
public void testRoute() throws InterruptedException {
// use templates and endpoints
}
}

Related

Working with apache camel Bean in Quarkus framework

I am trying to use apache camel with Quarkus. Previously I was using the spring-boot framework to develop camel integration. So there are lots of questions that I am still trying to figure out w.r.t. Quarkus framework.
Regarding: Bean
In spring-boot I could do something like this
#Configuration
public class JABXContextConfig {
#Bean
Unmarshaller jaxbUnmarshaller() throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(MyPOJO.class );
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
return jaxbUnmarshaller;
}
}
and then I could inject it into the class using DI
#Component
public class MyRestServiceRoute extends RouteBuilder {
private final JaxbDataFormat jaxb;
#Autowired
public MyRestServiceRoute(JaxbDataFormat jaxb) throws Exception{
this.jaxb = jaxb;
}
....
}
QUESTION:
How can I do the same in the Quarkus framework?
P.S> I tried replacing #Configuration with #ApplicationScoped and #Bean with #Dependent but it's not working.
Thanks,
I recommend taking a read through the Quarkus CDI documentation:
https://quarkus.io/guides/cdi
https://quarkus.io/guides/cdi-reference
There's also a basic overview of using CDI to configure Camel:
https://camel.apache.org/camel-quarkus/latest/user-guide/bootstrap.html#_cdi
https://camel.apache.org/camel-quarkus/latest/user-guide/cdi.html
In your examples, #Bean could be replaced by a producer method like:
public class JaxbDataFormatProducer {
#ApplicationScoped
JaxbDataFormat jaxbDataFormat() {
return new JaxbDataFormat();
}
}
And the #Autowired constructor argument might look like this (If there’s only one constructor then there's actually no need for #Inject):
#ApplicationScoped
public class MyRestServiceRoute extends BaseRouteBuilder {
private final JaxbDataFormat jaxb;
#Inject
public MyRestServiceRoute(JaxbDataFormat jaxb) throws Exception{
super(properties);
this.jaxb = jaxb;
}
}

Camel bean component invokes cached instance of #Named / #Dependent bean

In our application we are using Apache Camel with camel-cdi component in JBoss EAP 7.1 environment. After upgrade of Apache Camel to actual version the application started to behave incorrectly in parallel execution.
I have found, that bean component invokes always the same instance. From my understanding, bean with #Dependent scope should be always fresh instance for every CDI lookup.
I have tried endpoint parameter cache=false, which should be default, but the behavior stays the same. Also tried to specify #Dependent, which should be default too.
Attaching MCVE, which fails on Apache Camel 2.20.0 and newer. Works well with 2.19.5 and older. Full reproducible project on Github.
#ApplicationScoped
#Startup
#ContextName("cdi-context")
public class MainRouteBuilder extends RouteBuilder {
public void configure() throws Exception {
from("timer:test")
.to("bean:someDependentBean?cache=false");
}
}
#Named
//#Dependent //Dependent is default
public class SomeDependentBean implements Processor {
private int numOfInvocations = 0;
private static Logger log = LoggerFactory.getLogger(SomeDependentBean.class);
public void process(Exchange exchange) throws Exception {
log.info("This is: "+toString());
numOfInvocations++;
if (numOfInvocations!=1){
throw new IllegalStateException(numOfInvocations+"!=1");
} else {
log.info("OK");
}
}
}
Is there anything I can do in our application to change this behavior and use actual version of Apache Camel?
EDIT:
Removing tags camel-cdi and jboss-weld. I have created unit test, to simulate this situation without dependencies to camel-cdi and Weld. This test contains assertion to test JndiRegistry#lookup, which returns correct instance. According this test I believe, the issue is in bean component itself. Fails with version >=2.20.0 and passes with <=2.19.5
public class CamelDependentTest extends CamelTestSupport {
private Context context;
private JndiRegistry registry;
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:in")
.to("bean:something?cache=false");
}
};
}
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry registry = super.createRegistry();
registry.bind("something", new SomeDependentBean());
this.context = registry.getContext();
this.registry = registry;
return registry;
}
#Test
public void testFreshBeanInContext() throws Exception{
SomeDependentBean originalInstance = registry.lookup("something", SomeDependentBean.class);
template.sendBody("direct:in",null);
context.unbind("something");
context.bind("something", new SomeDependentBean()); //Bind new instance to Context
Assert.assertNotSame(registry.lookup("something"), originalInstance); //Passes, the issue is not in JndiRegistry.
template.sendBody("direct:in",null); //fails, uses cached instance of SameDependentBean
}
}
According CAMEL-12610 is Processor supposed to be singleton scope. This behavior was introduced in version 2.20.0. Do not implement Processor interface, instead annotate invokable method as #Handler.
Replace
#Named
public class SomeDependentBean implements Processor {
public void process(Exchange exchange) throws Exception {
}
}
with
#Named
public class SomeDependentBean {
#Handler
public void process(Exchange exchange) throws Exception {
}
}
If you cannot afford that as me, because it is breaking behavior for our app extensions, I have implemented simple component. This component have no caching and allows to invoke Processor directly from registry.
CdiEndpoint class
public class CdiEndpoint extends ProcessorEndpoint {
private String beanName;
protected CdiEndpoint(String endpointUri, Component component) {
super(endpointUri, component);
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
#Override
protected void onExchange(Exchange exchange) throws Exception {
Object target = getCamelContext().getRegistry().lookupByName(beanName);
Processor processor = getCamelContext().getTypeConverter().tryConvertTo(Processor.class, target);
if (processor != null){
processor.process(exchange);
} else {
throw new RuntimeException("CDI bean "+beanName+" not found");
}
}
}
CdiComponent class
public class CdiComponent extends DefaultComponent {
#Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
CdiEndpoint endpoint = new CdiEndpoint(uri, this);
endpoint.setBeanName(remaining);
return endpoint;
}
}
Usage
public void configure() throws Exception {
getContext().addComponent("cdi", new CdiComponent());
from("direct:in")
.to("cdi:something");
}

Simple Camel test fails with no messages recieved

Am using Spring Boot and I have just added camel to it.
I have a simple camel route setup :
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("file://in").to("file://out");
}
}
When I try to create simple test for this route with :
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class MyRouteTest extends CamelTestSupport {
#Autowired
private CamelContext camelContext;
#Produce(uri = "file://in")
private ProducerTemplate producerTemplate;
#EndpointInject(uri = "mock:file://out")
private MockEndpoint mockEndpoint;
#Test
public void routeTest() throws Exception {
mockEndpoint.expectedMessageCount(1);
producerTemplate.sendBody("Test");
mockEndpoint.assertIsSatisfied();
}
}
It fails with
mock://file://out Received message count. Expected: <1> but was: <0>
Not sure what could be a problem here. I have producer template that has uri as my route from point and am mocking to endpoint with EndpointInject and the the mock uri?
Fixed but not 100%
If I change route from real one
from("file://in").to("file://out");
to
from("file://in").to("mock:out");
And in my test override
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new MyRoute();
}
to create specific route
and strangest of all ! Had to remove :
#SpringBootTest
and after that
private CamelContext camelContext;
And then it started working !
But unfortunately not what I need, still there are things that need to be fixed, I would like to use my real prod route !
from("file://in").to("file://out");
And if possible not use advise on route , but just mock it , tried with
mock:file://out in test, but it didnt work :(
and also , it does not work with #SpringBootTest ??? very strange ?!
You need to add
#Override
public String isMockEndpoints() {
return "*";
}
This should mock all the enpoints and then you can use mock:file:out for example
If I am not misstaken you are mocking your output endpoint yet your endpoint endpoint is a file endpoint. When you send a message you need to drop a message to whereever the file endpoint is polling. Otherwise you need to mock that as well.

Using Guice for Dependency Injection with GAE Basic Scaling Instance

I have an application running on GAE (Basic Scaling) which uses Guice for dependency injection.
Guice Listener
public class GuiceListener extends GuiceServletContextListener {
private ServletContext servletContext = null;
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
servletContext = servletContextEvent.getServletContext();
super.contextInitialized(servletContextEvent);
}
/**
* Function to create Guice Injector
*/
#Override
protected Injector getInjector() {
return Guice.createInjector(
new GuiceServletModule(),
new ServiceInjectionModule()
);
}
}
Guice Servlet Module
public class GuiceServletModule extends GuiceSystemServiceServletModule {
#Override
protected void configureServlets() {
super.configureServlets();
Logger.info("Guice configure servelet");
filter("/*").through(NamespaceFilterCustom.class);
Map<String, String> params = new HashMap<>();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "com.my.service.endpoint");
params.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
//you can create your own filters to handle request and response differently
params.put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName());
params.put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, GZIPContentEncodingFilter.class.getName());
serve("/*").with(GuiceContainer.class, params);
}
}
Service Injection Module
public class ServiceInjectionModule extends AbstractModule {
#Override
protected void configure() {
bind(Service.class).to(ServiceImpl.class);
}
}
My endpoint class expects "Service" object to be injected. The application runs fine when used with GAE Auto Scaling. However the "Service" object is not getting injected when the application runs on GAE Basic Scaling. Null pointer exception is thrown when "Service" is accessed.

Change EndPoint details in CXF ServiceInfo

The environment CXF2.2.6 and Spring 2.5. On Startup JBOSS I need to read CXF properties and change End point details. From basic reading it gives me the idea that CXF Service Info class (org.apache.cxf.service.model.ServiceInfo) handle bindings,endpoints,messages,schemas and so on.
I can Extend CXFServlet and create my own custom servlet. Please advise me the way I can give my own details to Endpoint in startup and override what is given in Spring.xml
The below Spring bean should do what you wanted. Why do you want to override ServiceInfo class ? Any particular reason ?
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ServletContextAware;
public class CXFConfig implements InitializingBean{
#Autowired
Bus cxfBus;
#Override
public void afterPropertiesSet() throws Exception {
EndpointImpl endpoint = new EndpointImpl(cxfBus, new GdsAutomationServiceProviderImpl());
endpoint.setAddress("/public/api/service/v1");//WSDL URL
endpoint.setPublishedEndpointUrl(getEndPointAddress());
endpoint.publish();
}
public Bus getCxfBus() {
return cxfBus;
}
public void setCxfBus(Bus cxfBus) {
this.cxfBus = cxfBus;
}
public String getEndPointAddress() {
// Soap address location you need to define here
return "address"
}
#Override
public void setServletContext(ServletContext context) {
context.getServerInfo();
}
}

Resources