Search code examples
javajmsactivemq-artemisjavalite

How to create Apache Artemis queues in code and use them with JMS?


I'm migrating the JavaLite Async from Artemis 2.3.0 to 2.11.0 version. JavaLite Async does NOT use any file base-configuration, but instead relies on code.

Between v 2.3.0 and 2.11.0 the JMS management API is gone/deprecated now, and we are encouraged to use Core Management API.

Unfortunately I cannot find a way to:

  1. Create a queue programmatically
  2. Lookup that queue using JNDI to use JMS to send and receive messages.

Here is an example (imports are left for brevity):

class QueueLookup {
    private static final String LOCATION = "./target/artemis";

    private static EmbeddedActiveMQ server;

    public static void main(String[] args) throws Exception {
        try{
            Configuration configuration = new ConfigurationImpl()
                    .setPersistenceEnabled(true)
                    .setBindingsDirectory(LOCATION + "/bindings")
                    .setJournalDirectory(LOCATION + "/journal")
                    .setLargeMessagesDirectory(LOCATION + "/largemessages")
                    .setPagingDirectory(LOCATION + "/paging")
                    .setSecurityEnabled(false)
                    .addAcceptorConfiguration("invm", "vm://0")
                    .setJournalBufferTimeout_AIO(100)
                    .setJournalBufferTimeout_NIO(100)
                    .setJournalType(JournalType.NIO)
                    .setMaxDiskUsage(90);


            //the following three lines have no effect
            CoreQueueConfiguration coreQueueConfiguration = new CoreQueueConfiguration();
            coreQueueConfiguration.setName("Queue123").setDurable(true);
            configuration.addQueueConfiguration(coreQueueConfiguration);


            server = new EmbeddedActiveMQ();
            server.setConfiguration(configuration);
            server.start();


            TransportConfiguration transportConfiguration = new TransportConfiguration(InVMConnectorFactory.class.getName());
            ConnectionFactory connectionFactory = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, transportConfiguration);

            Hashtable<String, String> jndi = new Hashtable<>();
            jndi.put("java.naming.factory.initial", "org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory");
            jndi.put("connectionFactory.ConnectionFactory", "vm://0");
            //# queue.[jndiName] = [physicalName]
            jndi.put("queue.queue/Queue123", "Queue123");

            InitialContext initialContext = new InitialContext(jndi);
            Queue jmsQueue = (Queue) initialContext.lookup("queue/Queue123");

            try (Connection connection = connectionFactory.createConnection()) {
                try(Session jmsSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)){
                    MessageProducer producer = jmsSession.createProducer(jmsQueue);
                    connection.start();
                    TextMessage message = jmsSession.createTextMessage("Hello, Artemis!");
                    producer.send(message);
                    System.out.println("Message sent: " + message.getText());
                }
            } catch (Exception ex){
                ex.printStackTrace();
            }

        }finally {
            server.stop();
        }
    }
}

These lines have no effect:

CoreQueueConfiguration coreQueueConfiguration = new CoreQueueConfiguration();
coreQueueConfiguration.setName("Queue123").setDurable(true);
configuration.addQueueConfiguration(coreQueueConfiguration);

However, if I remove this line:

jndi.put("queue.queue/Queue123", "Queue123");

then JNDI cannot find the queue.

On the surface, it seems that the I could "create" queues by adding their names to the JNDI:

jndi.put("queue.queue/Queue123", "Queue123");

however, this only works partially, with the queue seems to be existing to send and receive messages, while neither QueueControl nor QueueBrtowser can find it.

Can someone please explain how I can do this in code (no XML configuration):

  1. Create a queue and pass all necessary parameters (durable, etc)
  2. Find this queue using JNDI
  3. Control this queue using QueueControl
  4. Browse this queue using QueueBrowser.

A full example complete with a GUI version can be found here: https://github.com/ipolevoy/artemis-sanbox/blob/master/src/main/java/examples/

Much appreciate any help!


Solution

  • There appear to be a couple misunderstandings here...

    First, the reason your CoreQueueConfiguration has "no effect" is because it isn't valid. It isn't valid because you haven't specified the name of the address that the queue will be bound to. The broker should log something like this when it comes up indicating that the configuration isn't valid:

    WARN  [org.apache.activemq.artemis.core.server] AMQ222275: Failed to deploy queue <queue name>: null
    

    As noted in the JMS-to-core mapping documentation, a JMS queue is a core address and an anycast core queue with the same name. Therefore, you should be using a configuration like this instead:

    coreQueueConfiguration.setAddress("Queue123").setName("Queue123").setDurable(true).setRoutingType(org.apache.activemq.artemis.api.core.RoutingType.ANYCAST);
    

    My guess is that things are partially working because by default the core JMS client will auto-create the destinations that it needs so whatever you configure in your JNDI properties will get automatically created regardless of the CoreQueueConfiguration you've specified. If you don't want to auto-create the underlying addresses and queues required for JMS destinations then you should disable this using the corresponding address settings. There are also settings to auto-delete addresses and queues when they are no longer used (i.e. when the message-count = 0 & counsumer-count = 0) which you'd probably want to disable as well. Here's an example:

    server.getAddressSettingsRepository().addMatch("#", new AddressSettings()
       .setAutoCreateQueues(false)
       .setAutoDeleteQueues(false)
       .setAutoCreateAddresses(false)
       .setAutoDeleteAddresses(false));
    

    Second, JNDI configuration is explained in this bit of documentation:

    JMS destinations are also typically looked up via JNDI. As with connection factories, destinations can be configured using special properties in the JNDI context environment. The property name should follow the pattern: queue.<jndi-binding> or topic.<jndi-binding>. The property value should be the name of the queue hosted by the Apache ActiveMQ Artemis server.

    This explains why you need this line:

    jndi.put("queue.queue/Queue123", "Queue123");
    

    The documentation also states:

    It is also possible to look-up JMS destinations which haven't been configured explicitly in the JNDI context environment. This is possible using dynamicQueues/ or dynamicTopics/ in the look-up string. For example, if the client wanted to look-up the aforementioned "OrderQueue" it could do so simply by using the string "dynamicQueues/OrderQueue". Note, the text that follows dynamicQueues/ or dynamicTopics/ must correspond exactly to the name of the destination on the server.

    This means that you could leave out the aforementioned line and use this kind of lookup instead:

    Queue jmsQueue = (Queue) initialContext.lookup("dynamicQueues/Queue123");
    

    That said, it's not clear why you would use JNDI here at all versus simply instantiating the destination with the JMS API. You could simplify that code substantially by removing all the JNDI code, e.g.:

    ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://0");
    
    try (Connection connection = connectionFactory.createConnection(); 
         Session jmsSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
       MessageProducer producer = jmsSession.createProducer(jmsSession.createQueue("Queue123"));
       connection.start();
       TextMessage message = jmsSession.createTextMessage("Hello, Artemis!");
       producer.send(message);
       System.out.println("Message sent: " + message.getText());
    } catch (Exception ex) {
       ex.printStackTrace();
    }
    

    To get the control for the queue use something like this:

    QueueControl coreQueueControl = (QueueControl) server.getManagementService().getResource(org.apache.activemq.artemis.api.core.management.ResourceNames.QUEUE + "Queue123");
    

    To browse the queue you can use a javax.jms.QueueBrowser. There's lots of tutorials on the web for that.

    Lastly, the decision to deprecate the JMS configuration and management bits was made before version 2.0.0 was released after we added support for AMQP, MQTT, and OpenWire. At that point it made sense to simplify the config and management down to just core resources (i.e. addresses, queues, and routing-types). Having all the core stuff plus corresponding config and management for JMS would have certainly caused confusion for users and would have also been hard to maintain. We spent a fair amount of time updating the documentation to explain how everything maps from the various supported protocols and APIs down to the core resources. This document provides an overview you may find helpful.