Sending a message to an embedded HornetQ from an external application - spring-boot

I am using spring-boot 1.2.2.
I have an embedded hornet queue setup in application.properties:
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.queues=myQueue
I want to add a message to "myQueue" from an external application (not the one with the embedded queue). Is this possible?
In the other application (the one without the embedded hornetq), I tried creating a connectionFactory that points to the embedded hornetq server but I don't really know what port I should be using. According to the spring-boot documentation it says it is only valid for "native" mode.
spring.hornetq.mode= # connection mode (native, embedded)
spring.hornetq.host=localhost # hornetQ host (native mode)
spring.hornetq.port=5445 # hornetQ port (native mode)
here is my code so far:
#EnableJms
#Configuration
public class HornetQConfig {
#Bean
public CachingConnectionFactory connectionFactory() {
CachingConnectionFactory cachingConnectionFactory =
new CachingConnectionFactory();
cachingConnectionFactory.setSessionCacheSize(10);
cachingConnectionFactory.setCacheProducers(false);
cachingConnectionFactory.setTargetConnectionFactory(hornetQConnectionFactory());
return cachingConnectionFactory;
}
#Bean
public HornetQConnectionFactory hornetQConnectionFactory() {
HornetQConnectionFactory connectionFactory =
new HornetQConnectionFactory(false, transportConfiguration());
return connectionFactory;
}
#Bean
public TransportConfiguration transportConfiguration() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("host", "localhost");
map.put("port", 5445);
TransportConfiguration configuration =
new TransportConfiguration(
"org.hornetq.core.remoting.impl.netty.NettyConnectorFactory", map);
return configuration;
}
}
And then:
#Autowired
private JmsTemplate jmsTemplate;
#Scheduled(fixedDelay = 1000L)
public void send() {
this.jmsTemplate.convertAndSend("myQueue", "Hello from external app");
}
But I am getting a connection problem.
Failed to create session factory; nested exception is HornetQNotConnectedException[errorType=NOT_CONNECTED message=HQ119007: Cannot connect to server(s)

The issue is that the embedded HornetQ server is configured with only an InVMAcceptorFactory by default. You need to add an AcceptorFactory that actually listens on a port, like NettyAcceptorFactory.
You can use the HornetQConfigurationCustomizer to configure this. Below example uses a hardcoded host/port, but you can easily create your own properties to make this configurable.
#Bean
public HornetQConfigurationCustomizer hornetCustomizer() {
return new HornetQConfigurationCustomizer() {
#Override
public void customize(Configuration configuration) {
Set<TransportConfiguration> acceptors = configuration.getAcceptorConfigurations();
Map<String, Object> params = new HashMap<String, Object>();
params.put("host", "localhost");
params.put("port", "5445");
TransportConfiguration tc = new TransportConfiguration(NettyAcceptorFactory.class.getName(), params);
acceptors.add(tc);
}
};
}
In your application with the embedded server, you configure it as embedded (as I believe you already have anyway, just to make sure):
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.queues=myQueue
And in your "other" application that you want to connect to the embedded server, you configure HornetQ in native mode:
spring.hornetq.mode=native
spring.hornetq.host=localhost
spring.hornetq.port=5445

Related

Java JobRunr when using Spring Boot Redis Starter

How do I create and use the Redis connection that spring-boot-starter-data-redis creates? It doesn't seem like there is a Bean for RedisClient created by the default auto configuration so I'm not sure of the best way to do this.
The documentation does state that in this case you need to create the StorageProvider yourself which is fine, but can you reuse what Spring Boot has already created. I believe this would need to be a pooled connection which you would also need to enable through Spring Boot.
RedisTemplate offers a high-level abstraction for Redis interactions:
https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:template
Redis autoconfiguration :
#AutoConfiguration
#ConditionalOnClass({RedisOperations.class})
#EnableConfigurationProperties({RedisProperties.class})
#Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
#Bean
#ConditionalOnMissingBean(
name = {"redisTemplate"}
)
#ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
#Bean
#ConditionalOnMissingBean
#ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
Here you can find the corresponding configuration properties(including connection pool default configuration).
Simple implementation example :
https://www.baeldung.com/spring-data-redis-tutorial

How can I configure RabbitMQ authentication mechanism in Spring Boot?

#Configuration
#EnableRabbit
public class RabbitConfiguration {
private static final String queueName = "3055";
private static final String topicExchangeName = queueName + "-exchange";
#Bean
Queue queue() {
return new Queue(queueName, false);
}
#Bean
TopicExchange exchange() {
return new TopicExchange(topicExchangeName);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("foo.bar.#");
}
#Bean
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
MessageConverter messageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
Code above is my Spring Boot Project's RabbitMQ configuration class.
However, I cannot connect the RMQ server since below error pops up every time I try to connect.
Caused by: com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.
The Server provider told me that I need to set the authentication mechanism to AMQPLAIN.
My question is that How can I set authentication mechanism to AMQPLAIN?
No matter how much I google, I couldn't figure out how.
I confirm to #Raja Anbazhagan. Check the RabbitMQ logs first. Supposedly your user credentials were guest/guest.
The easiest way to solve your problem could be to add those lines in your application.yml:
spring:
rabbitmq:
username: <user-name>
password: <user-password>

Spring Tomcat configuration for JMS (IBM MQ, Tomcat, Spring)

I have a relatively old application that uses Websphere MQ for messaging. It runs on WAS (Websphere Application Server) and uses MDBs (Message Driven Beans). I have to migrate that application from Websphere so Tomcat
I tried something using springboot and was able to write a sample JMS application that connects to queues and read messages and is able to process them but have not implemented transaction management with JMS.
Now I have been asked to configure the application so that it runs on tomcat.
Can anyone please help, how and where I have to setup configuration in tomcat.
Or what all changes will be required if package my springboot application as war and deploy it on Tomcat.
This is how my code in applicationconfig.java looks like
#Bean(name = "mqQueueConnectionFactory")
public MQQueueConnectionFactory mqQueueConnectionFactory() {
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
try {
mqQueueConnectionFactory.setHostName("hostname");
mqQueueConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
mqQueueConnectionFactory.setCCSID(1208);
mqQueueConnectionFactory.setChannel("channel");
mqQueueConnectionFactory.setPort(1415);
mqQueueConnectionFactory.setQueueManager("qManager");
} catch (Exception e) {
System.out.println("MQQueueConnectionFactory bean exception" + e);
}
return mqQueueConnectionFactory;
}
#Bean
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter(
MQQueueConnectionFactory mqQueueConnectionFactory) {
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter = new UserCredentialsConnectionFactoryAdapter();
userCredentialsConnectionFactoryAdapter.setUsername("");
userCredentialsConnectionFactoryAdapter.setPassword("");
userCredentialsConnectionFactoryAdapter.setTargetConnectionFactory(mqQueueConnectionFactory);
return userCredentialsConnectionFactoryAdapter;
}
#Bean
#Primary
public CachingConnectionFactory cachingConnectionFactory(
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter) {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setTargetConnectionFactory(userCredentialsConnectionFactoryAdapter);
cachingConnectionFactory.setReconnectOnException(true);
return cachingConnectionFactory;
}
#Bean
public JmsOperations jmsOperations(CachingConnectionFactory cachingConnectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
jmsTemplate.setReceiveTimeout(50000);
return jmsTemplate;
}
#Bean(name = "wmq")
public JmsComponent wmQ(#Value(AppConstants.WMQ_CONNECTION_TYPE) int connType,
#Value(AppConstants.WMQ_HOST) String hostName,
#Value(AppConstants.WMQ_PORT) Integer port,
#Value(AppConstants.WMQ_QUEUE_MANAGER) String queueManager,
#Value(AppConstants.WMQ_CHANNEL) String channel,
#Value(AppConstants.WMQ_CONCURRENT_CONSUMERS) int concurrentConsumers,
#Value(AppConstants.WMQ_USERNAME) String username,
#Value(AppConstants.WMQ_PASSWORD) String password
) throws JMSException {
JmsComponent jmsComponent = new JmsComponent();
MQConnectionFactory mqConnectionFactory = new MQConnectionFactory();
try {
mqConnectionFactory.setTransportType(connType);
mqConnectionFactory.setHostName(hostName);
mqConnectionFactory.setPort(port);
mqConnectionFactory.setQueueManager(queueManager);
mqConnectionFactory.setChannel(channel);
jmsComponent.setConnectionFactory(mqConnectionFactory);
JmsConfiguration jmsConfiguration = new JmsConfiguration(mqConnectionFactory);
jmsConfiguration.setUsername(username);
jmsConfiguration.setPassword(password);
jmsConfiguration.setConcurrentConsumers(concurrentConsumers);
jmsComponent.setConfiguration(jmsConfiguration);
} catch (JMSException e) {
String msg = "Error while creating IBM MQ Connection Factory";
throw new JMSException(msg);
}
return jmsComponent;
}

Spring JMS HornetQ user is null

I am trying to connect to a remote HornetQ broker in a spring boot/spring jms application and setup a #JmsListener.
HornetQ ConnectionFactory is being fetched from JNDI registry that HornetQ instance hosts. Everything works fine as long as HornetQ security is turned off but when it is turned on I get this error
WARN o.s.j.l.DefaultMessageListenerContainer : Setup of JMS message listener invoker failed for destination 'jms/MI/Notification/Queue' - trying to recover. Cause: User: null doesn't have permission='CONSUME' on address jms.queue.MI/Notification/Queue
I ran a debug session to figure out that ConnectionFactory instance being returned is HornetQXAConnectionFactory but user and password fields are not set, which I believe is why user is null. I verified that user principal and credentials are set in JNDI properties but somehow it is not being passed on to ConnectionFactory instance. Any help on how I can get this setup working would be greatly appreciated.
This is my jms related config
#Configuration
#EnableJms
public class JmsConfig {
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setDestinationResolver(destinationResolver());
return factory;
}
#Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.BYTES);
converter.setTypeIdPropertyName("_type");
return converter;
}
#Value("${jms.jndi.provider.url}")
private String jndiProviderURL;
#Value("${jms.jndi.principal}")
private String jndiPrincipal;
#Value("${jms.jndi.credentials}")
private String jndiCredential;
#Bean
public JndiTemplate jndiTemplate() {
Properties env = new Properties();
env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
env.put("java.naming.provider.url", jndiProviderURL);
env.put("java.naming.security.principal", jndiPrincipal);
env.put("java.naming.security.credentials", jndiCredential);
return new JndiTemplate(env);
}
#Bean
public DestinationResolver destinationResolver() {
JndiDestinationResolver destinationResolver = new JndiDestinationResolver();
destinationResolver.setJndiTemplate(jndiTemplate());
return destinationResolver;
}
#Value("${jms.connectionfactory.jndiname}")
private String connectionFactoryJNDIName;
#Bean
public JndiObjectFactoryBean connectionFactoryFactory() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiTemplate(jndiTemplate());
jndiObjectFactoryBean.setJndiName(connectionFactoryJNDIName);
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(ConnectionFactory.class);
return jndiObjectFactoryBean;
}
#Bean
public ConnectionFactory connectionFactory(JndiObjectFactoryBean connectionFactoryFactory) {
return (ConnectionFactory) connectionFactoryFactory.getObject();
}
}
JNDI and JMS are 100% independent as they are completely different specifications implemented in potentially completely different ways. Therefore the credentials you use for your JNDI lookup do not apply to your JMS resources. You need to explicitly set the username and password credentials on your JMS connection. This is easy using the JMS API directly (e.g. via javax.jms.ConnectionFactory#createConnection(String username, String password)). Since you're using Spring you could use something like this:
#Bean
public ConnectionFactory connectionFactory(JndiObjectFactoryBean connectionFactoryFactory) {
UserCredentialsConnectionFactoryAdapter cf = new UserCredentialsConnectionFactoryAdapter();
cf.setTargetConnectionFactory((ConnectionFactory) connectionFactoryFactory.getObject());
cf.setUsername("yourJmsUsername");
cf.setPassword("yourJmsPassword");
return cf;
}
Also, for what it's worth, the HornetQ code-base was donated to the Apache ActiveMQ project three and a half years ago now and it lives on as the Apache ActiveMQ Artemis broker. There's been 22 releases since then with numerous new features and bug fixes. I strongly recommend you migrate if at all possible.
Wrap the connection factory in a UserCredentialsConnectionFactoryAdapter.
/**
* An adapter for a target JMS {#link javax.jms.ConnectionFactory}, applying the
* given user credentials to every standard {#code createConnection()} call,
* that is, implicitly invoking {#code createConnection(username, password)}
* on the target. All other methods simply delegate to the corresponding methods
* of the target ConnectionFactory.
* ...

Spring Boot JMS AutoStartup

I am trying to start/stop manually JMS listeners in my Spring Boot App. I am currently using the following configuration to my container factory:
#EnableJms
public class ConfigJms {
...
#Bean(name = "queueContainerFactory")
public JmsListenerContainerFactory<?> queueContainerFactory(ConnectionFactory cf) {
ActiveMQConnectionFactory amqCf = (ActiveMQConnectionFactory) cf;
amqCf.setTrustAllPackages(true);
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(amqCf);
**factory.setAutoStartup(false);**
return factory;
}
...
}
After testing factory.setAutoStartup(false); I am quite confused because even indicating to do not start any listener for this factory container, the listeners are already registered and started when the context starts.
I tested this situation by using a jmsListenerEndpointRegistry.
jmsListenerEndpointRegistry.isAutoStartup() is true and
jmsListenerEndpointRegistry. isRunning () is true before execute jmsListenerEndpointRegistry.start();
Is it necessary to configure anything else? Maybe I am omitting to override some auto-configuration.
EDIT 1: Invalid status of JmsListenerEndpointRegistry listeners
I detected a couple of inconsistences in my beans:
jmsListenerEndpointRegistry.getListenerContainerIds().size() is always 0.
jmsListenerEndpointRegistry.isAutoStartup() is just a return true method.
Even if I register a couple of listeners with annotations like this:
#JmsListener(containerFactory="queueContainerFactory", destination = "${dest}")
jmsListenerEndpointRegistry does not show information about these listeners status but they are connected to ActiveMQ on startup. (Checking the ActiveMQ admin console)
EDIT 2: #JmsListener starts even auto-startup is set to false
I checked the jmsListenerEndpointRegistry for each container and I do not know if this is a bug or I am not correctly defining the configuration. However, I am just defining the container factory as explained before with AUTO-START set to false and the both listeners are started and consuming messages (running).
From my Log file:
jmsListenerEndpointRegistry ID <org.springframework.jms.JmsListenerEndpointContainer#1>, Auto-Startup <false>, Running <true>
jmsListenerEndpointRegistry ID <org.springframework.jms.JmsListenerEndpointContainer#0>, Auto-Startup <false>, Running <true>
You must have something else going on - I just wrote a quick boot app (1.4.1) and the container is not started...
#SpringBootApplication
public class So39654027Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So39654027Application.class, args);
JmsListenerEndpointRegistry reg = context.getBean(JmsListenerEndpointRegistry.class);
MessageListenerContainer listenerContainer = reg.getListenerContainer("foo");
System.out.println(listenerContainer.isRunning());
}
#Bean(name = "queueContainerFactory")
public JmsListenerContainerFactory<?> queueContainerFactory(ConnectionFactory cf) {
ActiveMQConnectionFactory amqCf = (ActiveMQConnectionFactory) cf;
amqCf.setTrustAllPackages(true);
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(amqCf);
factory.setAutoStartup(false);
return factory;
}
#JmsListener(id="foo", destination = "so39654027", containerFactory = "queueContainerFactory")
public void listen(String foo) {
System.out.println(foo);
}
}
and...
2016-09-23 09:24:33.428 INFO 97907 --- [ main] com.example.So39654027Application : Started So39654027Application in 1.193 seconds (JVM running for 2.012)
false
I suggest you use a debugger in the container's start() method to see why it's being started.
Order is important, factory.setAutoStartup(autoStartup) after configure.
#Bean
public JmsListenerContainerFactory<?> ShipmentListenerFactory(#Qualifier("GSUBCachingConnectionFactory") CachingConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
// Added ability to disable not start listener
boolean autoStartup = env.getProperty("app-env.CKPT_QUEUE_AUTO_START",Boolean.class,true);
log.info("[MQ] CKPT_QUEUE_AUTO_START:{}",autoStartup);
configurer.configure(factory, connectionFactory);
factory.setAutoStartup(autoStartup);
// You could still override some of Boot's default if necessary.
return factory;
}

Resources