Reputation: 2848
I am writing an application using Akka, Akka-Camel and Spring for configuration. The application needs to act as a standalone JMS client against a variety of application servers, to which end it needs to set up the JMS connection factory using JNDI. I'm testing this with jBoss. I have the same problem with jBoss 5 and 6 (this seems to be a client-side Spring problem, not related to jBoss).
I am configuring the Spring beans with this xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:camel="http://camel.apache.org/schema/spring"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
">
<camelContext id="camel" trace="false" xmlns="http://camel.apache.org/schema/spring">
<jmxAgent id="agent" disabled="true"/>
</camelContext>
<jee:jndi-lookup id="jmsConnectionFactory" jndi-name="ConnectionFactory">
<jee:environment>
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp://192.168.0.109:1099
</jee:environment>
</jee:jndi-lookup>
<bean name="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
</beans>
As you can see I'm setting up:
With this configuration my app fails at startup with this:
java.lang.IllegalArgumentException: connectionFactory must be specified
at org.apache.camel.util.ObjectHelper.notNull(ObjectHelper.java:294) ~[camel-core.jar:2.10.4]
at org.apache.camel.component.jms.JmsConfiguration.createConnectionFactory(JmsConfiguration.java:1053) ~[camel-jms.jar:2.10.4]
at org.apache.camel.component.jms.JmsConfiguration.getConnectionFactory(JmsConfiguration.java:416) ~[camel-jms.jar:2.10.4]
at org.apache.camel.component.jms.JmsConfiguration.createListenerConnectionFactory(JmsConfiguration.java:1062) ~[camel-jms.jar:2.10.4]
at org.apache.camel.component.jms.JmsConfiguration.getListenerConnectionFactory(JmsConfiguration.java:435) ~[camel-jms.jar:2.10.4]
at org.apache.camel.component.jms.JmsConfiguration.configureMessageListenerContainer(JmsConfiguration.java:889) ~[camel-jms.jar:2.10.4]
at org.apache.camel.component.jms.JmsConfiguration.createMessageListenerContainer(JmsConfiguration.java:379) ~[camel-jms.jar:2.10.4]
This comes from this code in JmsConfiguration.java:
protected ConnectionFactory createConnectionFactory() {
ObjectHelper.notNull(connectionFactory, "connectionFactory");
return null;
}
So it looks like the Spring beans initialization is failing to associate / wire the beans as instructed here (extracted from the full XML Spring configuration previously pasted):
<bean name="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
I have also tried to create an intermediate JmsConfiguration bean, and setting the configuration property of the JmsComponent, instead of setting the connectionFactory property directly, but I get the same result in both setups.
By the way, I can wire the beans by code alright. I mean that this:
val connFactory = springContext.getBean[javax.jms.ConnectionFactory]("jmsConnectionFactory", classOf[javax.jms.ConnectionFactory])
camelContext.addComponent("jms", JmsComponent.jmsComponentAutoAcknowledge(connFactory))
works perfectly fine. So I know that I am getting the ConnectionFactory from JNDI, it's just that I can't hit the right Spring configuration to wire it in XML.
I need this application to be very configurable without recompiling, so getting the XML to work is a must for me.
In case it's not clear, the question is: How do I get Spring to set up my JmsComponent bean, with its connectionFactory set to the JNDI-obtained factory?
EDIT: The point of using Camel is that it should allow me to swap this component even for another of a different type. So today I'm using JMS, maybe tomorrow I'll be using TCP. This is why it would be important to be able to define everything in XML.
Upvotes: 3
Views: 3576
Reputation: 489
I believe the problem is that Akka uses it's own CamelContext rather than the one defined in the Spring configuration. In earlier versions of Akka it seemed it was possible to set the context used by Akka, but that no longer appears to be possible in the latest versions.
I have encountered the same issue, I use annotation based configuration, in Java (rather than Scala), and have got around the issue using this code:
@Bean
public ActorSystem getCamelActorSystem(ConnectionFactory factory) {
ActorSystem system = ActorSystem.create("some-system");
Camel camel = CamelExtension.get(system);
CamelContext camelContext = camel.context();
camelContext.addComponent("jms", JmsComponent.jmsComponentAutoAcknowledge(factory));
return system;
}
This injects a ConnectionFactory dependency defined elsewhere and uses this to add the jms component to the camelContext used by Akka.
Another solution may be to extend the CamelExtension code in some way to allow the camelContext dependency to be injected, as was previously possible. However, I am assuming they had good reason for the change, and so am leaving it alone. I think it is so they can ensure the context cannot be changed so the Actor System always uses the same Camel context, this is based on the following:
One CamelExtension is only loaded once for every one ActorSystem, which makes it safe to call the CamelExtension at any point in your code to get to the Apache Camel objects associated with it. There is one CamelContext and one ProducerTemplate for every one ActorSystem that uses a CamelExtension.
http://doc.akka.io/docs/akka/current/scala/camel.html#CamelExtension
Upvotes: 1
Reputation: 2422
Since your lookup seems to be working, this is probably more than you are looking for, but here is how I get a connection factory via jndi using a jndiTemplate (spring 3.1). Note, I provide the contection factory via configuration (via the transaction manager). Sorry for any typos, but you'll get the idea.
<bean id="jndiDestinationResolver" class="org.springframework.jms.support.destination.JndiDestinationResolver"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:connection.properties</value>
</property>
</bean>
<bean name="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="configuration" ref="jmsConfig" />
</bean>
<bean id="txManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="springConnectionFactory"/>
<bean>
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="springConnectionFactory" />
<property name="transactionManager" ref="txManager" />
<property name="testConnectionOnStartup" value="true" />
<property name="destinationResolver" ref="jndiDestinationResolver" />
</bean>
<bean id="springConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="clientId" ref="SubCid" />
<property name="reconnectOnException" ref="true" />
<property name="targetConnectionFactory">
<bean parent="jndiObjectFactory"/>
</property>
<property name="sessionCacheSize" value="1"/>
</bean>
<bean id="jndiObjectFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="TopicConnectionFactory"/>
<property name="jndiTemplate">
<ref bean="jndiTemplate"/>
</property>
</bean>
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<props>
<prop key="java.naming.provider.url">${db.jms.JNDIServerName}</prop>
</props>
</property>
</bean>
Upvotes: 0