mzzzzb
mzzzzb

Reputation: 1452

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();
    }
}

Upvotes: 1

Views: 631

Answers (2)

Justin Bertram
Justin Bertram

Reputation: 35038

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.

Upvotes: 2

Gary Russell
Gary Russell

Reputation: 174564

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.
 * ...

Upvotes: 2

Related Questions